Appearance
SOC 2 Controls Mapping
Evidence register for SOC 2 Type II Trust Services Criteria. Each row links a TSC criterion to the concrete implementation and the evidence artifact that proves it. Appended to every session that implements a relevant control; never rewritten retroactively.
Scope: CC (Common Criteria). Availability (A), Confidentiality (C), and Privacy (P) rows will be added if customers or the auditor request those categories.
CC6 — Logical and Physical Access Controls
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC6.1 — logical access controls restricting unauthorized access | Six narrow-scoped MongoDB users, one per service purpose. Each bound to a custom role with collection-level privileges. Prod app-write still exists and is documented as the exit criterion to remove. | ADR 0003, runbook, Atlas custom-role JSON exported on demand. | 2026-04-17 |
| CC6.3 — access is removed when no longer required | Temporary admin tmp_restore_admin created for mongorestore, deleted same session. Pattern: every break-glass credential has a teardown step in its runbook. | Session log 2026-04-17 "Temporary user created and deleted same session". | 2026-04-17 |
| CC6.6 — restrictions on logical access from outside boundaries | Staging Atlas project is fully isolated from prod project. No shared allowlist, no shared users, no shared roles. Staging allowlist is a single laptop IP; no 0.0.0.0/0. | ADR 0001, Atlas access-list export. | 2026-04-17 |
| CC6.6 (additional row) — restrictions on logical access from outside boundaries (cross-cluster reference reads) | Prod cluster reads non-PHI public CMS reference data from staging cluster via AWS PrivateLink (vpce-0c81aea11e29bb928 in prod VPC vpc-09201679b87261b6d, targeting Atlas endpoint service com.amazonaws.vpce.us-east-1.vpce-svc-0d8138ea0f6542afa). Traffic stays on AWS backbone — no public internet path, no IP allowlist drift. Identity-bound at AWS account level (PrivateLink) + Atlas Mongo authentication (app_read_staging user, read-only on askflorence database). Service approved by Atlas with connectionStatus=AVAILABLE. | ADR — pivot CMS-API-direct + cross-cluster (section "Cross-cluster reference reads via AWS PrivateLink"); Terraform infra/envs/prod/atlas-staging-privatelink.tf (named af-prod-to-staging-reference-pl); Atlas privateEndpoints describe output. | 2026-05-08 | | CC6.7 — transmission encryption | All MongoDB Atlas connections use TLS 1.2+ (Atlas-enforced floor). PrivateLink traffic is doubly protected: AWS-backbone-only path AT THE NETWORK LAYER + TLS at the application layer. No plaintext channel exists between prod ECS and either Atlas cluster. | Atlas connection-string SRV resolves to TLS-only endpoints; AWS PrivateLink eliminates public-network exposure. | 2026-05-08 |
CC7 — System Operations
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC7.2 — the entity detects events indicating a failure of controls | agent_audit_log enforced append-only at the database permission layer. No app-tier credential can mutate the log; tamper attempts produce not allowed to do action [update/remove] errors, which are themselves logged by Atlas. | ADR 0002, role JSON for role_writer_agents and role_admin_agents. | 2026-04-17 |
| CC7.2 (additional row) — detection of unauthorized cross-cluster data-flow scope | Static CI guard at scripts/audit/staging-collections-guard.ts runs on every PR via .github/workflows/staging-collections-guard.yml. Scans every getReferenceDb() call site in src/ + scripts/ and verifies the accessed collection is on STAGING_ALLOWED_COLLECTIONS (the cross-cluster data-classification allow-list in src/lib/db.ts). Fails the build if a developer (human or AI) introduces a cross-cluster read against a non-public collection. Catches three patterns: string-literal accesses, dynamic-name accesses (where static analysis can't verify the runtime collection name), and the inline (await getReferenceDb()).collection("…") form. Verified 2026-05-08 against synthetic violations of all three patterns — all caught with correct file:line + reason classification. Phase 2 live nightly drift check shipped 2026-05-09 (next row). | Workflow first-push run (success, 42s); scripts/audit/staging-collections-guard.ts; src/lib/db.ts STAGING_ALLOWED_COLLECTIONS; ADR 0004; #100 / ENG-239. | 2026-05-08 |
| CC7.2 (additional row) — detection of runtime privilege drift on cross-cluster reader | Live nightly CI check at scripts/audit/staging-cluster-drift.ts runs at 08:00 UTC daily via .github/workflows/staging-cluster-drift.yml (cron + manual dispatch). Audits the actual Atlas state of app_read_staging (the user prod authenticates as when reading formularies_staging + providers_staging over PrivateLink): verifies (1) user has exactly one role, (2) role is role_reader_reference@admin, (3) role grants FIND action only, (4) on exactly askflorence.formularies_staging + askflorence.providers_staging and nothing else, (5) no inheritedRoles. Catches the runtime-drift cases the static guard cannot — someone elevating privileges via Atlas Admin UI by hand, an out-of-band script changing the role, an extra role attached to the user. On failure: opens a P1 GitHub issue (labels compliance + priority-1) with the violation report and rollback snippets. Verified 2026-05-09 against three synthetic violations: (a) extra collection grant, (b) wider action (INSERT instead of FIND), (c) extra role on user — all three caught with correct violation category + detail line. Pre-tightening role (read@askflorence) replaced with custom role_reader_reference in lock-step; verified prod cross-cluster reads still healthy after the swap (drug_tier=PreferredBrand and network_tier=Preferred smoke responses byte-identical to baseline). | scripts/audit/staging-cluster-drift.ts; .github/workflows/staging-cluster-drift.yml; src/lib/db.ts STAGING_REFERENCE_READ_COLLECTIONS; docs/runbooks/atlas-user-provisioning.md Step H; ADR 0004; #100 / ENG-239. | 2026-05-09 |
CC8 — Change Management
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC8.1 — change management for controls protecting non-prod-isolation invariants | The cross-cluster Atlas PrivateLink architecture (ADR 0004) requires the staging cluster stay non-PHI for the segmentation argument to hold. Any PR that introduces a getReferenceDb() call against a collection not on the data-classification allow-list is rejected at build time before merge — preventing silent SOC 2 CC6.6 segmentation breach. The allow-list (STAGING_ALLOWED_COLLECTIONS in src/lib/db.ts) is reviewed quarterly per the cadence in docs/security-compliance/vendor-register.md. The allow-list is intentionally duplicated between the source-of-truth file (src/lib/db.ts) and the enforcement script (scripts/audit/staging-collections-guard.ts) — defense-in-depth so a developer can't make the script "self-approve" by importing the source list. | scripts/audit/staging-collections-guard.ts; src/lib/db.ts lines around STAGING_ALLOWED_COLLECTIONS; .github/workflows/staging-collections-guard.yml; ADR 0004. | 2026-05-08 |
| CC8.1 (additional row) — change management for cross-cluster reader role privileges | The cross-cluster reader app_read_staging was tightened from built-in read@askflorence to custom role_reader_reference@admin granting FIND action on exactly askflorence.formularies_staging + askflorence.providers_staging (the only collections the prod app actually reads via getReferenceDb()). Principle of least privilege; matches the STAGING_REFERENCE_READ_COLLECTIONS constant in src/lib/db.ts. The constants are duplicated between the source-of-truth file and the enforcement script (scripts/audit/staging-cluster-drift.ts) — same defense-in-depth pattern as Phase 1. Any out-of-band change to the user's role (Atlas Admin UI, ad-hoc atlas CLI invocation, role escalation script) is detected the next morning by the live nightly drift check (CC7.2 row above). Reviewed quarterly alongside STAGING_ALLOWED_COLLECTIONS. | scripts/audit/staging-cluster-drift.ts EXPECTED_READ_COLLECTIONS_SCRIPT_COPY; src/lib/db.ts STAGING_REFERENCE_READ_COLLECTIONS; .github/workflows/staging-cluster-drift.yml; docs/runbooks/atlas-user-provisioning.md Step B + Step H; ADR 0004. | 2026-05-09 |
CC1 — Control Environment
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC1.1 — commitment to integrity and ethical values | Founder + cofounders operate under documented Code of Conduct (acknowledgment captured at onboarding); engineering rules in CLAUDE.md Security rules section govern day-to-day behavior (no secrets in repo, no PHI in chat, etc.). | CLAUDE.md Security rules section; Onboard Team Member runbook. | 2026-05-11 |
| CC1.4 — commitment to attract, develop, retain competent individuals | Onboarding runbook enforces BAA-awareness brief + HIPAA workforce-awareness brief before any PHI access; quarterly access reviews verify continued competence + appropriate access scope. | Onboard Team Member runbook; Access reviews. | 2026-05-11 |
CC2 — Communication and Information
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC2.3 — communicates with external parties about its objectives, system, processes | Privacy notice (pending publish per #55) communicates data practices to consumers + agents; vendor BAA-inventory communicates subprocessor scope; quarterly access review records communicate operating cadence internally. | Privacy Impact Assessment; Vendor / Subprocessor Register; Access reviews. | 2026-05-11 |
CC3 — Risk Assessment
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC3.1, CC3.2 — identifies + analyzes risks; assesses fraud risk | Annual risk register at risk-assessment.md; first version 2026-05-11 with 16 identified risks scored Likelihood × Impact; mitigations tracked per row with owner + follow-up. | Risk Assessment. | 2026-05-11 |
| CC3.3 — considers potential for fraud | Risk R-006 (legacy app-write whole-DB credential) and R-012 (HubSpot gdpr-delete pattern) explicitly address insider + scripted-action fraud paths; mitigations in flight. | Risk Assessment R-006, R-012. | 2026-05-11 |
Updates to CC6 — Additional rows
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC6.2 — user registration + provisioning | Onboarding runbook enforces identity-domain provisioning across AWS SSO, Atlas, GitHub, Google Workspace, HubSpot with MFA-enrollment-before-access; ADR 0003 narrow-scoped users enforce least privilege per role; Phase 5 agent onboarding (magic link + TOTP per NIST 800-63B AAL2) is designed in agent platform compliance. | Onboard runbook; Access Control Policy; ADR 0003; Agent platform compliance. | 2026-05-11 |
| CC6.8 — protection against malware | AWS GuardDuty enabled org-wide (account-level threat detection); GuardDuty malware-protection plan covers agent-survey-uploads S3 bucket; GitHub secret scanning + Dependabot active on the main repo; macOS endpoint protection (built-in XProtect + FileVault) on founder + ops devices. | GuardDuty configuration at infra/envs/log-archive/; docs/infrastructure/guardduty-setup.md; GitHub org settings. | 2026-05-11 |
CC7 — Additional rows
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC7.1 — detection + monitoring of system anomalies | AWS GuardDuty (account threat detection, malware protection); AWS Security Hub (cross-service findings: CIS, FSBP, NIST 800-53 standards); CloudTrail org-trail (all AWS API events, 7-year retention in log-archive S3); Atlas database audit logs (DB-layer auth + admin actions); staging-cluster-drift workflow (nightly 08:00 UTC drift check on cross-cluster reader role, opens P1 issue on drift); staging-collections-guard workflow (per-PR static guard); validate-secrets workflow (per-PR secret-format check). | GuardDuty config; Security Hub config; CloudTrail org-trail in log-archive account; .github/workflows/staging-cluster-drift.yml, .github/workflows/staging-collections-guard.yml, .github/workflows/validate-secrets.yml. | 2026-05-11 |
| CC7.3 — incident response | Formal Incident Response Plan defines roles (IC = Taha, Comms = Ian, Compliance Liaison = Asad), severity classification (SEV-0/1/2/3), 5-step lifecycle (Detect → Contain → Assess → Notify → Remediate), regulatory notification timelines (HIPAA Breach Notification Rule 60-day clock from discovery); operational playbook in security-incident-response runbook; 5 worked-example incidents from 2026 documented. | Incident Response Plan; Security Incident Response runbook; session-log entries for 2026-04-10, 2026-04-30, 2026-05-06, 2026-05-09 incidents. | 2026-05-11 |
| CC7.4 — incident communication + disclosure | IRP defines notification recipients (affected individuals, HHS OCR, media for >500-affected breaches, state AGs, CMS EDE program contact, vendor BAA partners, investors, internal team) with timelines per HIPAA Breach Notification Rule. Comms Lead (Ian) owns external messaging; Compliance Liaison (Asad) owns regulatory clock. | Incident Response Plan, Step 4 — Notify. | 2026-05-11 |
CC9 — Risk Mitigation
| Criterion | Implementation | Evidence | Last updated |
|---|---|---|---|
| CC9.2 — vendor management; third-party risk | Vendor register catalogs every subprocessor with BAA / DPA / FedRAMP status; Tier 1/2/3 segmentation by data exposure; quarterly review for status drift; annual full BAA-PDF audit at audit prep; new-vendor adoption gated on BAA-signed-before-data-flows; PostHog removal in flight per #75 (currently on free tier without HIPAA BAA). | Vendor / Subprocessor Register; #57 (BAA chase); #75 (PostHog removal). | 2026-05-11 |
How to add a row
Each session that lands a control-relevant change MUST append a row here, including a link to the ADR / runbook / session log that is the evidence. Rows are additive; if a control is superseded, leave the old row and add a new one with a later Last updated date.