OIDC validation at service boundaries + citizen-upload isolation (amends ADR-019)
## Summary ADR-023 mandates OIDC validation at every program service, token exchange for user-context requests, citizen-upload isolation, and service-class credential narrowing. Today every service accepts the same two audiences with any-match semantics and any `service:*` role passes `require_service_caller` anywhere — one abused service credential (above all the portal's, which parses citizen-supplied bytes) authorizes calls across the whole fleet. The 2026-08-10 maintainer ruling on #546 (note 3666918785) activated the full program and resolved the five architect flags: foundations first; the citizen path uses a dedicated narrow IdP service account (ADR-026 opaque sessions — no citizen token exists to exchange; recorded in amending ADR-043 on the first implementing MR); sequential FTI-first rollout; per-request exchanged-token cache; Keycloak-only v1; `X-Canopy-Actor` retained until migration completes; mTLS post-migration stretch. The migration is **receiver-first**: each target service accepts per-target exchanged tokens before its senders switch, observes via the conformance harness + `auth.token_exchange` audit events, then enforces (user-only routes reject service-class bearers). The portal gets per-target narrow credentials, an `azp` allowlist + operation scopes at its 8 targets, and origin-verifiable ownership binding (absorbing #665). Honest scope: five residuals are deferred to real T5 follow-ups (per-service service-class audiences, nested-hop exchange, citizen-content process isolation, revocation-guidance correction, non-Keycloak notes) — they are children here, not footnotes. **Program plan:** [oidc-at-services-and-citizen-upload-isolation.adoc](https://gitlab.com/gadhs/application/eligibility/canopy/-/blob/main/docs/modules/ROOT/pages/plans/oidc-at-services-and-citizen-upload-isolation.adoc) Dependencies are wired as blocks/is_blocked_by links on the children — the DAG, not this list's order, is authoritative for what can start. Sole current root: #1418. ## Children (dependency order) - [x] #1006 security(applications): quarantine applicant uploads behind content scanning (shipped 2026-08-02, ADR-042) - [x] #546 placeholder — decomposed into the children below (2026-08-11) - [x] #1418 docs: fleet authorization-branch inventory manifest (OIDC F1a) (weight: 3) - [x] #1419 security: EffectiveUser resolution + guards + amending ADR-043 (OIDC F1b) (weight: 5) - [x] #1420 security: validated-bearer extension + exact-aud/azp/role policy primitives (OIDC F2) (weight: 3) - [x] #1421 security: TokenExchanger broker + test-lib acquire_exchanged_token (OIDC F3) (weight: 5) - [x] #1422 test: conformance harness skeleton for boundary-auth matrix (OIDC F4) (weight: 3) - [x] #1423 security: exchanger clients + realm wiring + hop-2 devstack proof (OIDC R1) (weight: 5) - [x] #1424 security: auth.token_exchange audit sink + broker emit (OIDC A1) (weight: 3) - [x] #1425 security(tanf): adopt user-context auth on worker routes (OIDC S-tanf) (weight: 3) - [x] #1426 security(medicaid): adopt user-context auth on worker routes (OIDC S-medicaid) (weight: 2) - [x] #1427 security(security): adopt user-context auth on worker routes (OIDC S-security) (weight: 3) - [x] #1428 security(persons): adopt user-context auth on worker routes (OIDC S-persons) (weight: 3) - [x] #1429 security(applications): adopt user-context auth on worker routes (OIDC S-applications) (weight: 3) - [x] #1430 security(eligibility): adopt user-context auth on worker routes (OIDC S-eligibility) (weight: 3) - [x] #1431 security(snap): adopt user-context auth on worker routes (OIDC S-snap) (weight: 2) - [x] #1432 security(caps): adopt user-context auth on worker routes (OIDC S-caps) (weight: 2) - [x] #1433 security(wic): adopt user-context auth on worker routes (OIDC S-wic) (weight: 2) - [x] #1434 security(verification): adopt user-context auth on worker routes (OIDC S-verification) (weight: 2) - [x] #1435 security(enrollment): adopt user-context auth on worker routes (OIDC S-enrollment) (weight: 2) - [x] #1436 security(renewals): adopt user-context auth on worker routes (OIDC S-renewals) (weight: 2) - [x] #1437 security(notices): adopt user-context auth on worker routes (OIDC S-notices) (weight: 2) - [x] #1438 security(reporting): adopt user-context auth on worker routes (OIDC S-reporting) (weight: 2) - [x] #1439 security(appeals): adopt user-context auth on worker routes (OIDC S-appeals) (weight: 2) - [x] #1440 security(portal): target/scope-aware token sources + self-validation contract (OIDC P1) (weight: 5) - [x] #1441 security(portal): azp allowlist + operation scopes at the 8 targets (OIDC P2) (weight: 5) - [x] #1442 security(portal): origin-verifiable ownership binding (OIDC P3) (weight: 5) - [x] #1443 security: cutover — retire legacy guards + X-Canopy-Actor where migrated (OIDC C1) (weight: 3) - [x] #1444 docs: Keycloak deployment notes consolidation in idp-integration (OIDC N1) (weight: 2) - [x] #1445 test: fleet conformance matrix closure (OIDC S6) (weight: 3) - [x] #1446 chore: epic &52 terminal — status reconciliation + closure (OIDC T1) (weight: 2) - [ ] #1447 feat: per-service audience for service-class tokens (OIDC FU-A) (weight: 5) - [ ] #1448 feat: nested-hop exchange + attribution preservation (OIDC FU-B) (weight: 5) - [ ] #1449 feat: citizen-content process isolation for document parsing (OIDC FU-C) (weight: 8) - [ ] #1450 chore: correct identity-revocation guidance — values, not JTIs (OIDC FU-D) (weight: 2) - [ ] #1451 docs: non-Keycloak IdP deployment notes (OIDC non-KC) (weight: 2)
epic