Appearance
Access Control Policy
Status: Active. Effective 2026-05-11. Owner: Taha Abbasi (technical implementation) + Asad Khalid (joiner / mover / leaver governance). Supersedes: the DB-focused
docs/infrastructure/access-control.md(now a pointer stub to this policy + Atlas implementation detail). Reviewed: Quarterly via access reviews; annually for policy-level changes.
Purpose
Define how human and service principals authenticate, what they are authorized to do, and how that access is provisioned, reviewed, and revoked. Required artifact for:
- HIPAA §164.308(a)(3) (workforce security), §164.308(a)(4) (information access management), §164.312(a)(1) (access control), §164.312(d) (person or entity authentication)
- SOC 2 CC6.1 (logical access controls), CC6.2 (provisioning), CC6.3 (access removal), CC6.6 (boundary access), CC6.7 (transmission encryption — see encryption policy), CC6.8 (malware protection)
- CMS EDE Phase 3 / MARS-E 2.2 AC-family controls; NIST 800-53 R4 Moderate AC controls
- State law where applicable (NY SHIELD reasonable safeguards)
Principle of least privilege
Every account, user, and service credential is granted the minimum access necessary for its function. No shared credentials. No persistent admin sessions. Every privilege escalation is auditable.
Identity sources
| Identity domain | Source of truth | MFA | Notes |
|---|---|---|---|
| AWS (all four org accounts) | AWS IAM Identity Center (SSO) provisioned under the management account | Required (TOTP today; hardware MFA tracked in #67) | Permission sets: admin, power_user, billing_ro, security_audit. Pre-positioned but not yet in Terraform state (Phase 3b import pending — see infra/envs/management/outputs-reference.md). |
| Google Workspace (founder + ops email) | Google Cloud Identity | Required (TOTP today; YubiKey planned alongside AWS) | Account owner = Taha; users today = founders + Asad |
| GitHub | GitHub.com user accounts | Required for org members | SAML SSO via Google Workspace planned at next-tier paid plan |
| MongoDB Atlas | Atlas organization | Required for all org members | Org Owner = Taha; per-project users follow least-privilege per ADR 0003 |
| HubSpot | HubSpot user accounts | Required | Users: Taha + Asad + Ian + agent-relations |
| Vercel (legacy) | Retired post Phase 10; org archived not deleted | n/a | Vercel project retained for rollback safety only |
AWS SSO permission sets
| Permission set | Granted to | Session duration | AWS managed policies | Use |
|---|---|---|---|---|
admin | Taha (today; Asad after hardware MFA) | 8h | AdministratorAccess | Full AWS access for infrastructure changes; should not be the daily-driver for routine work |
power_user | Taha (daily driver) | 8h | PowerUserAccess | Day-to-day engineering — read/write to most services, no IAM modification |
billing_ro | Asad | 4h | Custom billing-readonly policy + Billing | Cost monitoring, no write access |
security_audit | Future SOC 2 auditor + compliance-automation vendor manual review | 4h | SecurityAudit + ReadOnlyAccess | Read-only security posture review; same policy shape as DrataAutopilotRole (see compliance automation integration) |
Assignments live in the management account; replicated to each member account (mgmt / prod / staging / log-archive).
Database access (MongoDB Atlas)
Per ADR 0006 (supersedes ADR 0003 — ENG-279, 2026-05-12), the canonical model is 4 users per cluster at the Atlas layer. Pre-Phase-5 content (CMS marketplace data + agent waitlist PII) requires no row-level filtering at the DB layer; Phase 5 PHI evolution shifts the security boundary to JWT-in-middleware + MongoDB views rather than re-narrowing DB users.
| User | Role | Per cluster | Env var | Purpose |
|---|---|---|---|---|
app_read | Built-in read@askflorence | staging + prod | MONGODB_URI + MONGODB_REFERENCE_URI (cross-cluster) | All reads at runtime + audit re-validation |
app_write | Built-in readWrite@askflorence | staging + prod | MONGODB_WRITE_URI | All runtime + ingest writes (ENG-236 LARK MRF refresh, HubSpot sync, etc.) |
app_audit_writer | Custom role_audit_writer (FIND + INSERT on agent_audit_log only) | staging + prod | MONGODB_AUDIT_WRITE_URI | Append-only at the DB permission layer per ADR 0002 — UPDATE / REMOVE return "not authorized" regardless of app code. Phase 5 audit writers consume via getAuditDb() in src/lib/db.ts (defensive fallback to MONGODB_WRITE_URI while no consumer exists). |
app_admin_schema | Custom role_admin_schema (FIND + CREATE / LIST / DROP INDEX on agent_waitlist_submissions + agent_survey_responses + hubspot_sync_log only) | staging + prod | MONGODB_URI_ADMIN_SCHEMA (deploy-time ECS RunTask only — not bound to the runtime task role per ENG-266 Phase 3.5) | scripts/db/ensure-indexes.ts |
(legacy) app-write | readWrite@askflorence | prod | (transition aliases until Phase G) | Removal is the production exit criterion of Issue #56. See risk R-006. |
Plus the 9 legacy narrow users (app_read_local_staging, app_read_staging, app_writer_survey, app_writer_plans, app_writer_waitlist, app_writer_hubspot_sync, app_writer_agents, app_admin_agents, audit_reader) and the prod app-read + app-write users which stay active until Phase G (tracked at ENG-282, due 2026-05-14, hard-gated on ≥ 24h soak from ENG-279's PR merge).
Phase 5 PHI evolution — security boundary shifts up the stack (no new DB users)
When PHI lands at Phase 5 (member intake + agent portal + admin dashboard), the 4-user model holds by:
- JWT identity (app middleware). Agent JWTs carry
agent_id+agent_npn. Admin JWTs carryadmin_id+admin_role. Middleware extracts these into a per-request context. - MongoDB views, not per-tenant users. Per-tenant data isolation is enforced via views filtered on
agent_id/member_id. Agent portal route handlers query the view, never the underlying collection. Views are created at agent provisioning time (scripts/db/setup-agent-views.ts— Phase 5 work). The DB grants onapp_read/app_writestay unchanged. - Admin tier reads through underlying collections with WHERE clauses derived from the JWT role. Every admin read of a member record is audit-logged via
app_audit_writerinsert. - Audit-log writes continue to flow through
app_audit_writerviaMONGODB_AUDIT_WRITE_URI. Append-only property preserved across agent portal, admin dashboard, super-admin path. - Super-admin uses the
/sa-loginbreak-glass flow (CLAUDE.md): password (argon2id) + TOTP + IP allowlist. Connects with an Atlas user not held by any task role.
ENG-279 acceptance criteria cap total users at ≤ 5 per cluster. A possible 5th app_admin_full at Phase 5 lands at the cap only if the admin dashboard needs writes that views cannot express. Any 6th user requires a follow-up ADR superseding ADR 0006.
Authoritative live state: docs/infrastructure/atlas-access-matrix.md (CI-synced from infra/atlas/access-matrix.ts).
Service principals (no human authentication)
| Principal | Authenticated via | Permissions | Use |
|---|---|---|---|
GitHubActionsDeployRole (in prod + staging accounts) | GitHub OIDC, short-lived STS | Least-privilege inline policy: ECS task-def register, service update, ECR push, S3 deploy assets only | CI/CD prod + staging deploys |
DrataAutopilotRole (all four accounts) | Assume-role with placeholder ExternalId — never activated | SecurityAudit + ReadOnlyAccess + DrataAutopilotExtras inline | Pre-positioned for compliance automation vendor at signing — see compliance automation integration |
| ECS Fargate task role | Internal AWS STS | Read specific Secrets Manager secrets; write CloudWatch Logs; read S3 deploy assets; SES send from askflorence.health | App runtime |
| Atlas Programmatic Keys (for nightly drift check + Terraform Atlas provider) | Atlas API key | Project Read Only for the drift-check key; Project Owner for Terraform-managed projects | CI workflows |
Multi-factor authentication
| Surface | MFA today | MFA target | Tracking |
|---|---|---|---|
| AWS SSO console | TOTP enforced | Hardware MFA (YubiKey) enrolled for admin + power_user; TOTP retained as backup | #67 |
| Google Workspace | TOTP enforced | Same as AWS — YubiKey enrolled | #67 |
| Atlas org members | TOTP enforced | TOTP acceptable (Atlas does not yet support WebAuthn org-wide) | Quarterly review verifies enrollment |
| GitHub | TOTP enforced | Same as Atlas | Quarterly review verifies enrollment |
| HubSpot | TOTP enforced | Same | Quarterly review |
| Agent portal (Phase 5) | n/a yet | TOTP via authenticator app (NIST 800-63B AAL2) | agent platform compliance |
| Super-admin path (Phase 5+) | n/a yet | Password (argon2id) + TOTP + IP allowlist (Tailscale / static VPN) | agent platform compliance |
Provisioning
Joiner (new team member)
- Manager (Taha or Asad) creates a Linear / GitHub issue with the joining-context (role, start date, what access they need).
- Run the Onboard Team Member runbook — checklist of identity-domain account creation + role assignment + MFA enrollment.
- Document the date + grant in
docs/infrastructure/atlas-access-matrix.md(if Atlas access granted) AND in the current quarter's access review file atdocs/infrastructure/access-reviews/. - MFA enrollment is verified before any access is granted.
Mover (role change within the team)
- Document the role change + new access requirements (Linear / GitHub issue).
- Run the relevant additions per the onboard runbook + the relevant removals per the offboard runbook.
- The change is documented in the next access review.
Leaver (offboarding)
- Manager triggers the Offboard Team Member runbook — same-day access revocation across every identity domain.
- Credentials the person had direct access to (shared API keys, etc.) are rotated.
- Audit-log row written to
agent_audit_logper offboarding event. - The offboarding is documented in the next access review.
Quarterly access review
Triggered at the end of each calendar quarter. Documented in docs/infrastructure/access-reviews/<year>-Q<n>-review.md. The review confirms:
- SSO assignments are current — every assigned permission set still maps to a current employee + an in-scope role
- Atlas users are current — every user in the Atlas access matrix is in active use; deprecated users have been revoked
- MFA enrollment is verified — each user's MFA status confirmed per identity domain
- Vendor BAA + DPA status is current — no expired BAAs; new vendors have BAA in place
- Cross-cluster reader role is unchanged —
staging-cluster-driftnightly check passing - Allow-lists are current —
STAGING_ALLOWED_COLLECTIONSmatches actual usage - Cost alarms + retention TTLs in place — verified per data retention policy
The first review is 2026-Q2 (July 2026) — see ../infrastructure/access-reviews/2026-Q2-review.md.
Break-glass access
When the standard SSO path is unavailable (account lockout, MFA device lost, AWS Console outage), the break-glass procedure documented in docs/runbooks/break-glass-root-login.md is the only authorized fallback. Break-glass access:
- Is logged immediately (CloudTrail capture is automatic; the runbook adds an
agent_audit_logentry for application-side actions) - Must be terminated as soon as the standard path is restored
- Requires a post-event report at the next access review
Credential rotation
| Credential type | Rotation cadence | Owner | Mechanism |
|---|---|---|---|
| AWS SSO passwords | At login per Google Workspace policy (no AWS-side password) | n/a (SSO) | Google Cloud Identity |
| AWS Access Keys (long-lived) | Forbidden for human users. CI uses OIDC STS only. | n/a | n/a |
| Atlas user passwords | Annual + on incident | Taha | atlas dbusers update --password + Secrets Manager update-secret |
| Atlas Programmatic Keys (CI) | Annual | Taha | Atlas org settings + GitHub secrets gh secret set |
| HubSpot user passwords | Per HubSpot policy (90 days) + on incident | Per user | HubSpot UI |
| Google Workspace passwords | Per Cloud Identity policy + on incident | Per user | Google admin console |
| CMS Marketplace API key | Annual + on incident | Taha | CMS portal + Secrets Manager |
| SSH keys (if any) | n/a — no SSH-accessible infrastructure today | n/a | n/a |
| TLS certs | Auto-renewed by ACM | n/a | ACM-managed |
| KMS CMKs | Auto-rotation enabled (annual) | n/a | AWS-managed |
Network access
| Surface | Today | Future |
|---|---|---|
| AWS console + APIs | SSO with MFA only | + Conditional access policy (Tailscale-based IP allowlist or geographic restriction) at scale |
| Atlas org + project UI | Atlas user + MFA + IP allowlist (Taha's home IP + GitHub Actions runners) | Tighten after AWS cutover — staging/prod Atlas accessible only from prod VPC PrivateLink + a single break-glass admin IP |
Apex askflorence.health | Public + CloudFront + AWS WAF | Same |
| ECS task internal network | Private subnets only (no public IP) | Same |
| Database direct connections | TLS-only Atlas endpoints + IP allowlist | PrivateLink-only (planned) |
Account dormancy
Inactive identities are reviewed quarterly:
- No human SSO login in 90 days → review at quarterly access review; remove unless explicit retain rationale documented
- No Atlas user activity in 90 days → review at quarterly access review; revoke if not in use
- Old SSH keys / personal access tokens — none today; future tokens get a 1-year max lifetime
Vendor + sub-processor access
Each vendor with access to AskFlorence data is documented in vendor / subprocessor register with BAA / DPA status. New vendor adoption requires:
- Compliance Liaison (Asad) reviews the vendor's BAA / DPA status BEFORE any production data flows
- New row added to vendor register before contract signed
- Vendor evidence (signed BAA PDF) filed at
docs/infrastructure/evidence/<vendor>-<date>.pdf - Listed in privacy notice by next privacy-policy version
Reference
- Authoritative Atlas state:
docs/infrastructure/atlas-access-matrix.md - Atlas user provisioning runbook:
docs/runbooks/atlas-user-provisioning.md - Onboard / offboard runbooks:
docs/runbooks/onboard-team-member.md,docs/runbooks/offboard-team-member.md - Break-glass runbook:
docs/runbooks/break-glass-root-login.md - ADR 0003 — Narrow-scoped MongoDB users
- Encryption Policy — credential storage at rest
- Data Retention Policy — credential retention windows
- SOC 2 Control Mapping — CC6.1, CC6.2, CC6.3, CC6.6, CC6.8 rows
- HIPAA Control Mapping — §164.308(a)(3), (a)(4), §164.312(a)(1), (d) rows
- CMS EDE Appendix A Mapping — §1 (identity proofing), §6 (NPN validation), §7 (session management), §8 (role-based access) rows