- README rewritten: Docker and native quickstarts, try-it flow, gate commands, promote-a-stub steps. - CONTRIBUTING rules 9-13 (pii_read on PII reads, __audit_redact__ for encrypted columns, retention policy per table, models.py registry, vendored-code change discipline); PR checklist updated. - docs/nist-mapping.md: per-control file:line evidence and remaining gap. - NIST_800_53_CONTROL_MAPPING.md v0.3: Phase 0 notes on 28 rows (12 implemented, 12 partial, 4 progress notes on hybrid/tailored rows) plus a status-shift table. - PHASE0_PLAN: 0.9 complete; Definition of Done 12/14 checked with how each was verified. Open: first green Gitea CI run, Docker/Postgres boot. - POA&M and status handoff updated; status TL;DR no longer claims an unverified Docker boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
99a50dc86c
commit
e3c4cb0cdc
7 files changed
+241
-111
No files matched your search
+18
-7
@@ -4,22 +4,32 @@ Hard rules — mirrored from OWC's `CONTRIBUTING.md` because the two systems sha
|
|||||||
|
|
||||||
## Repository conventions
|
## Repository conventions
|
||||||
|
|
||||||
1. **Migrations are immutable once merged.** Add a new Alembic revision, never edit `0001_baseline.py` or its successors. If a schema change requires undoing an older migration, the fix is a new revision, not a rewrite. Every migration is a paper trail an auditor can walk.
|
1. **Migrations are immutable once merged.** Add a new Alembic revision, never edit `0001_baseline.py` or its successors. If a schema change requires undoing an older migration, the fix is a new revision, not a rewrite. Every migration is a paper trail an auditor can walk. Every revision's SHA-256 is pinned in `migrations/IMMUTABLE.sha256` — add a line with each new migration; a test fails if a pinned file changes. Migrations never import app code.
|
||||||
|
|
||||||
2. **Don't add domain logic to `src/ypms/framework/` or `src/ypms/security/`.** Those are reused by every entity; entity-specific code belongs in `src/ypms/<entity>/`. If a framework enhancement is genuinely needed, propose it in its own PR with a written reason.
|
2. **Don't add domain logic to `src/ypms/framework/` or `src/ypms/security/`.** Those are reused by every entity; entity-specific code belongs in `src/ypms/<entity>/`. If a framework enhancement is genuinely needed, propose it in its own PR with a written reason.
|
||||||
|
|
||||||
3. **Every state-mutating route emits an audit row.** Enforced by `CRUDService` and the router factory once the framework lands. Don't write handlers that bypass either.
|
3. **Every state-mutating route emits an audit row.** Enforced by `CRUDService`, the router factory, and the stub router. Don't write handlers that bypass them.
|
||||||
|
|
||||||
4. **501 stubs still fire RBAC + audit.** Deferred actions must route through `require_permission` and emit an audit event. Permission gaps surface at Phase 0 review, not at feature-completion.
|
4. **501 stubs still fire RBAC + audit.** Deferred actions must route through `require_permission` and emit an audit event. Permission gaps surface at Phase 0 review, not at feature-completion.
|
||||||
|
|
||||||
5. **Tests required.** `src/ypms/framework/` and `src/ypms/security/` need ≥ 80% coverage. Everything else: at least one happy-path test.
|
5. **Tests required.** `src/ypms/framework/` and `src/ypms/security/` need ≥ 80% coverage. Everything else: at least one happy-path test.
|
||||||
|
|
||||||
6. **No `--no-verify`, `# noqa`, or `# type: ignore` without an inline comment naming the specific reason.** If you need to silence a check, say why in the code.
|
6. **No `--no-verify`, `# noqa`, `# type: ignore`, `# nosec`, or `gitleaks:allow` without an inline comment naming the specific reason.** Suppressions are per-finding, never blanket. Don't lower a CI gate to get green.
|
||||||
|
|
||||||
7. **Sensitive fields never in logs.** SSN, banking, tokens, passwords, encryption keys — route through `sanitize_for_log` before emitting or don't emit. See `src/ypms/logging.py`.
|
7. **Sensitive fields never in logs.** SSN, banking, tokens, passwords, encryption keys — route through `sanitize_for_log` before emitting or don't emit. See `src/ypms/logging.py`.
|
||||||
|
|
||||||
8. **No commits attributed to a person who didn't write them.** Set `user.name` and `user.email` in `git config` to yourself before committing. Co-author trailers are welcome and encouraged for pairing.
|
8. **No commits attributed to a person who didn't write them.** Set `user.name` and `user.email` in `git config` to yourself before committing. Co-author trailers are welcome and encouraged for pairing.
|
||||||
|
|
||||||
|
9. **Reads of sensitive PII emit `pii_read`.** Any GET, list, or history route returning SSN-bearing, banking, FERPA, CFSA, or immigration data calls `framework.pii_audit` after RBAC passes (NIST AU-14).
|
||||||
|
|
||||||
|
10. **Encrypted columns go through `security.column_crypto` and into `__audit_redact__`.** Otherwise their ciphertext lands in `audit_log` and `version_history`. Sensitive values are write-only on the model and masked in the read schema.
|
||||||
|
|
||||||
|
11. **Every mapped table declares a retention policy.** The app refuses to start otherwise. Don't invent durations — use a `retention_classes.py` class, or an unset one citing the POA&M item that owns the decision.
|
||||||
|
|
||||||
|
12. **New model modules go in `src/ypms/models.py`.** Alembic, the retention job, and the tests all read that one list.
|
||||||
|
|
||||||
|
13. **Changes to vendored OWC code need a written reason in the module docstring** and get added to the "Upstream to OWC" list in the current phase status doc.
|
||||||
|
|
||||||
## Change control
|
## Change control
|
||||||
|
|
||||||
- Every substantive change ties to a POA&M ID, a phase-plan milestone, or an issue. Include the reference in the PR description.
|
- Every substantive change ties to a POA&M ID, a phase-plan milestone, or an issue. Include the reference in the PR description.
|
||||||
@@ -37,9 +47,10 @@ Hard rules — mirrored from OWC's `CONTRIBUTING.md` because the two systems sha
|
|||||||
- [ ] Tests pass locally (`pytest`).
|
- [ ] Tests pass locally (`pytest`).
|
||||||
- [ ] Coverage doesn't drop below 80% on `framework/` or `security/`.
|
- [ ] Coverage doesn't drop below 80% on `framework/` or `security/`.
|
||||||
- [ ] `ruff check`, `mypy` clean.
|
- [ ] `ruff check`, `mypy` clean.
|
||||||
- [ ] SAST (`bandit`) and SCA (`pip-audit`) clean, or documented waivers.
|
- [ ] SAST (`bandit`), SCA (`pip-audit --strict .`), and secret scan (gitleaks) clean, or per-finding waivers with reasons.
|
||||||
- [ ] Migration added (if schema changed); no edits to older migrations.
|
- [ ] Migration added (if schema changed) and pinned in `migrations/IMMUTABLE.sha256`; no edits to older migrations.
|
||||||
- [ ] Any new endpoint has RBAC + audit + a test.
|
- [ ] New tables have a retention policy; new model modules are in `src/ypms/models.py`.
|
||||||
|
- [ ] Any new endpoint has RBAC + audit + a test; PII reads emit `pii_read`.
|
||||||
- [ ] Any new sensitive field routes through the app-layer encryption helpers.
|
- [ ] Any new sensitive field routes through the app-layer encryption helpers.
|
||||||
- [ ] `docs/nist-mapping.md` updated if the change wires a new NIST control.
|
- [ ] `docs/nist-mapping.md` updated if the change wires a new NIST control.
|
||||||
- [ ] POA&M item updated if the change closes or advances one.
|
- [ ] POA&M item updated if the change closes or advances one.
|
||||||
@@ -49,4 +60,4 @@ Hard rules — mirrored from OWC's `CONTRIBUTING.md` because the two systems sha
|
|||||||
|
|
||||||
- Squash-merge is the default. Merge commit messages should read like release notes for that PR.
|
- Squash-merge is the default. Merge commit messages should read like release notes for that PR.
|
||||||
- Force-pushes to `main` are not allowed. Force-pushes to feature branches during review are fine.
|
- Force-pushes to `main` are not allowed. Force-pushes to feature branches during review are fine.
|
||||||
- CI must be green.
|
- CI (Gitea Actions, `.gitea/workflows/ci.yml`) must be green.
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
**System:** Youth Program Management System (YPMS)
|
**System:** Youth Program Management System (YPMS)
|
||||||
**System Owner:** District of Columbia Department of Employment Services (DOES)
|
**System Owner:** District of Columbia Department of Employment Services (DOES)
|
||||||
**Document version:** 0.2 (draft — in-house build reframe)
|
**Document version:** 0.3 (draft — Phase 0 evidence populated)
|
||||||
**Date:** 2026-09-30
|
**Date:** 2026-10-01
|
||||||
**Author:** DOES CIO
|
**Author:** DOES CIO
|
||||||
**Companion to:** [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md)
|
**Companion to:** [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md)
|
||||||
|
|
||||||
@@ -85,6 +85,22 @@ The Moderate baseline contains ~261 controls including enhancements; the ~80 not
|
|||||||
|
|
||||||
Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materializes; refresh at every phase gate.
|
Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materializes; refresh at every phase gate.
|
||||||
|
|
||||||
|
### Phase 0 status shift (2026-10-01)
|
||||||
|
|
||||||
|
Rows touched by Phase 0 code. Each carries a dated **Phase 0** note with file:line evidence and the remaining gap; the same list with links is in [`docs/nist-mapping.md`](docs/nist-mapping.md). Rows whose status is 🤝 or ⚠️ keep it and gain a progress note.
|
||||||
|
|
||||||
|
| Family | ✅ Implemented | 🟡 Partial | Note only (🤝 / ⚠️ kept) |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| AC | 5 | 2 | — |
|
||||||
|
| AU | 4 | 1 | 1 |
|
||||||
|
| CM | — | 1 | 1 |
|
||||||
|
| IA | 1 | 3 | — |
|
||||||
|
| RA | — | 1 | — |
|
||||||
|
| SA | — | 1 | — |
|
||||||
|
| SC | — | 3 | 1 |
|
||||||
|
| SI | 2 | — | 1 |
|
||||||
|
| **Totals** | **12** | **12** | **4** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Detailed Control Mapping
|
## 4. Detailed Control Mapping
|
||||||
@@ -94,23 +110,23 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| Control | Status | Owner | RFP Ref | Implementation & Evidence |
|
| Control | Status | Owner | RFP Ref | Implementation & Evidence |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **AC-1 Policy and Procedures** | 📋 | D | — | DOES OCTO IT Security Policy applies (`C.5.7.53`). DOES ISSO owns YPMS access-control procedure document. **Evidence:** signed DOES Access Control Policy, RoB (`ypms/RULES_OF_BEHAVIOR.md`). |
|
| **AC-1 Policy and Procedures** | 📋 | D | — | DOES OCTO IT Security Policy applies (`C.5.7.53`). DOES ISSO owns YPMS access-control procedure document. **Evidence:** signed DOES Access Control Policy, RoB (`ypms/RULES_OF_BEHAVIOR.md`). |
|
||||||
| **AC-2 Account Management** | 📝 | O | `C.5.7.4`, `C.5.7.5`, `C.5.7.7`, `C.5.7.29`, `C.5.7.47` | Admin enable/disable; auto-disable on inactivity; 12–64 char alphanumeric IDs; group + individual assignment via Entra ID; no re-issue of deleted or previously-assigned IDs. **Evidence:** account-lifecycle event samples from Sentinel; documented account-provisioning workflow in `ypms/RULES_OF_BEHAVIOR.md`; quarterly access-recert records; DOES OIT personnel-access roster per source RFP `C.5.8.16`, `C.5.8.17` adopted as internal policy. |
|
| **AC-2 Account Management** | 🟡 | O | `C.5.7.4`, `C.5.7.5`, `C.5.7.7`, `C.5.7.29`, `C.5.7.47` | Admin enable/disable; auto-disable on inactivity; 12–64 char alphanumeric IDs; group + individual assignment via Entra ID; no re-issue of deleted or previously-assigned IDs. **Evidence:** account-lifecycle event samples from Sentinel; documented account-provisioning workflow in `ypms/RULES_OF_BEHAVIOR.md`; quarterly access-recert records; DOES OIT personnel-access roster per source RFP `C.5.8.16`, `C.5.8.17` adopted as internal policy. **Phase 0 (2026-10-01):** User model with active flag; inactive accounts rejected at authentication. Evidence: `src/ypms/users/models.py:48`, `src/ypms/auth/deps.py:43`. Gap: No admin enable/disable endpoint (admin module, Phase 8); inactivity auto-disable is YPMS-POAM-071. |
|
||||||
| **AC-2(1) Automated Account Management** | 📝 | O | `C.5.7.4`, `C.5.7.31` | Automated inactivity-disable; real-time and scheduled access-right changes. **Evidence:** configuration screenshot of inactivity threshold; audit log showing an auto-disable event. |
|
| **AC-2(1) Automated Account Management** | 📝 | O | `C.5.7.4`, `C.5.7.31` | Automated inactivity-disable; real-time and scheduled access-right changes. **Evidence:** configuration screenshot of inactivity threshold; audit log showing an auto-disable event. |
|
||||||
| **AC-2(3) Disable Inactive Accounts** | 📝 | O | `C.5.7.4` | Organization-defined inactivity threshold (`[TBD]` — recommend 90 days for staff, 180 days for youth accounts). **Evidence:** configured threshold value + audit sample. |
|
| **AC-2(3) Disable Inactive Accounts** | 📝 | O | `C.5.7.4` | Organization-defined inactivity threshold (`[TBD]` — recommend 90 days for staff, 180 days for youth accounts). **Evidence:** configured threshold value + audit sample. |
|
||||||
| **AC-2(4) Automated Audit Actions** | 📝 | O | `C.5.7.36`, `C.5.7.39` | All account create/modify/enable/disable/remove actions auto-logged. **Evidence:** log samples. |
|
| **AC-2(4) Automated Audit Actions** | 📝 | O | `C.5.7.36`, `C.5.7.39` | All account create/modify/enable/disable/remove actions auto-logged. **Evidence:** log samples. |
|
||||||
| **AC-3 Access Enforcement** | 📝 | O | `C.5.7.21`, `C.5.7.27`, `C.5.7.28`, `C.5.7.31`, `C.5.7.48` | Server-side authorization on every action; admin-only view/change of access rights; enforce authorizations per access-control policies. **Evidence:** access-control matrix showing every role × object; penetration-test evidence that unauthenticated and lower-role users cannot reach protected functions. |
|
| **AC-3 Access Enforcement** | ✅ | O | `C.5.7.21`, `C.5.7.27`, `C.5.7.28`, `C.5.7.31`, `C.5.7.48` | Server-side authorization on every action; admin-only view/change of access rights; enforce authorizations per access-control policies. **Evidence:** access-control matrix showing every role × object; penetration-test evidence that unauthenticated and lower-role users cannot reach protected functions. **Phase 0 (2026-10-01):** `require_permission` runs on every protected route, including all 208 stub routes; portal-only callers restricted to their own Registrant row. Evidence: `src/ypms/security/rbac.py:46`, `src/ypms/youth/router.py:67`, `tests/test_stubs.py`. Gap: Row-level scoping beyond Registrant is Phase 2+. |
|
||||||
| **AC-4 Information Flow Enforcement** | 📝 | O | `C.5.7.23` | Trusted / transition / untrusted zone segmentation; untrusted systems cannot reach trusted-zone systems directly. **Evidence:** network architecture diagram (from the DOES OIT architecture document — `YPMS-POAM-040`); Azure NSG / firewall rule export. |
|
| **AC-4 Information Flow Enforcement** | 📝 | O | `C.5.7.23` | Trusted / transition / untrusted zone segmentation; untrusted systems cannot reach trusted-zone systems directly. **Evidence:** network architecture diagram (from the DOES OIT architecture document — `YPMS-POAM-040`); Azure NSG / firewall rule export. |
|
||||||
| **AC-6 Least Privilege** | 📝 | O | `C.5.7.32`, `C.5.7.33`, `C.5.7.34` | RBAC over defined objects; one login per staff, role-mediated access; admin maintains role-based security. CFSA foster/ward data and OSSE FERPA records reachable only by role-flagged users. **Evidence:** role catalogue with data-class access mapping. |
|
| **AC-6 Least Privilege** | ✅ | O | `C.5.7.32`, `C.5.7.33`, `C.5.7.34` | RBAC over defined objects; one login per staff, role-mediated access; admin maintains role-based security. CFSA foster/ward data and OSSE FERPA records reachable only by role-flagged users. **Evidence:** role catalogue with data-class access mapping. **Phase 0 (2026-10-01):** Data-driven RBAC (8 roles × 30 entities × 8 actions); FERPA/CFSA entities limited to ELIGIBILITY_EXAMINER; super-role explicit. Evidence: `src/ypms/seed/permissions_matrix.json`, `src/ypms/security/rbac.py:22`, `tests/test_stubs.py`. Gap: Four matrix review items open (PHASE0_STATUS.md). |
|
||||||
| **AC-6(1) Authorize Access to Security Functions** | 📝 | O | `C.5.7.28` | Only administrators can view/change access rights. **Evidence:** role catalogue. |
|
| **AC-6(1) Authorize Access to Security Functions** | ✅ | O | `C.5.7.28` | Only administrators can view/change access rights. **Evidence:** role catalogue. **Phase 0 (2026-10-01):** Only DOES_SYSTEM_ADMIN holds RBAC-table rights (User/Role not granted to any other role). Evidence: `src/ypms/seed/permissions_matrix.json`. |
|
||||||
| **AC-6(2) Non-Privileged Access for Non-Security Functions** | 📝 | O | `C.5.7.33` | Staff use non-privileged accounts by default for routine work. **Evidence:** account-inventory review. |
|
| **AC-6(2) Non-Privileged Access for Non-Security Functions** | 📝 | O | `C.5.7.33` | Staff use non-privileged accounts by default for routine work. **Evidence:** account-inventory review. |
|
||||||
| **AC-6(5) Privileged Accounts** | 📝 | O | | Named, approved list of privileged users. **Evidence:** privileged-user roster + quarterly recert. |
|
| **AC-6(5) Privileged Accounts** | 📝 | O | | Named, approved list of privileged users. **Evidence:** privileged-user roster + quarterly recert. |
|
||||||
| **AC-6(9) Log Use of Privileged Functions** | 📝 | O | `C.5.7.36` | Auto-log all admin actions including account create/modify/enable/disable/remove. **Evidence:** log samples. |
|
| **AC-6(9) Log Use of Privileged Functions** | 📝 | O | `C.5.7.36` | Auto-log all admin actions including account create/modify/enable/disable/remove. **Evidence:** log samples. |
|
||||||
| **AC-6(10) Prohibit Non-Privileged Users from Executing Privileged Functions** | 📝 | O | `C.5.7.27`, `C.5.7.32` | Enforced server-side. **Evidence:** pentest evidence. |
|
| **AC-6(10) Prohibit Non-Privileged Users from Executing Privileged Functions** | ✅ | O | `C.5.7.27`, `C.5.7.32` | Enforced server-side. **Evidence:** pentest evidence. **Phase 0 (2026-10-01):** Delete and Restore (super-role only today) and Registrant VersionHistory (OYP_STAFF and ELIGIBILITY_EXAMINER only) enforced server-side; 403 asserted for every other role. Evidence: `src/ypms/security/rbac.py:29`, `tests/test_stubs.py`, `tests/youth/test_registrant_api.py`. |
|
||||||
| **AC-7 Unsuccessful Logon Attempts** | 📝 | O | `C.5.7.10`, `C.5.7.11` | Lockout after 5 attempts; user notified on revocation. **Evidence:** configured threshold; audit sample of a lockout event and notification. |
|
| **AC-7 Unsuccessful Logon Attempts** | 🟡 | O | `C.5.7.10`, `C.5.7.11` | Lockout after 5 attempts; user notified on revocation. **Evidence:** configured threshold; audit sample of a lockout event and notification. **Phase 0 (2026-10-01):** 5 failed logins per source IP per 15 minutes → 429; every failure (bad credentials, unknown user, inactive, throttled) audited as `login_failed`. Evidence: `src/ypms/auth/rate_limit.py:47`, `src/ypms/auth/router.py:86`, `src/ypms/auth/router.py:65`. Gap: Per-account lockout (`locked_until`) and user notification not built; in-memory limiter is per-process. |
|
||||||
| **AC-8 System Use Notification** | 📝 | O | `C.5.7.37` | Notification banner before granting access. **Evidence:** screenshot of banner; DOES-approved banner text on file. |
|
| **AC-8 System Use Notification** | 📝 | O | `C.5.7.37` | Notification banner before granting access. **Evidence:** screenshot of banner; DOES-approved banner text on file. |
|
||||||
| **AC-11 Session Lock** | 📝 | O | `C.5.7.3` | Idle-timeout session lock with re-authentication. **Evidence:** configured timeout value. |
|
| **AC-11 Session Lock** | 📝 | O | `C.5.7.3` | Idle-timeout session lock with re-authentication. **Evidence:** configured timeout value. |
|
||||||
| **AC-11(1) Pattern-Hiding Displays** | 📝 | O | | Screen obscured on lock. **Evidence:** UX walkthrough. |
|
| **AC-11(1) Pattern-Hiding Displays** | 📝 | O | | Screen obscured on lock. **Evidence:** UX walkthrough. |
|
||||||
| **AC-12 Session Termination** | 📝 | O | `C.5.7.3` | Idle session termination with re-authentication. **Evidence:** configured timeout value; log sample of a session-terminated event. |
|
| **AC-12 Session Termination** | ✅ | O | `C.5.7.3` | Idle session termination with re-authentication. **Evidence:** configured timeout value; log sample of a session-terminated event. **Phase 0 (2026-10-01):** Access tokens expire (default 60 min, `exp` claim verified); refresh tokens cannot access resources. Evidence: `src/ypms/auth/security.py:59`, `src/ypms/auth/deps.py:35`. Gap: Idle-timeout UI behaviour lands with the portal (Phase 3). |
|
||||||
| **AC-14 Permitted Actions Without Identification or Authentication** | 📝 | O | | Limited to public content (program info, job postings, application intake as unauthenticated entry). **Evidence:** enumeration of unauthenticated endpoints. |
|
| **AC-14 Permitted Actions Without Identification or Authentication** | 📝 | O | | Limited to public content (program info, job postings, application intake as unauthenticated entry). **Evidence:** enumeration of unauthenticated endpoints. |
|
||||||
| **AC-17 Remote Access** | 🤝 | O, D | | All access is remote by architecture. MFA required for staff and admin via Entra ID conditional access (see IA-2). **Evidence:** Entra CA policy export; endpoint-compliance policy (source RFP `C.5.6.30`). |
|
| **AC-17 Remote Access** | 🤝 | O, D | | All access is remote by architecture. MFA required for staff and admin via Entra ID conditional access (see IA-2). **Evidence:** Entra CA policy export; endpoint-compliance policy (source RFP `C.5.6.30`). |
|
||||||
| **AC-17(1) Monitoring and Control** | 📝 | O | `C.5.6.9` #19 | SIEM monitoring of remote access. **Evidence:** SIEM feed. |
|
| **AC-17(1) Monitoring and Control** | 📝 | O | `C.5.6.9` #19 | SIEM monitoring of remote access. **Evidence:** SIEM feed. |
|
||||||
@@ -135,18 +151,18 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| Control | Status | Owner | RFP Ref | Implementation & Evidence |
|
| Control | Status | Owner | RFP Ref | Implementation & Evidence |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **AU-1 Policy and Procedures** | 📋 | D | `C.5.7.53` | DOES OCTO logging policy applies. **Evidence:** policy. |
|
| **AU-1 Policy and Procedures** | 📋 | D | `C.5.7.53` | DOES OCTO logging policy applies. **Evidence:** policy. |
|
||||||
| **AU-2 Event Logging** | 📝 | O | `C.5.7.39`, `C.5.6.9` #4 | Comprehensive event set: root/admin actions, audit-log access/modification, successful and unsuccessful logon attempts, password changes, system-level object create/delete, all PII/sensitive-data interactions, role changes, shutdown/restart, file access authorized and unauthorized, user logon events, plus DOES-defined additions. **Evidence:** event-catalog document; SIEM sample stream. |
|
| **AU-2 Event Logging** | 🟡 | O | `C.5.7.39`, `C.5.6.9` #4 | Comprehensive event set: root/admin actions, audit-log access/modification, successful and unsuccessful logon attempts, password changes, system-level object create/delete, all PII/sensitive-data interactions, role changes, shutdown/restart, file access authorized and unauthorized, user logon events, plus DOES-defined additions. **Evidence:** event-catalog document; SIEM sample stream. **Phase 0 (2026-10-01):** Audited today: login success/failure, password change, every create/update/delete/restore, every PII read, every stub invocation, each retention run. Evidence: `src/ypms/audit/service.py:17`, `src/ypms/framework/crud.py:111`, `src/ypms/auth/router.py:65`, `src/ypms/auth/router.py:104`. Gap: Event catalogue items not yet applicable (role changes, shutdown, file access) arrive with their features. |
|
||||||
| **AU-3 Content of Audit Records** | 📝 | O | `C.5.7.15`, `C.5.7.48`, `C.5.7.49`, `C.5.6.9` #8, #11, #12, #13 | What/when/where/source/outcome/identity; sufficient to reconstruct events; unique transaction ID per transaction; before-and-after transaction history; communication send/access logging. **Evidence:** log-record specification; log sample. |
|
| **AU-3 Content of Audit Records** | ✅ | O | `C.5.7.15`, `C.5.7.48`, `C.5.7.49`, `C.5.6.9` #8, #11, #12, #13 | What/when/where/source/outcome/identity; sufficient to reconstruct events; unique transaction ID per transaction; before-and-after transaction history; communication send/access logging. **Evidence:** log-record specification; log sample. **Phase 0 (2026-10-01):** Each row: actor id + username, action, entity, entity id, before/after, timestamp, IP; app-layer ciphertext redacted from before/after. Evidence: `src/ypms/audit/models.py:18`, `src/ypms/framework/crud.py:46`. Gap: Request id is in logs, not yet on the audit row. |
|
||||||
| **AU-3(1) Additional Audit Information** | 📝 | O | | Extended fields for financial-adjustment events (payroll approval, banking change, eligibility change). **Evidence:** log sample. |
|
| **AU-3(1) Additional Audit Information** | 📝 | O | | Extended fields for financial-adjustment events (payroll approval, banking change, eligibility change). **Evidence:** log sample. |
|
||||||
| **AU-4 Audit Log Storage Capacity** | 📝 | O | | Sized for `[TBD]` retention (see AU-11). **Evidence:** capacity plan. |
|
| **AU-4 Audit Log Storage Capacity** | 📝 | O | | Sized for `[TBD]` retention (see AU-11). **Evidence:** capacity plan. |
|
||||||
| **AU-5 Response to Audit Logging Process Failures** | 📝 | O | `C.5.7.26`, `C.5.6.9` #23 | Alerts to admins and additional roles on security compromise; user-account lockout alerts; logging-pipeline health monitoring. **Evidence:** runbook + alert-configuration screenshot. |
|
| **AU-5 Response to Audit Logging Process Failures** | 📝 | O | `C.5.7.26`, `C.5.6.9` #23 | Alerts to admins and additional roles on security compromise; user-account lockout alerts; logging-pipeline health monitoring. **Evidence:** runbook + alert-configuration screenshot. |
|
||||||
| **AU-6 Audit Record Review, Analysis, and Reporting** | 🤝 | O, D | `C.5.6.9` #19–22 | Sentinel aggregation into meaningful security intelligence; alerts on business-rule/workflow thresholds. DC OCTO SOC consumes the feed. **Evidence:** Sentinel correlation-rule inventory; SOC alert-triage runbook. |
|
| **AU-6 Audit Record Review, Analysis, and Reporting** | 🤝 | O, D | `C.5.6.9` #19–22 | Sentinel aggregation into meaningful security intelligence; alerts on business-rule/workflow thresholds. DC OCTO SOC consumes the feed. **Evidence:** Sentinel correlation-rule inventory; SOC alert-triage runbook. |
|
||||||
| **AU-7 Audit Record Reduction and Report Generation** | 📝 | O | `C.5.6.9` #20 | Configurable aggregation and reporting. **Evidence:** report templates. |
|
| **AU-7 Audit Record Reduction and Report Generation** | 📝 | O | `C.5.6.9` #20 | Configurable aggregation and reporting. **Evidence:** report templates. |
|
||||||
| **AU-8 Time Stamps** | 📝 | O | `C.5.7.40` | All critical system times synchronized. **Evidence:** NTP configuration; sample logs showing consistent UTC/ISO-8601 timestamps. |
|
| **AU-8 Time Stamps** | ✅ | O | `C.5.7.40` | All critical system times synchronized. **Evidence:** NTP configuration; sample logs showing consistent UTC/ISO-8601 timestamps. **Phase 0 (2026-10-01):** All timestamps timezone-aware UTC; Postgres container pinned to UTC. Evidence: `src/ypms/audit/models.py:24`, `docker-compose.yml:8`. Gap: NTP is inherited from Azure at deployment. |
|
||||||
| **AU-9 Protection of Audit Information** | 📝 | O | `C.5.7.41`, `C.5.6.9` #6 | Audit information and audit tools protected from unauthorized access, modification, and deletion; role-based access to audit logs. Sentinel ingestion moves logs to a separate trust boundary. **Evidence:** ACL export on audit store; Sentinel workspace RBAC. |
|
| **AU-9 Protection of Audit Information** | 📝 | O | `C.5.7.41`, `C.5.6.9` #6 | Audit information and audit tools protected from unauthorized access, modification, and deletion; role-based access to audit logs. Sentinel ingestion moves logs to a separate trust boundary. **Evidence:** ACL export on audit store; Sentinel workspace RBAC. |
|
||||||
| **AU-11 Audit Record Retention** | 🤝 | O, D | `C.5.7.42` | Organization-defined; **DOES recommendation: 1 year online, 3 years archived** (align to DOES records schedule — `[TBD]`). **Evidence:** written retention policy; archive location and format. |
|
| **AU-11 Audit Record Retention** | 🤝 | O, D | `C.5.7.42` | Organization-defined; **DOES recommendation: 1 year online, 3 years archived** (align to DOES records schedule — `[TBD]`). **Evidence:** written retention policy; archive location and format. **Phase 0 (2026-10-01):** Retention scaffold: audit log carries the PIA §9 interim policy (archive at 1 year); report-only job. Evidence: `src/ypms/retention_classes.py`, `src/ypms/framework/retention.py:150`. Gap: Period unapproved until YPMS-POAM-054; archive tier is deployment work. |
|
||||||
| **AU-12 Audit Record Generation** | 📝 | O | `C.5.7.39`, `C.5.6.9` #17, #18 | Automated audit trails for all system components; system-generated security audit trail; SANS Information Logging Standard compliance. **Evidence:** component inventory × logging coverage table. |
|
| **AU-12 Audit Record Generation** | ✅ | O | `C.5.7.39`, `C.5.6.9` #17, #18 | Automated audit trails for all system components; system-generated security audit trail; SANS Information Logging Standard compliance. **Evidence:** component inventory × logging coverage table. **Phase 0 (2026-10-01):** Audit generation is in the framework (CRUDService, stub router, auth), not per-handler. Evidence: `src/ypms/framework/crud.py:111`, `src/ypms/framework/stub_router.py:66`. |
|
||||||
| **AU-14 Session Audit** | 📝 | O | `C.5.6.9` #5, #7 | Searchable log of all screens accessed by user; sensitive-data screen access monitored and recorded. **Evidence:** search-tool demo; log sample. |
|
| **AU-14 Session Audit** | ✅ | O | `C.5.6.9` #5, #7 | Searchable log of all screens accessed by user; sensitive-data screen access monitored and recorded. **Evidence:** search-tool demo; log sample. **Phase 0 (2026-10-01):** `pii_read` audit on Registrant GET, list (ids recorded), and history. Evidence: `src/ypms/framework/pii_audit.py:36`, `src/ypms/youth/router.py:99`, `src/ypms/youth/router.py:115`. Gap: Extends to Guardian/TaxDocument/Attachment as they are built. |
|
||||||
|
|
||||||
### CA — Assessment, Authorization, and Monitoring
|
### CA — Assessment, Authorization, and Monitoring
|
||||||
|
|
||||||
@@ -170,13 +186,13 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| **CM-2 Baseline Configuration** | 📝 | O | `C.5.6.13`, IEEE 828-2012 | Application baseline in the repo (Dockerfile / compose / IaC); DOES-approved tool/software list. **Evidence:** baseline files in `ypms/`; CM plan (see CM-9). |
|
| **CM-2 Baseline Configuration** | 📝 | O | `C.5.6.13`, IEEE 828-2012 | Application baseline in the repo (Dockerfile / compose / IaC); DOES-approved tool/software list. **Evidence:** baseline files in `ypms/`; CM plan (see CM-9). |
|
||||||
| **CM-2(2) Automation Support for Accuracy and Currency** | 📝 | O | | IaC (Bicep or Terraform) maintains Azure baseline. **Evidence:** tool inventory; IaC repo path. |
|
| **CM-2(2) Automation Support for Accuracy and Currency** | 📝 | O | | IaC (Bicep or Terraform) maintains Azure baseline. **Evidence:** tool inventory; IaC repo path. |
|
||||||
| **CM-2(3) Retention of Previous Configurations** | 📝 | O | | Rollback-capable baseline history via git. **Evidence:** git log. |
|
| **CM-2(3) Retention of Previous Configurations** | 📝 | O | | Rollback-capable baseline history via git. **Evidence:** git log. |
|
||||||
| **CM-3 Configuration Change Control** | 🤝 | O, D | `C.5.6.13`, `C.5.6.17` | DOES CIO approves changes; 30-day advance notice for maintenance windows; NOC coordination; emergency-change track. **Evidence:** change-record register; monthly change-review minutes. |
|
| **CM-3 Configuration Change Control** | 🤝 | O, D | `C.5.6.13`, `C.5.6.17` | DOES CIO approves changes; 30-day advance notice for maintenance windows; NOC coordination; emergency-change track. **Evidence:** change-record register; monthly change-review minutes. **Phase 0 (2026-10-01):** Merged migrations pinned by SHA-256; CI fails on any edit. Every change ships through a reviewed commit. Evidence: `migrations/IMMUTABLE.sha256`, `tests/test_migration_immutability.py:31`. Gap: Maintenance-window process is organizational. |
|
||||||
| **CM-4 Impact Analysis** | 📝 | O | `C.5.8.12` | Required before database migrations affecting programs already in production. **Evidence:** impact-analysis template + sample; migration review discipline. |
|
| **CM-4 Impact Analysis** | 📝 | O | `C.5.8.12` | Required before database migrations affecting programs already in production. **Evidence:** impact-analysis template + sample; migration review discipline. |
|
||||||
| **CM-5 Access Restrictions for Change** | 📝 | O | | Only DOES OIT delivery leads have merge rights on `main`; branch protection + code review required. **Evidence:** git branch-protection settings; Entra group ACL. |
|
| **CM-5 Access Restrictions for Change** | 📝 | O | | Only DOES OIT delivery leads have merge rights on `main`; branch protection + code review required. **Evidence:** git branch-protection settings; Entra group ACL. |
|
||||||
| **CM-6 Configuration Settings** | 📝 | O | `C.5.6.5`, `C.5.6.6`, `C.5.6.7` | Admin-configurable security controls; CA configuration; DB/space/performance configuration. DOES OIT establishes secure baseline (CIS Azure Foundations benchmark + DOES overlays). **Evidence:** baseline settings export; Azure Policy assignment. |
|
| **CM-6 Configuration Settings** | 📝 | O | `C.5.6.5`, `C.5.6.6`, `C.5.6.7` | Admin-configurable security controls; CA configuration; DB/space/performance configuration. DOES OIT establishes secure baseline (CIS Azure Foundations benchmark + DOES overlays). **Evidence:** baseline settings export; Azure Policy assignment. |
|
||||||
| **CM-7 Least Functionality** | 📝 | O | | Unused features disabled. **Evidence:** enabled-feature inventory. |
|
| **CM-7 Least Functionality** | 📝 | O | | Unused features disabled. **Evidence:** enabled-feature inventory. |
|
||||||
| **CM-7(1) Periodic Review** | 📝 | O | | Quarterly review of enabled features. **Evidence:** review record. |
|
| **CM-7(1) Periodic Review** | 📝 | O | | Quarterly review of enabled features. **Evidence:** review record. |
|
||||||
| **CM-8 System Component Inventory** | 📝 | O | `C.5.6.13` | Comprehensive tool/software list with licensing and upgrade path. **Evidence:** SBOM (CycloneDX, per release, from CI) + tool inventory. |
|
| **CM-8 System Component Inventory** | 🟡 | O | `C.5.6.13` | Comprehensive tool/software list with licensing and upgrade path. **Evidence:** SBOM (CycloneDX, per release, from CI) + tool inventory. **Phase 0 (2026-10-01):** CycloneDX 1.6 SBOM of the runtime dependency set generated in CI. Evidence: `.gitea/workflows/ci.yml:70`. Gap: Pending first green CI run (Gitea runner); per-release retention of SBOMs not yet defined. |
|
||||||
| **CM-8(1) Updates During Installation and Removal** | 📝 | O | | Inventory auto-updated via CI pipeline. **Evidence:** tooling. |
|
| **CM-8(1) Updates During Installation and Removal** | 📝 | O | | Inventory auto-updated via CI pipeline. **Evidence:** tooling. |
|
||||||
| **CM-9 Configuration Management Plan** | 📝 | O | | `ypms/CONFIGURATION_MANAGEMENT_PLAN.md` (to be authored; also serves source RFP §C.5.12 Change Management Plan). **Evidence:** signed plan. |
|
| **CM-9 Configuration Management Plan** | 📝 | O | | `ypms/CONFIGURATION_MANAGEMENT_PLAN.md` (to be authored; also serves source RFP §C.5.12 Change Management Plan). **Evidence:** signed plan. |
|
||||||
| **CM-10 Software Usage Restrictions** | 📋 | D | `C.5.6.13` | DOES-approved software list; license terms respected. **Evidence:** approved-software list. |
|
| **CM-10 Software Usage Restrictions** | 📋 | D | `C.5.6.13` | DOES-approved software list; license terms respected. **Evidence:** approved-software list. |
|
||||||
@@ -205,15 +221,15 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| Control | Status | Owner | RFP Ref | Implementation & Evidence |
|
| Control | Status | Owner | RFP Ref | Implementation & Evidence |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **IA-1 Policy and Procedures** | 📋 | D | | DOES identity policy. **Evidence:** policy. |
|
| **IA-1 Policy and Procedures** | 📋 | D | | DOES identity policy. **Evidence:** policy. |
|
||||||
| **IA-2 Identification and Authentication (Organizational Users)** | 📝 | O | `C.5.7.38`, `C.5.7.36` | Unique user ID per user; admin actions tied to individual identity. **Evidence:** IdP integration record. |
|
| **IA-2 Identification and Authentication (Organizational Users)** | ✅ | O | `C.5.7.38`, `C.5.7.36` | Unique user ID per user; admin actions tied to individual identity. **Evidence:** IdP integration record. **Phase 0 (2026-10-01):** Every non-public route requires a valid bearer token bound to a unique, active user. Evidence: `src/ypms/auth/deps.py:25`. Gap: MFA enhancements (IA-2(1)/(2)) arrive with Entra ID in Phase 1. |
|
||||||
| **IA-2(1) MFA to Privileged Accounts** | ⚠️ | O | `C.5.7.6` | Source RFP `C.5.7.6` permits "unique identifier + shared secret answer" — **insufficient by NIST 800-63B AAL2**. **SSP tailors above the source RFP: MFA required for all privileged and staff accounts** via Entra ID conditional access. **Evidence:** Entra CA policy export; sample log showing MFA challenge (`YPMS-POAM-030`). |
|
| **IA-2(1) MFA to Privileged Accounts** | ⚠️ | O | `C.5.7.6` | Source RFP `C.5.7.6` permits "unique identifier + shared secret answer" — **insufficient by NIST 800-63B AAL2**. **SSP tailors above the source RFP: MFA required for all privileged and staff accounts** via Entra ID conditional access. **Evidence:** Entra CA policy export; sample log showing MFA challenge (`YPMS-POAM-030`). |
|
||||||
| **IA-2(2) MFA to Non-Privileged Accounts** | ⚠️ | O | | Same tailoring: MFA for all staff-role accounts. **Evidence:** as IA-2(1). |
|
| **IA-2(2) MFA to Non-Privileged Accounts** | ⚠️ | O | | Same tailoring: MFA for all staff-role accounts. **Evidence:** as IA-2(1). |
|
||||||
| **IA-2(8) Access to Accounts — Replay Resistant** | 📝 | O | | Modern MFA protocols. **Evidence:** protocol inventory. |
|
| **IA-2(8) Access to Accounts — Replay Resistant** | 📝 | O | | Modern MFA protocols. **Evidence:** protocol inventory. |
|
||||||
| **IA-2(12) Acceptance of PIV Credentials** | 📋 | D | | DC OCTO enterprise: no PIV in scope; DOES employee credentials via DOES SSO. **Evidence:** IdP config. |
|
| **IA-2(12) Acceptance of PIV Credentials** | 📋 | D | | DC OCTO enterprise: no PIV in scope; DOES employee credentials via DOES SSO. **Evidence:** IdP config. |
|
||||||
| **IA-3 Device Identification and Authentication** | 📝 | O | | Device certificates for managed endpoints. **Evidence:** MDM cert deployment. |
|
| **IA-3 Device Identification and Authentication** | 📝 | O | | Device certificates for managed endpoints. **Evidence:** MDM cert deployment. |
|
||||||
| **IA-4 Identifier Management** | 📝 | O | `C.5.7.7`, `C.5.7.38`, `C.5.7.47` | 12-alphanumeric minimum; unique per user; no re-issue of deleted IDs. **Evidence:** IdP configuration; audit sample. |
|
| **IA-4 Identifier Management** | 🟡 | O | `C.5.7.7`, `C.5.7.38`, `C.5.7.47` | 12-alphanumeric minimum; unique per user; no re-issue of deleted IDs. **Evidence:** IdP configuration; audit sample. **Phase 0 (2026-10-01):** Usernames unique (DB constraint); no user-delete path exists, so identifiers are never re-issued. Evidence: `migrations/versions/0002_framework_tables.py:54`. Gap: 12-character minimum identifier and explicit tombstoning arrive with registration (Phase 1). |
|
||||||
| **IA-5 Authenticator Management** | 📝 | O | `C.5.7.8`, `C.5.7.12`, `C.5.7.13`, `C.5.7.14`, `C.5.7.45`, `C.5.7.46` | User-established and changeable passwords; admin reset with forced change; expiration; email/SMS notification on change; complexity/reuse/length policy; masked entry. **Evidence:** password-policy configuration; user-communication template. |
|
| **IA-5 Authenticator Management** | 🟡 | O | `C.5.7.8`, `C.5.7.12`, `C.5.7.13`, `C.5.7.14`, `C.5.7.45`, `C.5.7.46` | User-established and changeable passwords; admin reset with forced change; expiration; email/SMS notification on change; complexity/reuse/length policy; masked entry. **Evidence:** password-policy configuration; user-communication template. **Phase 0 (2026-10-01):** Users change their own password (12-character floor, current password required); hashes only. Evidence: `src/ypms/auth/router.py:138`, `src/ypms/auth/router.py:147`. Gap: Password-change notification (C.5.7.14) and admin reset arrive with notifications/admin. |
|
||||||
| **IA-5(1) Password-Based Authentication** | 📝 | O | `C.5.7.9`, `C.5.7.45` | FIPS 140-2 crypto (SSP tailors to FIPS 140-3 forward alignment); policy per RFP. **Evidence:** cryptographic-module cert; policy document. |
|
| **IA-5(1) Password-Based Authentication** | 🟡 | O | `C.5.7.9`, `C.5.7.45` | FIPS 140-2 crypto (SSP tailors to FIPS 140-3 forward alignment); policy per RFP. **Evidence:** cryptographic-module cert; policy document. **Phase 0 (2026-10-01):** PBKDF2-SHA256 (NIST SP 800-132) via passlib. Evidence: `src/ypms/auth/security.py:30`. Gap: FIPS 140-3 validated module is deployment-dependent (YPMS-POAM-032). |
|
||||||
| **IA-6 Authenticator Feedback** | 📝 | O | `C.5.7.46` | Masked password entry. **Evidence:** UI walkthrough. |
|
| **IA-6 Authenticator Feedback** | 📝 | O | `C.5.7.46` | Masked password entry. **Evidence:** UI walkthrough. |
|
||||||
| **IA-7 Cryptographic Module Authentication** | 📝 | O | `C.5.7.9` | FIPS 140-2 validated modules (SSP tailors: FIPS 140-3 where available). **Evidence:** module cert; usage inventory. |
|
| **IA-7 Cryptographic Module Authentication** | 📝 | O | `C.5.7.9` | FIPS 140-2 validated modules (SSP tailors: FIPS 140-3 where available). **Evidence:** module cert; usage inventory. |
|
||||||
| **IA-8 Identification and Authentication (Non-Organizational Users)** | ❓ | O | `C.5.3.2`, `C.5.7.30` | Youth and host authentication; LDAP-compliant; social-media login option. **Open decision (SSP §17.D):** identity model for minors — recommend social-media login for browsing/pre-application only, with IAL2-equivalent identity proofing (Login.gov or SSA verification) before banking/payroll actions. **Evidence:** identity-flow design; risk-acceptance memo. |
|
| **IA-8 Identification and Authentication (Non-Organizational Users)** | ❓ | O | `C.5.3.2`, `C.5.7.30` | Youth and host authentication; LDAP-compliant; social-media login option. **Open decision (SSP §17.D):** identity model for minors — recommend social-media login for browsing/pre-application only, with IAL2-equivalent identity proofing (Login.gov or SSA verification) before banking/payroll actions. **Evidence:** identity-flow design; risk-acceptance memo. |
|
||||||
@@ -349,7 +365,7 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| **RA-2 Security Categorization** | 📋 | D | | `ypms/FIPS_199_MEMO.md`. |
|
| **RA-2 Security Categorization** | 📋 | D | | `ypms/FIPS_199_MEMO.md`. |
|
||||||
| **RA-3 Risk Assessment** | 🤝 | O, D | | Initial RA before ATO; annually thereafter; on major change. **Evidence:** RA report. |
|
| **RA-3 Risk Assessment** | 🤝 | O, D | | Initial RA before ATO; annually thereafter; on major change. **Evidence:** RA report. |
|
||||||
| **RA-3(1) Supply Chain Risk Assessment** | 📝 | O | | Third-party and fourth-party disclosure in architecture document. **Evidence:** SBOM + third/fourth-party register. |
|
| **RA-3(1) Supply Chain Risk Assessment** | 📝 | O | | Third-party and fourth-party disclosure in architecture document. **Evidence:** SBOM + third/fourth-party register. |
|
||||||
| **RA-5 Vulnerability Monitoring and Scanning** | 📝 | O | `C.5.7.52` | Cyber-threat detection and remediation; authenticated and unauthenticated scans monthly; container image scans in CI on push (`YPMS-POAM-062`). **Evidence:** scan schedule + reports. |
|
| **RA-5 Vulnerability Monitoring and Scanning** | 🟡 | O | `C.5.7.52` | Cyber-threat detection and remediation; authenticated and unauthenticated scans monthly; container image scans in CI on push (`YPMS-POAM-062`). **Evidence:** scan schedule + reports. **Phase 0 (2026-10-01):** pip-audit on runtime dependencies gates CI; an unfixable `ecdsa` advisory was removed by changing JWT library. Evidence: `.gitea/workflows/ci.yml:59`. Gap: Pending first green CI run; infrastructure scanning is deployment work. |
|
||||||
| **RA-5(2) Update Vulnerabilities to be Scanned** | 📝 | O | | Feed updates. **Evidence:** feed configuration. |
|
| **RA-5(2) Update Vulnerabilities to be Scanned** | 📝 | O | | Feed updates. **Evidence:** feed configuration. |
|
||||||
| **RA-5(5) Privileged Access** | 📝 | O | | Authenticated scans with privileged access. **Evidence:** scan configuration. |
|
| **RA-5(5) Privileged Access** | 📝 | O | | Authenticated scans with privileged access. **Evidence:** scan configuration. |
|
||||||
| **RA-7 Risk Response** | 📋 | D | | POA&M-driven. **Evidence:** POA&M. |
|
| **RA-7 Risk Response** | 📋 | D | | POA&M-driven. **Evidence:** POA&M. |
|
||||||
@@ -368,7 +384,7 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| **SA-8 Security and Privacy Engineering Principles** | 📝 | O | | SSDLC adherence. **Evidence:** SSDLC document. |
|
| **SA-8 Security and Privacy Engineering Principles** | 📝 | O | | SSDLC adherence. **Evidence:** SSDLC document. |
|
||||||
| **SA-9 External System Services** | 📝 | O | `C.5.6.3` | Payroll, LMS, address verification, social-media IdPs, DC MAR — each with security assurance. **Evidence:** vendor register + SIG-Lite / SOC 2 collection. |
|
| **SA-9 External System Services** | 📝 | O | `C.5.6.3` | Payroll, LMS, address verification, social-media IdPs, DC MAR — each with security assurance. **Evidence:** vendor register + SIG-Lite / SOC 2 collection. |
|
||||||
| **SA-10 Developer Configuration Management** | 📝 | O | `C.5.8.4`–`C.5.8.11` | Data-model deliverables (conceptual/logical/physical ERDs, data dictionary, integrity rules, CRUD matrix, denormalization rationale). **Evidence:** deliverables in `ypms/`. |
|
| **SA-10 Developer Configuration Management** | 📝 | O | `C.5.8.4`–`C.5.8.11` | Data-model deliverables (conceptual/logical/physical ERDs, data dictionary, integrity rules, CRUD matrix, denormalization rationale). **Evidence:** deliverables in `ypms/`. |
|
||||||
| **SA-11 Developer Testing and Evaluation** | 📝 | O | source RFP §C.5.10 | Unit / functional / non-functional / UAT with evidence. **Evidence:** test reports; CI test results. |
|
| **SA-11 Developer Testing and Evaluation** | 🟡 | O | source RFP §C.5.10 | Unit / functional / non-functional / UAT with evidence. **Evidence:** test reports; CI test results. **Phase 0 (2026-10-01):** 167 automated tests, 80% coverage floor, ruff, strict mypy, bandit SAST, migration-drift test. Evidence: `.gitea/workflows/ci.yml:53`, `.gitea/workflows/ci.yml:56`. Gap: Pending first green CI run. |
|
||||||
| **SA-15 Development Process, Standards, and Tools** | 📝 | O | | DOES OIT SSDLC standards. **Evidence:** SSDLC document. |
|
| **SA-15 Development Process, Standards, and Tools** | 📝 | O | | DOES OIT SSDLC standards. **Evidence:** SSDLC document. |
|
||||||
| **SA-22 Unsupported System Components** | 📝 | O | `C.5.6.13` | Third-party dependencies tracked in SBOM with upgrade path. **Evidence:** SBOM + upgrade-path statement. |
|
| **SA-22 Unsupported System Components** | 📝 | O | `C.5.6.13` | Third-party dependencies tracked in SBOM with upgrade path. **Evidence:** SBOM + upgrade-path statement. |
|
||||||
|
|
||||||
@@ -384,11 +400,11 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| **SC-7(4) External Telecommunications Services** | 📝 | O | | Documented external services with ISAs. |
|
| **SC-7(4) External Telecommunications Services** | 📝 | O | | Documented external services with ISAs. |
|
||||||
| **SC-7(5) Deny by Default / Allow by Exception** | 📝 | O | | Default-deny firewall posture. **Evidence:** ruleset export. |
|
| **SC-7(5) Deny by Default / Allow by Exception** | 📝 | O | | Default-deny firewall posture. **Evidence:** ruleset export. |
|
||||||
| **SC-7(7) Split Tunneling for Remote Devices** | 📝 | O | | No split tunneling from managed devices. |
|
| **SC-7(7) Split Tunneling for Remote Devices** | 📝 | O | | No split tunneling from managed devices. |
|
||||||
| **SC-8 Transmission Confidentiality and Integrity** | ⚠️ | O | `C.5.6.15`, `C.5.7.25`, `C.5.7.20` | Source RFP says SSL; **SSP tailors to TLS 1.2 minimum, TLS 1.3 preferred, FIPS-approved cipher suites**. All PII encrypted in transit. **Evidence:** SSL Labs–style report; TLS config (`YPMS-POAM-031`). |
|
| **SC-8 Transmission Confidentiality and Integrity** | ⚠️ | O | `C.5.6.15`, `C.5.7.25`, `C.5.7.20` | Source RFP says SSL; **SSP tailors to TLS 1.2 minimum, TLS 1.3 preferred, FIPS-approved cipher suites**. All PII encrypted in transit. **Evidence:** SSL Labs–style report; TLS config (`YPMS-POAM-031`). **Phase 0 (2026-10-01):** Not wired in Phase 0 (no local TLS proxy). Gap: TLS terminates at Front Door / App Gateway at deployment (YPMS-POAM-031). |
|
||||||
| **SC-8(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS 140-2/-3 cryptography for transmission. **Evidence:** module cert. |
|
| **SC-8(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS 140-2/-3 cryptography for transmission. **Evidence:** module cert. |
|
||||||
| **SC-10 Network Disconnect** | 📝 | O | `C.5.7.3` | Idle-session network disconnect. **Evidence:** timeout config. |
|
| **SC-10 Network Disconnect** | 📝 | O | `C.5.7.3` | Idle-session network disconnect. **Evidence:** timeout config. |
|
||||||
| **SC-12 Cryptographic Key Establishment and Management** | 📝 | O | `C.5.7.9` | Key management via Azure Key Vault (Gov / HSM). **Evidence:** key-management design; rotation policy (`YPMS-POAM-032`). |
|
| **SC-12 Cryptographic Key Establishment and Management** | 🟡 | O | `C.5.7.9` | Key management via Azure Key Vault (Gov / HSM). **Evidence:** key-management design; rotation policy (`YPMS-POAM-032`). **Phase 0 (2026-10-01):** Column key derived with HKDF-SHA256; ≥32-byte key material and ≥32-byte JWT secret enforced at startup. Evidence: `src/ypms/security/column_crypto.py:49`, `src/ypms/settings.py:15`. Gap: Keys come from env vars; Key Vault (Gov/HSM) in Phase 1. |
|
||||||
| **SC-13 Cryptographic Protection** | 📝 | O | `C.5.7.9`, `C.5.7.25` | FIPS 140-3 cryptography (140-2 acceptable during deprecation window) at rest and in transit. **Evidence:** module cert + usage map. |
|
| **SC-13 Cryptographic Protection** | 🟡 | O | `C.5.7.9`, `C.5.7.25` | FIPS 140-3 cryptography (140-2 acceptable during deprecation window) at rest and in transit. **Evidence:** module cert + usage map. **Phase 0 (2026-10-01):** FIPS-approved algorithms only: AES-256-GCM, HKDF-SHA256, HMAC-SHA256 (JWT), PBKDF2-SHA256. Evidence: `src/ypms/security/column_crypto.py:55`. Gap: Validated-module status is deployment-dependent (YPMS-POAM-032). |
|
||||||
| **SC-15 Collaborative Computing Devices** | 📝 | O | | Camera/mic access disabled by default in workspace tooling. |
|
| **SC-15 Collaborative Computing Devices** | 📝 | O | | Camera/mic access disabled by default in workspace tooling. |
|
||||||
| **SC-17 Public Key Infrastructure Certificates** | 📝 | O | `C.5.6.5` | Certificate Authority configuration for integrations. **Evidence:** CA config. |
|
| **SC-17 Public Key Infrastructure Certificates** | 📝 | O | `C.5.6.5` | Certificate Authority configuration for integrations. **Evidence:** CA config. |
|
||||||
| **SC-18 Mobile Code** | 📝 | O | | Only signed mobile code allowed. |
|
| **SC-18 Mobile Code** | 📝 | O | | Only signed mobile code allowed. |
|
||||||
@@ -396,7 +412,7 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| **SC-21 Secure Name/Address Resolution Service (Recursive)** | 🔵 | I | | Inherited. |
|
| **SC-21 Secure Name/Address Resolution Service (Recursive)** | 🔵 | I | | Inherited. |
|
||||||
| **SC-22 Architecture and Provisioning for Name/Address Resolution** | 🔵 | I | | Inherited. |
|
| **SC-22 Architecture and Provisioning for Name/Address Resolution** | 🔵 | I | | Inherited. |
|
||||||
| **SC-23 Session Authenticity** | 📝 | O | `C.5.7.20` | Anti-CSRF, secure cookies, per-transaction integrity for financial actions. **Evidence:** session-management design. |
|
| **SC-23 Session Authenticity** | 📝 | O | `C.5.7.20` | Anti-CSRF, secure cookies, per-transaction integrity for financial actions. **Evidence:** session-management design. |
|
||||||
| **SC-28 Protection of Information at Rest** | 📝 | O | `C.5.7.25`, `C.5.7.50` | Encryption at rest for all PII (Postgres TDE + envelope encryption for blobs); SSN masked/anonymized in display (`YPMS-POAM-080`). **Evidence:** DB encryption config; SSN-handling design. |
|
| **SC-28 Protection of Information at Rest** | 🟡 | O | `C.5.7.25`, `C.5.7.50` | Encryption at rest for all PII (Postgres TDE + envelope encryption for blobs); SSN masked/anonymized in display (`YPMS-POAM-080`). **Evidence:** DB encryption config; SSN-handling design. **Phase 0 (2026-10-01):** SSN encrypted at the app layer (AES-256-GCM, column-bound AAD); write-only on the model; masked in every response. Evidence: `src/ypms/security/column_crypto.py:55`, `src/ypms/youth/models.py:83`. Gap: Banking columns not yet built; Postgres TDE and blob encryption are deployment. |
|
||||||
| **SC-28(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS-approved cryptography for at-rest data via Azure Key Vault. **Evidence:** module cert. |
|
| **SC-28(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS-approved cryptography for at-rest data via Azure Key Vault. **Evidence:** module cert. |
|
||||||
| **SC-39 Process Isolation** | 🔵 | I | | Inherited from IaaS + COTS platform. |
|
| **SC-39 Process Isolation** | 🔵 | I | | Inherited from IaaS + COTS platform. |
|
||||||
|
|
||||||
@@ -416,9 +432,9 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
|
|||||||
| **SI-7 Software, Firmware, and Information Integrity** | 📝 | O | | Signed artifacts + integrity checks on deploy. |
|
| **SI-7 Software, Firmware, and Information Integrity** | 📝 | O | | Signed artifacts + integrity checks on deploy. |
|
||||||
| **SI-7(1) Integrity Checks** | 📝 | O | | On startup and periodically. |
|
| **SI-7(1) Integrity Checks** | 📝 | O | | On startup and periodically. |
|
||||||
| **SI-8 Spam Protection** | 🤝 | O, D | | Inbound-email spam protection at DOES enterprise level. |
|
| **SI-8 Spam Protection** | 🤝 | O, D | | Inbound-email spam protection at DOES enterprise level. |
|
||||||
| **SI-10 Information Input Validation** | 📝 | O | `C.5.7.44`, `C.5.6.2`, `C.5.6.3.b`, `C.5.6.3.d` | Character set / length / numerical range / format / content validation with trusted-source restriction; address, age, residency data checks; SSA SSN verification; DC MAR address validation. **Evidence:** input-validation rules; sample validation-failure log. |
|
| **SI-10 Information Input Validation** | ✅ | O | `C.5.7.44`, `C.5.6.2`, `C.5.6.3.b`, `C.5.6.3.d` | Character set / length / numerical range / format / content validation with trusted-source restriction; address, age, residency data checks; SSA SSN verification; DC MAR address validation. **Evidence:** input-validation rules; sample validation-failure log. **Phase 0 (2026-10-01):** Pydantic validation on every request body; SSN format and never-issued ranges, DOB, ZIP, email, lengths. Evidence: `src/ypms/youth/schemas.py:21`. |
|
||||||
| **SI-11 Error Handling** | 📝 | O | `C.5.7.43`, `C.5.6.9` #2, #3 | Corrective information without adversary-exploitable disclosure; consistent error handling; all errors logged. **Evidence:** error-response inventory. |
|
| **SI-11 Error Handling** | ✅ | O | `C.5.7.43`, `C.5.6.9` #2, #3 | Corrective information without adversary-exploitable disclosure; consistent error handling; all errors logged. **Evidence:** error-response inventory. **Phase 0 (2026-10-01):** Unhandled errors return a generic body with request id; logs record type and frames, not messages; SQL parameters hidden. Evidence: `src/ypms/main.py:56`, `src/ypms/db.py:24`, `tests/test_app_guards.py`. |
|
||||||
| **SI-12 Information Management and Retention** | 🤝 | O, D | | Aligned with DOES records schedule. **Evidence:** retention policy. |
|
| **SI-12 Information Management and Retention** | 🤝 | O, D | | Aligned with DOES records schedule. **Evidence:** retention policy. **Phase 0 (2026-10-01):** Every table carries a retention policy; app refuses to boot otherwise; nightly report-only job. Evidence: `src/ypms/framework/retention.py:135`, `src/ypms/main.py:34`. Gap: Disposal execution is Phase 5; periods await YPMS-POAM-054. |
|
||||||
| **SI-16 Memory Protection** | 🔵 | I | | Inherited from IaaS / OS. |
|
| **SI-16 Memory Protection** | 🔵 | I | | Inherited from IaaS / OS. |
|
||||||
|
|
||||||
### SR — Supply Chain Risk Management
|
### SR — Supply Chain Risk Management
|
||||||
|
|||||||
+15
-15
@@ -79,7 +79,7 @@ Every Phase 0 component maps to a source-RFP requirement or a NIST control. Tabl
|
|||||||
| **0.6** | 501-stub routers for remaining entities | ✅ **Complete 2026-09-30.** 26 stub entities registered in `src/ypms/stubs.py`, each with a router from `framework/stub_router.py` exposing one route per matrix action (8). Every route runs the real RBAC check and writes an audit row before returning 501. `tests/test_stubs.py` sweeps entity × role × route against the matrix JSON (501 + one audit row when granted; 403 + none when not). Registrant restore moved to the matrix `Restore` action. 138 tests, 95.05% coverage. |
|
| **0.6** | 501-stub routers for remaining entities | ✅ **Complete 2026-09-30.** 26 stub entities registered in `src/ypms/stubs.py`, each with a router from `framework/stub_router.py` exposing one route per matrix action (8). Every route runs the real RBAC check and writes an audit row before returning 501. `tests/test_stubs.py` sweeps entity × role × route against the matrix JSON (501 + one audit row when granted; 403 + none when not). Registrant restore moved to the matrix `Restore` action. 138 tests, 95.05% coverage. |
|
||||||
| **0.7** | Retention scaffold | ✅ **Complete 2026-10-01.** Every mapped table resolves a `RetentionPolicy` (entity models declare `retention_policy`; vendored framework tables mapped by name). PIA §9 interim periods encoded in `src/ypms/retention_classes.py`, all unapproved pending `YPMS-POAM-054`; classes without a PIA basis report as `unset`. Report-only job in `src/ypms/framework/retention.py` (`python -m ypms.framework.retention`) logs "0 records to purge" and writes a `retention_run` audit row. Stub entity models + migration `0004_stub_entity_tables`. Migration-vs-model drift test added. 156 tests, 95.91% coverage. |
|
| **0.7** | Retention scaffold | ✅ **Complete 2026-10-01.** Every mapped table resolves a `RetentionPolicy` (entity models declare `retention_policy`; vendored framework tables mapped by name). PIA §9 interim periods encoded in `src/ypms/retention_classes.py`, all unapproved pending `YPMS-POAM-054`; classes without a PIA basis report as `unset`. Report-only job in `src/ypms/framework/retention.py` (`python -m ypms.framework.retention`) logs "0 records to purge" and writes a `retention_run` audit row. Stub entity models + migration `0004_stub_entity_tables`. Migration-vs-model drift test added. 156 tests, 95.91% coverage. |
|
||||||
| **0.8** | CI pipeline | 🟡 **Pipeline complete 2026-10-01; green-on-`main` pending a Gitea runner.** `.gitea/workflows/ci.yml`: ruff, mypy, pytest (80% floor, includes migration-drift test), bandit, pip-audit (runtime deps), gitleaks (pinned + sha256-verified, full history), CycloneDX SBOM artifact. Every step passes locally. Running the gates fixed a broken pip-audit invocation, removed `ecdsa` by swapping python-jose → PyJWT, and enforced a 32-byte `JWT_SECRET`. Closes when the first run on `main` is green. |
|
| **0.8** | CI pipeline | 🟡 **Pipeline complete 2026-10-01; green-on-`main` pending a Gitea runner.** `.gitea/workflows/ci.yml`: ruff, mypy, pytest (80% floor, includes migration-drift test), bandit, pip-audit (runtime deps), gitleaks (pinned + sha256-verified, full history), CycloneDX SBOM artifact. Every step passes locally. Running the gates fixed a broken pip-audit invocation, removed `ecdsa` by swapping python-jose → PyJWT, and enforced a 32-byte `JWT_SECRET`. Closes when the first run on `main` is green. |
|
||||||
| **0.9** | Docs + acceptance | `README.md` with runnable quickstart; `CONTRIBUTING.md` with hard rules; `docs/nist-mapping.md` skeleton; this file marked complete. |
|
| **0.9** | Docs + acceptance | ✅ **Complete 2026-10-01.** README rewritten (Docker + native quickstart, try-it flow, gates, add-an-entity steps); CONTRIBUTING gains rules 9–13; `docs/nist-mapping.md` with file:line evidence; control mapping v0.3 (12 ✅, 12 🟡, 4 progress notes). Closing §6 gaps added: failed-login audit (AU-2), opaque 500 handler + hidden SQL params (SI-11), startup retention check (§7 risk), migration hash pinning (CM-3). Definition of Done in §8: 12 of 14 checked; remaining two need Docker and a Gitea runner. 167 tests. |
|
||||||
|
|
||||||
Each milestone independently mergeable.
|
Each milestone independently mergeable.
|
||||||
|
|
||||||
@@ -255,20 +255,20 @@ Everything else in the control mapping stays 📝 until later phases wire it.
|
|||||||
|
|
||||||
Phase 0 is complete when:
|
Phase 0 is complete when:
|
||||||
|
|
||||||
- [ ] `docker compose up` boots app + Postgres cleanly; `/healthz` returns 200.
|
- [ ] `docker compose up` boots app + Postgres cleanly; `/healthz` returns 200. — **Open:** no Docker on the verifying workstation. Native boot (uvicorn + SQLite) verified; `/healthz` and `/readyz` 200.
|
||||||
- [ ] `alembic upgrade head` applies the baseline migration.
|
- [x] `alembic upgrade head` applies the baseline migration. — All four revisions, SQLite; also exercised by the drift test on every CI run. Postgres run rides with the Docker item.
|
||||||
- [ ] `POST /auth/login` issues a JWT with `iam.assurance_level` claim.
|
- [x] `POST /auth/login` issues a JWT with `iam.assurance_level` claim. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
|
||||||
- [ ] `POST /registrants` creates a Registrant with masked SSN in the response, emits an audit row, requires `OYP_STAFF` or higher.
|
- [x] `POST /registrants` creates a Registrant with masked SSN in the response, emits an audit row, requires `OYP_STAFF` or higher. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
|
||||||
- [ ] `GET /registrants/{id}` reads the record, fires the PII-read audit decorator, masks SSN, requires appropriate role.
|
- [x] `GET /registrants/{id}` reads the record, fires the PII-read audit decorator, masks SSN, requires appropriate role. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
|
||||||
- [ ] `PATCH /registrants/{id}` updates fields, emits before/after in audit row, requires appropriate role.
|
- [x] `PATCH /registrants/{id}` updates fields, emits before/after in audit row, requires appropriate role. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
|
||||||
- [ ] `DELETE /registrants/{id}` soft-deletes, emits audit, requires appropriate role.
|
- [x] `DELETE /registrants/{id}` soft-deletes, emits audit, requires appropriate role. (verified 2026-10-01: clean clone, live uvicorn, HTTP) Only the super-role holds `Delete` on Registrant today (matrix review item).
|
||||||
- [ ] Every stub entity's endpoints return 501 but still emit an audit row and enforce RBAC.
|
- [x] Every stub entity's endpoints return 501 but still emit an audit row and enforce RBAC. — `tests/test_stubs.py` sweeps 26 entities × 8 roles × 8 routes; also seen live.
|
||||||
- [ ] Tests green in CI: lint, types, pytest, SAST, SCA, secret scan, SBOM.
|
- [ ] Tests green in CI: lint, types, pytest, SAST, SCA, secret scan, SBOM. — **Open:** every gate passes locally; Gitea runs queue until an `act_runner` is registered.
|
||||||
- [ ] Coverage ≥ 80% on `framework/` and `security/`; every stub has one test.
|
- [x] Coverage ≥ 80% on `framework/` and `security/`; every stub has one test. — ~96% overall; `framework/router.py` still excluded (first real caller arrives with a domain entity).
|
||||||
- [ ] `README.md` quickstart works on a clean checkout.
|
- [x] `README.md` quickstart works on a clean checkout. — Native path verified from a fresh clone; Docker path rides with the Docker item.
|
||||||
- [ ] `CONTRIBUTING.md` published with hard rules mirrored from OWC.
|
- [x] `CONTRIBUTING.md` published with hard rules mirrored from OWC (plus YPMS rules 9–13).
|
||||||
- [ ] `docs/nist-mapping.md` lists the controls wired above, with file:line evidence.
|
- [x] `docs/nist-mapping.md` lists the controls wired above, with file:line evidence.
|
||||||
- [ ] Control mapping `NIST_800_53_CONTROL_MAPPING.md` updated: the Phase-0-wired controls flip from 📝 to ✅ (or 🟡 for partials); file:line evidence populated.
|
- [x] Control mapping `NIST_800_53_CONTROL_MAPPING.md` updated: the Phase-0-wired controls flip from 📝 to ✅ (or 🟡 for partials); file:line evidence populated. — v0.3: 12 ✅, 12 🟡; SC-8 stays ⚠️ (TLS is deployment-phase).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+30
-17
@@ -1,7 +1,7 @@
|
|||||||
# Phase 0 — Handoff status
|
# Phase 0 — Handoff status
|
||||||
|
|
||||||
**Last updated:** 2026-10-01
|
**Last updated:** 2026-10-01
|
||||||
**Phase:** 0 — Foundations (in flight)
|
**Phase:** 0 — Foundations (built; exit pending two infra checks)
|
||||||
**Owner:** Chris Tonjes, DOES CIO (System Owner / ISSO / AO)
|
**Owner:** Chris Tonjes, DOES CIO (System Owner / ISSO / AO)
|
||||||
**Companion docs:** [`PHASE0_PLAN.md`](PHASE0_PLAN.md) (scope + milestones), [`ARCHITECTURE.md`](ARCHITECTURE.md) (design), [`README.md`](README.md) (runnable quickstart)
|
**Companion docs:** [`PHASE0_PLAN.md`](PHASE0_PLAN.md) (scope + milestones), [`ARCHITECTURE.md`](ARCHITECTURE.md) (design), [`README.md`](README.md) (runnable quickstart)
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
Phase 0 milestones **0.1 → 0.7** are shipped; **0.8 (CI)** is complete except for its green-on-`main` exit check, which needs a Gitea Actions runner registered on `gitea.doesworks.net`. **0.9 (docs + acceptance)** is next. All ATO artifacts (SSP, control mapping, POA&M, PIA) are drafted at v0.1–v0.3 and awaiting review signatures. The repo builds, tests are green (**158 passing, 95.83% coverage**), and Docker Compose boots. `main` is pushed to Gitea.
|
All nine Phase 0 milestones are built. **Phase 0 exits on two infrastructure checks**, neither of which can run on the dev workstation: (1) the first green Gitea Actions run on `main` — needs an `act_runner` registered on `gitea.doesworks.net`; (2) `docker compose up` against real Postgres. Definition of Done (PHASE0_PLAN §8) is 12 of 14 checked; everything else was verified end to end over HTTP from a clean clone. Tests green (**167 passing, ~96% coverage**); every CI gate passes locally. Control mapping is at v0.3 with file:line evidence (12 ✅ / 12 🟡). `main` is pushed to Gitea.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -17,15 +17,15 @@ Phase 0 milestones **0.1 → 0.7** are shipped; **0.8 (CI)** is complete except
|
|||||||
|
|
||||||
| Dimension | Status |
|
| Dimension | Status |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Phase | 0 (Foundations), milestones 0.1–0.7 shipped; 0.8 pending runner |
|
| Phase | 0 (Foundations), milestones 0.1–0.9 built; exit pending CI runner + Docker/Postgres boot |
|
||||||
| Git branch | `main`, tracking `origin/main` (Gitea `https://gitea.doesworks.net/chris.tonjes/YPMS.git`) |
|
| Git branch | `main`, tracking `origin/main` (Gitea `https://gitea.doesworks.net/chris.tonjes/YPMS.git`) |
|
||||||
| Tests | **158 passing**, **95.83% coverage** (floor 80% enforced in CI) |
|
| Tests | **167 passing**, **~96% coverage** (floor 80% enforced in CI) |
|
||||||
| CI | `.gitea/workflows/ci.yml` — all gates pass locally; runs queue as "Waiting" until a runner is registered |
|
| CI | `.gitea/workflows/ci.yml` — all gates pass locally; runs queue as "Waiting" until a runner is registered |
|
||||||
| Lint / types | `ruff` clean, `mypy` clean (43 files, strict) |
|
| Lint / types | `ruff` clean, `mypy` clean (43 files, strict) |
|
||||||
| Runtime | FastAPI + Postgres on Docker Compose; boots to `/healthz` + `/readyz`; `/auth/*` and `/registrants/*` live; 26 stub entities return 501 behind RBAC + audit |
|
| Runtime | Verified booting natively (uvicorn + SQLite) from a clean clone; Docker Compose + Postgres not yet run; `/auth/*` and `/registrants/*` live; 26 stub entities return 501 behind RBAC + audit |
|
||||||
| Migrations | 4 revisions (`0001`–`0004_stub_entity_tables`); all apply cleanly to SQLite in the drift test; live Postgres run pending (no Docker on the dev workstation) |
|
| Migrations | 4 revisions (`0001`–`0004_stub_entity_tables`); all apply cleanly to SQLite in the drift test; live Postgres run pending (no Docker on the dev workstation) |
|
||||||
| Seed | 8 roles + permissions matrix + bootstrap admin (`chris` / `DOES_SYSTEM_ADMIN`) |
|
| Seed | 8 roles + permissions matrix + bootstrap admin (`chris` / `DOES_SYSTEM_ADMIN`) |
|
||||||
| ATO docs | SSP v0.3, control mapping v0.2, POA&M v0.1, PIA v0.1 — all drafts, awaiting signatures |
|
| ATO docs | SSP v0.3, control mapping **v0.3 (Phase 0 evidence)**, POA&M v0.1, PIA v0.1 — all drafts, awaiting signatures |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -115,15 +115,29 @@ Guardrails baked in: `OYP_STAFF` can't see FERPA/CFSA (that's `ELIGIBILITY_EXAMI
|
|||||||
- Every step passes locally. Gitea has queued runs #1–#5 (and this push) as **Waiting** — no runner is online.
|
- Every step passes locally. Gitea has queued runs #1–#5 (and this push) as **Waiting** — no runner is online.
|
||||||
- **To close 0.8:** register an `act_runner` against `gitea.doesworks.net` with an `ubuntu-latest` label mapped to an act-compatible image (e.g. `catthehacker/ubuntu:act-22.04`), outbound to github.com + PyPI. Then confirm the newest run on `main` is green.
|
- **To close 0.8:** register an `act_runner` against `gitea.doesworks.net` with an `ubuntu-latest` label mapped to an act-compatible image (e.g. `catthehacker/ubuntu:act-22.04`), outbound to github.com + PyPI. Then confirm the newest run on `main` is green.
|
||||||
|
|
||||||
|
### 0.9 — Docs + acceptance ✅
|
||||||
|
|
||||||
|
- **README** rewritten: Docker and native (no-Docker) quickstarts, a try-it curl flow, all gate commands, and step-by-step "promote a stub to a real entity".
|
||||||
|
- **CONTRIBUTING** rules 9–13: PII reads emit `pii_read`; encrypted columns go in `__audit_redact__`; every table has a retention policy; model modules registered in `models.py`; vendored-code changes need a reason + upstream list entry. PR checklist and suppression rule updated.
|
||||||
|
- **`docs/nist-mapping.md`** — per-control code evidence with linked file:line and the remaining gap. **Control mapping v0.3** carries the same notes per row plus a status-shift table: 12 ✅, 12 🟡; AU-11, CM-3, SI-12 keep 🤝 and SC-8 keeps ⚠️, each with a progress note.
|
||||||
|
- **Closed gaps found while preparing evidence** (each promised by PHASE0_PLAN §6/§7 but not yet built):
|
||||||
|
- **Failed logins weren't audited** (AU-2, C.5.7.39). Now `login_failed` with reason `bad_credentials` / `unknown_user` / `inactive` / `throttled`. The typed username is stored only when it matches a real account.
|
||||||
|
- **SI-11:** app-wide handler returns `{"error":"internal_error","request_id":…}`; logs type + stack frames, never the message (driver errors echo PII); engine `hide_parameters=True`.
|
||||||
|
- **Startup retention check:** `create_app` refuses to boot if any table lacks a policy.
|
||||||
|
- **CM-3:** `migrations/IMMUTABLE.sha256` pins merged revisions; test fails on edits.
|
||||||
|
- Alembic `fileConfig(disable_existing_loggers=False)` — in-process migrations were silencing app loggers in tests.
|
||||||
|
- **End-to-end check** from a clean clone: migrate → seed → uvicorn → login, Registrant create/read/update/delete, stub 501, audit trail, ciphertext-only at rest, no SSN in the server log.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What's next
|
## What's next
|
||||||
|
|
||||||
### Immediate next milestone: 0.9 — Docs + acceptance
|
### Phase 0 exit — two infrastructure checks
|
||||||
|
|
||||||
`README.md` with runnable quickstart (expand the existing one), `CONTRIBUTING.md` hard rules (add the new ones: every table needs a retention policy; new model modules go in `models.py`; suppressions are per-finding), `docs/nist-mapping.md` skeleton, and flip `NIST_800_53_CONTROL_MAPPING.md` rows for the Phase-0-wired controls from 📝 to ✅/🟡 with file evidence (PHASE0_PLAN §7 acceptance). Phase 0 is done when 0.8's runner is green and 0.9's checklist is signed.
|
1. **CI green on `main`.** Register an `act_runner` on `gitea.doesworks.net` (label `ubuntu-latest` → an act-compatible image such as `catthehacker/ubuntu:act-22.04`; outbound to github.com and PyPI). The queued runs will start; confirm the newest on `main` passes. Closes 0.8 and the CI item in PLAN §8.
|
||||||
|
2. **Docker + Postgres boot.** On a Docker-capable machine: `cp .env.example .env && docker compose up --build`, then the README "Try it" flow. Closes the last PLAN §8 item and gives the first live Postgres run of migrations 0001–0004. If Postgres disagrees with SQLite anywhere, fix in a new migration.
|
||||||
|
|
||||||
Then Phase 1 (applications + identity + parental consent) starts.
|
Then: sign off Phase 0, and draft `PHASE1_PLAN.md` (applications + identity + parental consent — ARCH §12). Decisions that should be made before Phase 1 starts: the four permission-matrix review items below, the 0002 constraint follow-up, and `YPMS-POAM-050` (youth identity model), which Phase 1 is built around.
|
||||||
|
|
||||||
### Upstream to OWC (accumulated)
|
### Upstream to OWC (accumulated)
|
||||||
|
|
||||||
@@ -336,9 +350,9 @@ Then the ATO artifacts as needed:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## POA&M state (as of 2026-09-30)
|
## POA&M state (as of 2026-10-01)
|
||||||
|
|
||||||
- **62 items total** — 2 fully closed, 4 partial closures under `YPMS-POAM-040`
|
- **62 items total** — 2 fully closed; 9 Phase 0 milestone partial closures logged under `YPMS-POAM-040`; `YPMS-POAM-042` (CI/SBOM) in progress pending the runner
|
||||||
- **6 open decisions** (see Open Questions table above)
|
- **6 open decisions** (see Open Questions table above)
|
||||||
- **Immediate remaining action items:**
|
- **Immediate remaining action items:**
|
||||||
- PIA route to Privacy Officer + OGC review
|
- PIA route to Privacy Officer + OGC review
|
||||||
@@ -348,14 +362,13 @@ Then the ATO artifacts as needed:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Signals to watch for as we approach milestone 0.9
|
## Signals to watch for at Phase 0 exit / Phase 1 start
|
||||||
|
|
||||||
- Control-mapping status flips need file:line evidence, not "implemented in Phase 0". Auditors trace the path.
|
|
||||||
- Don't weaken a gate to get green (no blanket bandit skips, no `--cov-fail-under` drop). Suppressions are per-finding with a reason — e.g. the inline `gitleaks:allow` on the test JWT secret in `tests/conftest.py`.
|
|
||||||
- If the first real CI run fails on something local runs didn't catch (runner image, network), fix the workflow, not the gate.
|
- If the first real CI run fails on something local runs didn't catch (runner image, network), fix the workflow, not the gate.
|
||||||
|
- Phase 1 promotes Guardian and ConsentRecord from stubs: follow README "Adding an entity", and give both the Registrant treatment (`pii_read`, `__audit_redact__` for anything encrypted, coverage doc).
|
||||||
|
- PARENT_USER access to a child's Registrant hangs on the Guardian link — replace the Phase 0 "parents see nothing" rule in `youth/router.py`, don't layer around it.
|
||||||
|
- Keep `docs/nist-mapping.md` and the control-mapping rows in step; line numbers drift, so refresh them at each phase gate.
|
||||||
- Retention execution (actual disposal) is Phase 5 work, alongside the OSSE/CFSA ingest pipelines. Don't add it in passing.
|
- Retention execution (actual disposal) is Phase 5 work, alongside the OSSE/CFSA ingest pipelines. Don't add it in passing.
|
||||||
- Any entity that will hold PII in Phase 1+ (Guardian, TaxDocument, Attachment) should get the Registrant treatment then — `pii_read` on reads, `__audit_redact__` on encrypted columns.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**End of handoff v0.5.**
|
**End of handoff v0.6.**
|
||||||
@@ -180,6 +180,7 @@ Closures retained for traceability.
|
|||||||
| — | Phase 0 milestone 0.6 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | 501-stub routers for 26 entities; every route enforces RBAC and writes an audit row before returning 501 (source RFP C.5.7.27; NIST AC-3, AU-12). Matrix-driven test sweep covers every entity × role × action. Permission-matrix review items recorded in PHASE0_STATUS.md. 138 tests passing, 95.05% coverage. |
|
| — | Phase 0 milestone 0.6 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | 501-stub routers for 26 entities; every route enforces RBAC and writes an audit row before returning 501 (source RFP C.5.7.27; NIST AC-3, AU-12). Matrix-driven test sweep covers every entity × role × action. Permission-matrix review items recorded in PHASE0_STATUS.md. 138 tests passing, 95.05% coverage. |
|
||||||
| — | Phase 0 milestone 0.7 shipped (partial closure of YPMS-POAM-040) | 2026-10-01 | Retention scaffold (NIST AU-11, SI-12; source RFP C.5.7.42): every table carries a policy; PIA §9 interim periods encoded but unapproved; report-only nightly job logs eligible counts and audits each run. Implementation is ready to consume the signed schedule from YPMS-POAM-054 (flip `approved`, cite authority). 156 tests passing, 95.91% coverage. |
|
| — | Phase 0 milestone 0.7 shipped (partial closure of YPMS-POAM-040) | 2026-10-01 | Retention scaffold (NIST AU-11, SI-12; source RFP C.5.7.42): every table carries a policy; PIA §9 interim periods encoded but unapproved; report-only nightly job logs eligible counts and audits each run. Implementation is ready to consume the signed schedule from YPMS-POAM-054 (flip `approved`, cite authority). 156 tests passing, 95.91% coverage. |
|
||||||
| — | Phase 0 milestone 0.8 pipeline complete (partial closure of YPMS-POAM-040; advances YPMS-POAM-042) | 2026-10-01 | Gitea Actions workflow with lint, strict types, tests + coverage floor, migration drift test, SAST, SCA on runtime deps, full-history secret scan, CycloneDX SBOM. All gates pass locally; green-on-main pending runner registration. Removed `ecdsa` (PYSEC-2026-1325) by moving JWT to PyJWT; enforced 32-byte JWT secret (RFC 7518). 158 tests passing, 95.83% coverage. |
|
| — | Phase 0 milestone 0.8 pipeline complete (partial closure of YPMS-POAM-040; advances YPMS-POAM-042) | 2026-10-01 | Gitea Actions workflow with lint, strict types, tests + coverage floor, migration drift test, SAST, SCA on runtime deps, full-history secret scan, CycloneDX SBOM. All gates pass locally; green-on-main pending runner registration. Removed `ecdsa` (PYSEC-2026-1325) by moving JWT to PyJWT; enforced 32-byte JWT secret (RFC 7518). 158 tests passing, 95.83% coverage. |
|
||||||
|
| — | Phase 0 milestone 0.9 shipped (partial closure of YPMS-POAM-040) | 2026-10-01 | Docs + acceptance: README, CONTRIBUTING rules 9–13, `docs/nist-mapping.md`, control mapping v0.3 with file:line evidence (12 ✅ / 12 🟡). Closed Phase 0 control gaps: failed-login audit (AU-2, C.5.7.39), opaque error handler + hidden SQL parameters (SI-11, C.5.7.43), startup retention-policy check, migration hash pinning (CM-3). End-to-end HTTP verification from a clean clone. Phase 0 exit remaining: Docker/Postgres boot check and first green Gitea CI run. 167 tests passing. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
DC Department of Employment Services / Office of Youth Programs — in-house build replacing seven legacy tools (MBSYEP intake, TMS timekeeping, PF3 admin, manual payroll scripts, MicroStrategy, and two supporting apps) with a single Python / FastAPI / Postgres system on Azure Government.
|
DC Department of Employment Services / Office of Youth Programs — in-house build replacing seven legacy tools (MBSYEP intake, TMS timekeeping, PF3 admin, manual payroll scripts, MicroStrategy, and two supporting apps) with a single Python / FastAPI / Postgres system on Azure Government.
|
||||||
|
|
||||||
**Status:** Pre-development. Phase 0 (foundations) in flight.
|
**Status:** Phase 0 (foundations) — framework, auth, RBAC, audit, Registrant vertical slice, 501 stubs for every other entity, retention scaffold, CI. See [`PHASE0_STATUS.md`](PHASE0_STATUS.md).
|
||||||
**System owner + ISSO + AO:** Chris Tonjes, DOES CIO.
|
**System owner + ISSO + AO:** Chris Tonjes, DOES CIO.
|
||||||
**Full context:** [`OVERVIEW.md`](OVERVIEW.md), [`ARCHITECTURE.md`](ARCHITECTURE.md), [`PHASE0_PLAN.md`](PHASE0_PLAN.md).
|
**Full context:** [`OVERVIEW.md`](OVERVIEW.md), [`ARCHITECTURE.md`](ARCHITECTURE.md), [`PHASE0_PLAN.md`](PHASE0_PLAN.md).
|
||||||
|
|
||||||
## Quickstart (local)
|
## Quickstart — Docker (canonical)
|
||||||
|
|
||||||
Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free.
|
Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free.
|
||||||
|
|
||||||
@@ -15,68 +15,107 @@ cp .env.example .env
|
|||||||
docker compose up --build
|
docker compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
App at http://localhost:8000. OpenAPI docs at http://localhost:8000/docs.
|
Compose runs `alembic upgrade head`, seeds roles + permissions + the bootstrap admin (`chris` / `change-me-immediately`, or `SEED_ADMIN_PASSWORD`), then starts the app.
|
||||||
|
|
||||||
|
- App: http://localhost:8000 — OpenAPI at http://localhost:8000/docs
|
||||||
|
- Adminer: http://localhost:8081 — System PostgreSQL, server `db`, user/password/db `ypms`
|
||||||
|
- Stop with `Ctrl+C`; wipe the local DB with `docker compose down -v`.
|
||||||
|
|
||||||
|
## Quickstart — native, no Docker
|
||||||
|
|
||||||
|
Uses SQLite in place of Postgres. Fine for poking at the API; Postgres-specific behaviour needs the Docker path.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:8000/healthz # → {"status":"ok"}
|
python3.12 -m venv .venv && source .venv/bin/activate
|
||||||
curl http://localhost:8000/readyz # → {"status":"ok"} once db is up
|
|
||||||
```
|
|
||||||
|
|
||||||
Adminer (DB explorer) at http://localhost:8081. Connect with:
|
|
||||||
- System: PostgreSQL
|
|
||||||
- Server: `db`
|
|
||||||
- Username / Password / Database: `ypms` / `ypms` / `ypms`
|
|
||||||
|
|
||||||
Stop with `Ctrl+C`; wipe local DB with `docker compose down -v`.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Local (no Docker required for the tests):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -m venv .venv && source .venv/bin/activate
|
|
||||||
pip install -e '.[dev]'
|
pip install -e '.[dev]'
|
||||||
pytest
|
|
||||||
|
export DATABASE_URL=sqlite:///./ypms-dev.db
|
||||||
|
export JWT_SECRET=dev-jwt-secret-change-me-32-bytes-min # >= 32 bytes or the app won't start
|
||||||
|
export COLUMN_ENCRYPTION_KEY=dev-col-enc-key-change-me-32bytes-______
|
||||||
|
export SEED_ADMIN_PASSWORD=pick-a-local-password
|
||||||
|
|
||||||
|
alembic upgrade head
|
||||||
|
python -m ypms.seed.run
|
||||||
|
uvicorn ypms.main:app --reload
|
||||||
```
|
```
|
||||||
|
|
||||||
Coverage:
|
## Try it
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest --cov=src/ypms --cov-report=term-missing
|
curl http://localhost:8000/healthz # {"status":"ok"}
|
||||||
|
curl http://localhost:8000/readyz # {"status":"ok"} once the DB is reachable
|
||||||
|
|
||||||
|
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
|
||||||
|
-d 'username=chris&password=change-me-immediately' | python3 -c 'import json,sys;print(json.load(sys.stdin)["access_token"])')
|
||||||
|
|
||||||
|
curl -s -X POST http://localhost:8000/registrants \
|
||||||
|
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
||||||
|
-d '{"first_name":"Test","last_name":"Youth","dob":"2010-05-01","ssn":"123-45-6789"}'
|
||||||
|
# → ssn_masked "XXX-XX-6789"; the SSN itself is never returned
|
||||||
|
|
||||||
|
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8000/guardians -H "Authorization: Bearer $TOKEN"
|
||||||
|
# → 501 (stub: RBAC checked and audited, no logic yet)
|
||||||
```
|
```
|
||||||
|
|
||||||
Tests use SQLite in-memory for speed. Postgres-specific behaviour gets its own Postgres-only fixture when a migration or query needs it (see [`ARCHITECTURE.md`](ARCHITECTURE.md) §4.8).
|
Change the bootstrap password right away: `POST /auth/change-password`.
|
||||||
|
|
||||||
|
The retention job is report-only in Phase 0:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m ypms.framework.retention # logs "0 records to purge" and writes an audit row
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests and gates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest # SQLite in-memory, ~10s
|
||||||
|
pytest --cov=src/ypms --cov-report=term-missing --cov-fail-under=80
|
||||||
|
ruff check src tests
|
||||||
|
mypy src
|
||||||
|
bandit -q -r src -c pyproject.toml
|
||||||
|
pip-audit --strict . # runtime dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
CI runs all of these plus gitleaks and a CycloneDX SBOM on Gitea Actions — [`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml).
|
||||||
|
|
||||||
## Repo shape
|
## Repo shape
|
||||||
|
|
||||||
Full tree in [`ARCHITECTURE.md`](ARCHITECTURE.md) §5.1. High points:
|
Full tree in [`ARCHITECTURE.md`](ARCHITECTURE.md) §5.1. High points:
|
||||||
|
|
||||||
- `src/ypms/main.py` — FastAPI factory
|
- `src/ypms/main.py` — app factory; refuses to start if any table lacks a retention policy; opaque 500 handler
|
||||||
- `src/ypms/settings.py` — pydantic-settings config
|
- `src/ypms/settings.py` — pydantic-settings config (secret-length floors enforced)
|
||||||
- `src/ypms/db.py` — SQLAlchemy engine, session, `Base`
|
- `src/ypms/framework/` — vendored from OWC, plus YPMS additions: `pii_audit.py`, `stub_router.py`, `retention.py`
|
||||||
- `src/ypms/logging.py` — structlog + sensitive-field sanitizer
|
- `src/ypms/security/` — RBAC, `column_crypto.py` (AES-256-GCM app-layer encryption)
|
||||||
- `src/ypms/middleware.py` — request id
|
- `src/ypms/auth/` — login, JWT (PyJWT), refresh, change-password, login rate limit
|
||||||
- `src/ypms/framework/` — vendored from OWC, milestone 0.2
|
- `src/ypms/youth/` — Registrant, the real vertical-slice entity
|
||||||
- `src/ypms/security/` — auth + RBAC + column encryption, milestones 0.3–0.4
|
- `src/ypms/stubs.py`, `src/ypms/stub_models.py` — the 26 entities still at 501
|
||||||
- `src/ypms/youth/` — Registrant entity (vertical slice), milestone 0.5
|
- `src/ypms/retention_classes.py` — PIA §9 retention policy catalog
|
||||||
- Other entity packages — 501 stubs, milestone 0.6
|
- `src/ypms/models.py` — the one list of model modules
|
||||||
- `migrations/` — Alembic, immutable-once-merged
|
- `src/ypms/seed/` — roles, permissions matrix, bootstrap admin
|
||||||
- `tests/` — pytest, SQLite by default
|
- `migrations/` — Alembic; merged revisions pinned in `IMMUTABLE.sha256`
|
||||||
|
- `docs/coverage/` — per-entity requirement traces; `docs/nist-mapping.md` — control evidence index
|
||||||
|
|
||||||
## Adding an entity
|
## Adding an entity (promoting a stub to real)
|
||||||
|
|
||||||
Once the framework lands in milestone 0.2, adding a new entity follows the OWC add-entity pattern (model → schemas → router → main.py registration → migration → permissions seed entry → test → coverage doc). Full instructions land here at milestone 0.2 completion.
|
1. Move the class from `src/ypms/stub_models.py` into `src/ypms/<domain>/models.py`; keep `__tablename__` and `retention_policy`. Add the module to `src/ypms/models.py`.
|
||||||
|
2. Add columns in a **new** migration; append its hash to `migrations/IMMUTABLE.sha256`.
|
||||||
|
3. Encrypted columns: write through `security.column_crypto`, list them in `__audit_redact__`.
|
||||||
|
4. Schemas in `<domain>/schemas.py`; the read schema masks anything sensitive.
|
||||||
|
5. Router: `framework.router.make_router` for plain CRUD, or a hand-built router on `CRUDService` (see `youth/router.py`) when you need PII-read audit or row scoping. Register it in `main.py` and remove the entity from `src/ypms/stubs.py`.
|
||||||
|
6. Check the permissions matrix rows for the entity — they're policy; review them as such.
|
||||||
|
7. Tests, then a coverage doc at `docs/coverage/<entity>.md` (see `registrant.md`).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the hard rules. Highlights: migrations are immutable once merged, every state-mutating route emits an audit row, framework/security modules must not carry domain logic, no `--no-verify` or `# type: ignore` without a written reason.
|
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the hard rules.
|
||||||
|
|
||||||
## Compliance
|
## Compliance
|
||||||
|
|
||||||
- [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) v0.3 — NIST 800-53 Moderate SSP
|
- [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) — NIST 800-53 Moderate SSP
|
||||||
- [`NIST_800_53_CONTROL_MAPPING.md`](NIST_800_53_CONTROL_MAPPING.md) v0.2 — full control-family map
|
- [`NIST_800_53_CONTROL_MAPPING.md`](NIST_800_53_CONTROL_MAPPING.md) v0.3 — full control map with Phase 0 evidence
|
||||||
- [`PRIVACY_IMPACT_ASSESSMENT.md`](PRIVACY_IMPACT_ASSESSMENT.md) v0.1 — PIA covering minors, SSN, banking, FERPA, CFSA
|
- [`docs/nist-mapping.md`](docs/nist-mapping.md) — Phase 0 control evidence index (file:line)
|
||||||
- [`POAM.md`](POAM.md) v0.1 — POA&M with 62 items and 2 closures as of 2026-09-30
|
- [`PRIVACY_IMPACT_ASSESSMENT.md`](PRIVACY_IMPACT_ASSESSMENT.md) — PIA covering minors, SSN, banking, FERPA, CFSA
|
||||||
|
- [`POAM.md`](POAM.md) — plan of action and milestones
|
||||||
|
|
||||||
## License / usage
|
## License / usage
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# NIST 800-53 — Phase 0 code evidence
|
||||||
|
|
||||||
|
**As of:** 2026-10-01 · **Scope:** controls that Phase 0 code implements or partially implements (PHASE0_PLAN.md §6) · **Authoritative mapping:** [`NIST_800_53_CONTROL_MAPPING.md`](../NIST_800_53_CONTROL_MAPPING.md) v0.3
|
||||||
|
|
||||||
|
This is the developer-facing index: for each control, where the code is and what's still missing. The control mapping carries the same notes in each row for assessors. When a change wires or advances a control, update both (CONTRIBUTING PR checklist).
|
||||||
|
|
||||||
|
Line numbers are as of the commit that last touched this file. If they drift, the function or name in the summary is the stable anchor.
|
||||||
|
|
||||||
|
| Control | Status | What the code does | Evidence | Remaining gap |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **AC-2** | 🟡 | User model with active flag; inactive accounts rejected at authentication. | [`src/ypms/users/models.py:48`](../src/ypms/users/models.py#L48)<br>[`src/ypms/auth/deps.py:43`](../src/ypms/auth/deps.py#L43) | No admin enable/disable endpoint (admin module, Phase 8); inactivity auto-disable is YPMS-POAM-071. |
|
||||||
|
| **AC-3** | ✅ | `require_permission` runs on every protected route, including all 208 stub routes; portal-only callers restricted to their own Registrant row. | [`src/ypms/security/rbac.py:46`](../src/ypms/security/rbac.py#L46)<br>[`src/ypms/youth/router.py:67`](../src/ypms/youth/router.py#L67)<br>[`tests/test_stubs.py`](../tests/test_stubs.py) | Row-level scoping beyond Registrant is Phase 2+. |
|
||||||
|
| **AC-6** | ✅ | Data-driven RBAC (8 roles × 30 entities × 8 actions); FERPA/CFSA entities limited to ELIGIBILITY_EXAMINER; super-role explicit. | [`src/ypms/seed/permissions_matrix.json`](../src/ypms/seed/permissions_matrix.json)<br>[`src/ypms/security/rbac.py:22`](../src/ypms/security/rbac.py#L22)<br>[`tests/test_stubs.py`](../tests/test_stubs.py) | Four matrix review items open (PHASE0_STATUS.md). |
|
||||||
|
| **AC-6(1)** | ✅ | Only DOES_SYSTEM_ADMIN holds RBAC-table rights (User/Role not granted to any other role). | [`src/ypms/seed/permissions_matrix.json`](../src/ypms/seed/permissions_matrix.json) | — |
|
||||||
|
| **AC-6(10)** | ✅ | Delete and Restore (super-role only today) and Registrant VersionHistory (OYP_STAFF and ELIGIBILITY_EXAMINER only) enforced server-side; 403 asserted for every other role. | [`src/ypms/security/rbac.py:29`](../src/ypms/security/rbac.py#L29)<br>[`tests/test_stubs.py`](../tests/test_stubs.py)<br>[`tests/youth/test_registrant_api.py`](../tests/youth/test_registrant_api.py) | — |
|
||||||
|
| **AC-7** | 🟡 | 5 failed logins per source IP per 15 minutes → 429; every failure (bad credentials, unknown user, inactive, throttled) audited as `login_failed`. | [`src/ypms/auth/rate_limit.py:47`](../src/ypms/auth/rate_limit.py#L47)<br>[`src/ypms/auth/router.py:86`](../src/ypms/auth/router.py#L86)<br>[`src/ypms/auth/router.py:65`](../src/ypms/auth/router.py#L65) | Per-account lockout (`locked_until`) and user notification not built; in-memory limiter is per-process. |
|
||||||
|
| **AC-12** | ✅ | Access tokens expire (default 60 min, `exp` claim verified); refresh tokens cannot access resources. | [`src/ypms/auth/security.py:59`](../src/ypms/auth/security.py#L59)<br>[`src/ypms/auth/deps.py:35`](../src/ypms/auth/deps.py#L35) | Idle-timeout UI behaviour lands with the portal (Phase 3). |
|
||||||
|
| **AU-2** | 🟡 | Audited today: login success/failure, password change, every create/update/delete/restore, every PII read, every stub invocation, each retention run. | [`src/ypms/audit/service.py:17`](../src/ypms/audit/service.py#L17)<br>[`src/ypms/framework/crud.py:111`](../src/ypms/framework/crud.py#L111)<br>[`src/ypms/auth/router.py:65`](../src/ypms/auth/router.py#L65)<br>[`src/ypms/auth/router.py:104`](../src/ypms/auth/router.py#L104) | Event catalogue items not yet applicable (role changes, shutdown, file access) arrive with their features. |
|
||||||
|
| **AU-3** | ✅ | Each row: actor id + username, action, entity, entity id, before/after, timestamp, IP; app-layer ciphertext redacted from before/after. | [`src/ypms/audit/models.py:18`](../src/ypms/audit/models.py#L18)<br>[`src/ypms/framework/crud.py:46`](../src/ypms/framework/crud.py#L46) | Request id is in logs, not yet on the audit row. |
|
||||||
|
| **AU-8** | ✅ | All timestamps timezone-aware UTC; Postgres container pinned to UTC. | [`src/ypms/audit/models.py:24`](../src/ypms/audit/models.py#L24)<br>[`docker-compose.yml:8`](../docker-compose.yml#L8) | NTP is inherited from Azure at deployment. |
|
||||||
|
| **AU-11** | 🤝 | Retention scaffold: audit log carries the PIA §9 interim policy (archive at 1 year); report-only job. | [`src/ypms/retention_classes.py`](../src/ypms/retention_classes.py)<br>[`src/ypms/framework/retention.py:150`](../src/ypms/framework/retention.py#L150) | Period unapproved until YPMS-POAM-054; archive tier is deployment work. |
|
||||||
|
| **AU-12** | ✅ | Audit generation is in the framework (CRUDService, stub router, auth), not per-handler. | [`src/ypms/framework/crud.py:111`](../src/ypms/framework/crud.py#L111)<br>[`src/ypms/framework/stub_router.py:66`](../src/ypms/framework/stub_router.py#L66) | — |
|
||||||
|
| **AU-14** | ✅ | `pii_read` audit on Registrant GET, list (ids recorded), and history. | [`src/ypms/framework/pii_audit.py:36`](../src/ypms/framework/pii_audit.py#L36)<br>[`src/ypms/youth/router.py:99`](../src/ypms/youth/router.py#L99)<br>[`src/ypms/youth/router.py:115`](../src/ypms/youth/router.py#L115) | Extends to Guardian/TaxDocument/Attachment as they are built. |
|
||||||
|
| **CM-3** | 🤝 | Merged migrations pinned by SHA-256; CI fails on any edit. Every change ships through a reviewed commit. | [`migrations/IMMUTABLE.sha256`](../migrations/IMMUTABLE.sha256)<br>[`tests/test_migration_immutability.py:31`](../tests/test_migration_immutability.py#L31) | Maintenance-window process is organizational. |
|
||||||
|
| **CM-8** | 🟡 | CycloneDX 1.6 SBOM of the runtime dependency set generated in CI. | [`.gitea/workflows/ci.yml:70`](../.gitea/workflows/ci.yml#L70) | Pending first green CI run (Gitea runner); per-release retention of SBOMs not yet defined. |
|
||||||
|
| **IA-2** | ✅ | Every non-public route requires a valid bearer token bound to a unique, active user. | [`src/ypms/auth/deps.py:25`](../src/ypms/auth/deps.py#L25) | MFA enhancements (IA-2(1)/(2)) arrive with Entra ID in Phase 1. |
|
||||||
|
| **IA-4** | 🟡 | Usernames unique (DB constraint); no user-delete path exists, so identifiers are never re-issued. | [`migrations/versions/0002_framework_tables.py:54`](../migrations/versions/0002_framework_tables.py#L54) | 12-character minimum identifier and explicit tombstoning arrive with registration (Phase 1). |
|
||||||
|
| **IA-5** | 🟡 | Users change their own password (12-character floor, current password required); hashes only. | [`src/ypms/auth/router.py:138`](../src/ypms/auth/router.py#L138)<br>[`src/ypms/auth/router.py:147`](../src/ypms/auth/router.py#L147) | Password-change notification (C.5.7.14) and admin reset arrive with notifications/admin. |
|
||||||
|
| **IA-5(1)** | 🟡 | PBKDF2-SHA256 (NIST SP 800-132) via passlib. | [`src/ypms/auth/security.py:30`](../src/ypms/auth/security.py#L30) | FIPS 140-3 validated module is deployment-dependent (YPMS-POAM-032). |
|
||||||
|
| **RA-5** | 🟡 | pip-audit on runtime dependencies gates CI; an unfixable `ecdsa` advisory was removed by changing JWT library. | [`.gitea/workflows/ci.yml:59`](../.gitea/workflows/ci.yml#L59) | Pending first green CI run; infrastructure scanning is deployment work. |
|
||||||
|
| **SA-11** | 🟡 | 167 automated tests, 80% coverage floor, ruff, strict mypy, bandit SAST, migration-drift test. | [`.gitea/workflows/ci.yml:53`](../.gitea/workflows/ci.yml#L53)<br>[`.gitea/workflows/ci.yml:56`](../.gitea/workflows/ci.yml#L56) | Pending first green CI run. |
|
||||||
|
| **SC-8** | ⚠️ | Not wired in Phase 0 (no local TLS proxy). | — | TLS terminates at Front Door / App Gateway at deployment (YPMS-POAM-031). |
|
||||||
|
| **SC-12** | 🟡 | Column key derived with HKDF-SHA256; ≥32-byte key material and ≥32-byte JWT secret enforced at startup. | [`src/ypms/security/column_crypto.py:49`](../src/ypms/security/column_crypto.py#L49)<br>[`src/ypms/settings.py:15`](../src/ypms/settings.py#L15) | Keys come from env vars; Key Vault (Gov/HSM) in Phase 1. |
|
||||||
|
| **SC-13** | 🟡 | FIPS-approved algorithms only: AES-256-GCM, HKDF-SHA256, HMAC-SHA256 (JWT), PBKDF2-SHA256. | [`src/ypms/security/column_crypto.py:55`](../src/ypms/security/column_crypto.py#L55) | Validated-module status is deployment-dependent (YPMS-POAM-032). |
|
||||||
|
| **SC-28** | 🟡 | SSN encrypted at the app layer (AES-256-GCM, column-bound AAD); write-only on the model; masked in every response. | [`src/ypms/security/column_crypto.py:55`](../src/ypms/security/column_crypto.py#L55)<br>[`src/ypms/youth/models.py:83`](../src/ypms/youth/models.py#L83) | Banking columns not yet built; Postgres TDE and blob encryption are deployment. |
|
||||||
|
| **SI-10** | ✅ | Pydantic validation on every request body; SSN format and never-issued ranges, DOB, ZIP, email, lengths. | [`src/ypms/youth/schemas.py:21`](../src/ypms/youth/schemas.py#L21) | — |
|
||||||
|
| **SI-11** | ✅ | Unhandled errors return a generic body with request id; logs record type and frames, not messages; SQL parameters hidden. | [`src/ypms/main.py:56`](../src/ypms/main.py#L56)<br>[`src/ypms/db.py:24`](../src/ypms/db.py#L24)<br>[`tests/test_app_guards.py`](../tests/test_app_guards.py) | — |
|
||||||
|
| **SI-12** | 🤝 | Every table carries a retention policy; app refuses to boot otherwise; nightly report-only job. | [`src/ypms/framework/retention.py:135`](../src/ypms/framework/retention.py#L135)<br>[`src/ypms/main.py:34`](../src/ypms/main.py#L34) | Disposal execution is Phase 5; periods await YPMS-POAM-054. |
|
||||||
|
|
||||||
|
## Verified end to end (2026-10-01)
|
||||||
|
|
||||||
|
Clean clone → fresh venv → `alembic upgrade head` → `python -m ypms.seed.run` → `uvicorn`, then over HTTP:
|
||||||
|
|
||||||
|
- `POST /auth/login` → JWT with `iam.assurance_level`; no token → 401.
|
||||||
|
- `POST /registrants` → 201, `ssn_masked: XXX-XX-6789`, no `ssn` field; `GET` → `pii_read` audit row; `PATCH` → version 2; `DELETE` → `deleted_at` set.
|
||||||
|
- `GET /guardians/5` → 501 with a `View` audit row.
|
||||||
|
- `audit_log`: `login`, `create`, `pii_read`, `update`, `delete`, `View` — every `ssn_encrypted` value `[REDACTED]`.
|
||||||
|
- `registrants.ssn_encrypted` holds `v1.` ciphertext only; the plaintext SSN appears nowhere in the DB or server log.
|
||||||
|
|
||||||
|
SQLite stood in for Postgres (no Docker on the verifying workstation). The Postgres run is still open.
|
||||||
Reference in new issue
Block a user