# ADR-038: Security Governance Automation Control Plane

- **Status**: Accepted
- **Date**: 2026-07-10
- **Decider**: Product owner
- **Supersedes**: generic owner/manual/HITL terminal gates in ADR-012 and ADR-018 for low/medium/high risk work
- **Related**: ADR-011, ADR-012, ADR-013, ADR-018, ADR-020, ADR-031, ADR-032, ADR-033

## Context

EwoooC has many AI automation receipts, smoke families and guarded preview stages, but the program still cannot prove a complete information-security and governance loop. Production review found four structural gaps:

1. Sensitive routes evolved blueprint by blueprint instead of behind one deny-by-default policy.
2. Asset, identity, dependency, backup and AI runtime evidence are not reconciled by one control plane.
3. Preview/review stages do not share one `trace_id/run_id/work_item_id` through execution, verifier, rollback and learning.
4. Source/test/UI evidence has repeatedly been reported ahead of production runtime closure.

## Decision

### 1. Control model

Use NIST CSF 2.0 as the program structure:

- GOVERN: policy, ownership, risk, supply chain and measurable targets.
- IDENTIFY: canonical asset graph, dependency inventory and risk assessment.
- PROTECT: identity, authorization, secure defaults, data and platform controls.
- DETECT: fresh logs, metrics, traces, anomaly signals and control drift.
- RESPOND: correlated incident, bounded action, verifier and communication receipts.
- RECOVER: rollback/retry, backup/restore, RPO/RTO and learning writeback.

OWASP ASVS 5.0.0 L2 is the application baseline, with selected L3 controls for operator, webhook, AI execution and admin surfaces. NIST SSDF SP 800-218 v1.1 is the software-supply-chain baseline.

### 2. Source of truth

Completion evidence is ordered:

1. production runtime receipt, independent post-verifier and durable DB receipt;
2. Gitea main/deploy marker/CD run/production readback;
3. committed machine-readable inventory, policy and tests;
4. docs and snapshots;
5. conversation.

Lower evidence cannot promote a higher incomplete layer to completed.

### 3. Controlled apply

Low, medium and high risk work defaults to controlled apply with blast-radius controls. Only critical break-glass classes remain externally gated: plaintext secrets, destructive database/storage operations, host/network cutovers, credentialed offensive scans, paid provider or core runtime replacement without replay/shadow/canary, destructive Git operations and raw secret-volume access.

Every side-effect run must retain one `trace_id/run_id/work_item_id` across:

`sensor -> normalize -> correlate -> decide -> risk/check -> bounded execute -> independent verify -> retry/rollback -> learn/writeback`

Any missing stage produces `partial` or `degraded`, never completed.

### 4. Machine-readable controls

- `governance/ewoooc_asset_inventory.json` is the canonical asset seed and must be reconciled against runtime receipts.
- `services/http_access_policy_service.py` is the central policy for sensitive HTTP surfaces.
- `services/security_governance_review_service.py` produces NIST/OWASP-aligned findings, completion scores and ordered work items.
- `scripts/ops/report_security_governance_review.py --strict` is the source release gate.
- `/api/ai-automation/security-governance-review` is the protected production readback.

### 4.1 Database identity controlled cutover

- `services/auth_identity_service.py` is the single policy for auth mode, trusted proxy CIDRs, role tiers and session identity versioning.
- `login_history` is the durable login/audit/lockout source; process-local counters are not authoritative.
- `auto` starts with bounded hybrid authority so the current operator is not locked out. Only successful database authentication by an active admin counts toward promotion; two durable success receipts are required by default.
- Once the threshold is reached, effective authority becomes database-only without a manual review gate. Legacy shared sessions are revoked on their next request and cannot inherit an admin role from missing fields.
- Any password, role, active-state or identity timestamp change invalidates the signed session through its durable identity version.
- Account creation, identity updates, password changes, account disablement and permission changes must commit their no-secret `login_history` audit event in the same database transaction; failures roll back both the mutation and its audit.
- `scripts/ops/report_auth_identity_governance.py` returns counts and control state only; it must never return password hashes, secret values or raw sessions.

### 5. Legacy policy sunset

| Legacy policy | Owner | Expiry | Replacement | Verifier |
|---|---|---|---|---|
| Generic HITL/manual terminal for low/medium/high | product-governance | 2026-07-10 | risk-based controlled apply in ADR-038 | governance review work-item state |
| Shared password treated as complete RBAC | security | 2026-07-31 | database identity + least-privilege role migration | anonymous/role route matrix |
| `actions/checkout@v4` in Gitea CD | delivery | 2026-07-10 | Gitea job-token source checkout | Gitea-only supply-chain gate |
| Backup script treated as recovery completion | resilience | 2026-07-31 | checksum/offsite/isolated restore receipt | RPO/RTO restore verifier |
| Registry or preview readiness treated as runtime completion | AI platform | 2026-07-17 | MCP/RAG live health and internal RAG canary | production smoke/readback |

## Alternatives Considered

- Keep adding domain-specific receipt stages: rejected because it increases surface area without unifying assurance.
- Make every action human-approved: rejected for low/medium/high because it defeats the product's AI automation goal.
- Make every action fully autonomous without independent verification: rejected because it cannot bound production risk.
- Adopt an external governance platform as the source of truth: rejected until internal asset and receipt contracts are stable and exportable.

## Consequences

Positive:

- Security and AI automation use the same measurable closure contract.
- Drift becomes a work item instead of an undocumented manual observation.
- Release gating can block critical source regressions without pretending all runtime debt is complete.
- Product, asset and runtime completion remain separate and honest.

Costs and risks:

- Existing routes and automation lanes require gradual migration.
- Shared identity, webhook secret activation, non-root containers and restore drills need canary rollout.
- The initial asset inventory has complete required fields but runtime reconciliation remains partial.

## Verification

1. `python3 scripts/ops/report_security_governance_review.py --strict` returns exit 0.
2. Anonymous requests to sensitive read/operator surfaces return 401 or login redirect.
3. Gitea workflow contains no external `uses:` action or forbidden GitHub source.
4. Production endpoint reports zero release-blocking source failures.
5. Program, asset and runtime closure percentages are reported separately.
