Appearance
ADR 0003 — Narrow-scoped MongoDB users per Issue #56
Status
Superseded by ADR 0006 on 2026-05-12 (ENG-279). Originally accepted 2026-04-17 — provisioned on staging project askflorence-staging; prod rollout deferred until AWS cutover is verified. The narrow-scoped 10-role model in this ADR proved to be the source of three silent regressions (ENG-271, ENG-272, ENG-279) and was rolled back in favour of the 4-user functional model in ADR 0006. The append-only agent_audit_log property (ADR 0002) is preserved verbatim under ADR 0006 via the new app_audit_writer user + role_audit_writer custom role.
Context
Prior state of the AskFlorence Atlas project had two users:
app-read—readon databaseaskflorence. Fine.app-write—readWriteon databaseaskflorence— entire DB. Used byscripts/db/*.js(ingest),src/app/api/unsubscribe/route.ts, and as a fallback insrc/lib/agent-db.ts.
app-write is the entire attack surface. A compromised route or mis-written script can touch any collection. Issue #56 calls this out as a SOC 2 / HIPAA / CMS EDE Phase 3 finding and as the production exit criterion for the agent platform: app-write must be deleted before any real agent or member data enters the cluster.
The app already has separate read and write code paths (src/lib/db.ts vs src/lib/agent-db.ts), and agent-db.ts:36 already reads MONGODB_URI_SURVEY_WRITE with fallback to MONGODB_WRITE_URI — so the code side is already primed for narrow users. What was missing was the roles and users themselves.
Decision
Provision five custom roles and six users on the staging project (one per purpose, plus a read user):
Roles
| Role | Privileges |
|---|---|
role_writer_survey | FIND / INSERT / UPDATE / REMOVE on agent_survey_responses |
role_writer_plans | FIND / INSERT / UPDATE / REMOVE + CREATE_INDEX / DROP_INDEX / COLL_MOD on plans, zip_county, regions, plan_years, audit_log |
role_writer_agents | FIND / INSERT / UPDATE / REMOVE on agents, agencies, agent_sessions; FIND / INSERT only on agent_audit_log; no access to admins |
role_admin_agents | Everything role_writer_agents has, plus FIND / INSERT / UPDATE / REMOVE on admins. Still append-only on agent_audit_log |
role_audit_reader | FIND on agent_audit_log only |
Users
| User | Role | Env var |
|---|---|---|
app_read_staging | built-in read@askflorence | MONGODB_URI |
app_writer_survey | role_writer_survey@admin | MONGODB_URI_SURVEY_WRITE |
app_writer_plans | role_writer_plans@admin | MONGODB_URI_PLANS_WRITE |
app_writer_agents | role_writer_agents@admin | MONGODB_URI_AGENTS_WRITE |
app_admin_agents | role_admin_agents@admin | MONGODB_URI_AGENTS_ADMIN |
audit_reader | role_audit_reader@admin | MONGODB_URI_AUDIT_READ |
Key design points:
app_writer_agentscannot read theadminscollection — not evenFIND. Promotion of an agent to an admin is anapp_admin_agents-only operation. This blocks the most common horizontal-escalation path.- Both
role_writer_agentsandrole_admin_agentsare append-only onagent_audit_log. See ADR 0002. role_writer_plansincludesplan_yearsandaudit_log(the legacy API-access log) in addition to Issue #56's literal list, becausescripts/db/setup-collections.js:44-109and the ingest scripts actively index and write those. Omitting them would break ingest.- No
app_super_adminuser yet. The/sa-loginpath described inCLAUDE.mdships later when the admin dashboard is actually built. Deferring keeps the staging attack surface smaller.
Staging provisioned 2026-04-17. Prod rollout is a separate later session, sequenced after AWS staging and AWS prod are both verified. See runbook.
Consequences
Positive:
- A compromise of any single app-tier credential is bounded by that credential's collection scope.
app-writecan be retired from prod on schedule without rushing. Its elimination is the Issue #56 exit criterion.- SOC 2 CC6.1 / CC6.3 and HIPAA §164.312(a)(1) / §164.308(a)(4) have concrete, auditable evidence: the Atlas role JSON.
scripts/db/*.jsgets a narrowly-scoped replacement forapp-write.
Accepted costs:
- Six env vars to manage instead of one write URI. Mitigated because they're grouped under a comment block in
.env.staging.localand.env.example. - Every new collection requires a role update. Mitigated by the small expected collection set (the agent platform's full schema is already accounted for here).
- Roles are duplicated across staging and prod projects (see ADR 0001). Mitigated by the runbook.
Alternatives considered
- Status quo (
app-writewith whole-DB readWrite) — rejected; is the problem Issue #56 exists to solve. - Per-service Atlas tenants — overkill at current scale.
- Atlas App Services for row-level access control — doesn't replace role-based scoping; might layer on top later for agent-record-level ACLs.
- Application-layer enforcement only — rejected for the same reasons as ADR 0002: trust boundary belongs at the DB layer.
References
- Issue #56 — MongoDB permissioned users setup
docs/agent-platform/mongodb-permissioning.md— the design spec this ADR implementsdocs/runbooks/atlas-user-provisioning.md— how to reproduce this on the prod project- ADR 0001 — Atlas project isolation
- ADR 0002 — Append-only audit log
- SOC 2 TSC 2017 — CC6.1, CC6.3, CC6.6
- HIPAA 45 CFR §164.312(a)(1), §164.308(a)(4)