ADR-0040: Deep audit, validation, and adversarial-testing pass (2026-06-14)¶
Status¶
Accepted
Date¶
2026-06-14
Authors¶
praxis maintainers
Context¶
After the feature waves through ADR-0038 the backlog reached fully resolved
(103 items). This pass re-validates the security posture against the nine invariants
and the STPA constraints, with adversarial runtime testing, and brings the prose
documentation back in line with the code. It follows the 2026-06-12 pass (ADR-0017,
audit/00-inventory.md..03-final-report.md) and ran in parallel with the same-day
read-only refresh in ADR-0039 (#70, which refreshed audit/00..03 in place and filed
BL-104); this pass is the deeper one that additionally remediated code findings, and is
recorded separately under audit/2026-06-14/.
Method: five parallel read-only domain audits (execution core and invariants;
transport/SSRF/redaction/audit-integrity; store/actuation/collectors/drift;
documentation drift; STPA and governance traceability), plus hands-on adversarial
probing of the security-critical functions, plus the existing gates (make ci-success
green at 92% coverage; scripts/fuzz.py 20000 iterations, no violations).
Headline result: no critical or high findings. The nine invariants are each mechanically enforced with a passing test (re-confirmed); STPA traceability is complete (28/28 UCAs covered, 10/10 SEC constraints enforced in code, the compliance validator reports 0 violations across its 11 rules). The pass produced six code findings (all remediated in-pass with regression tests), three documented dispositions, and a set of documentation-drift corrections.
Decision¶
Record the findings and their dispositions. The detail is in
audit/2026-06-14/01-findings.md; the disposition summary is in 02-report.md.
Remediated in this pass (each with a regression test):
| ID | Sev | Area | Fix |
|---|---|---|---|
| F-001 | Medium | execution/audit.py |
_canonical used default=str, uncontained on a hostile __str__ and on a circular reference, breaking the invariant-3 "logger never raises" claim. Added _safe_str plus an acyclic fallback so record never raises on any input. |
| F-006 | Medium | execution/redaction.py |
Anthropic (sk-ant-), HuggingFace (hf_), and DigitalOcean (do[opr]_v1_) tokens leaked (the generic sk-/alnum patterns miss them). Added explicit value patterns. |
| F-007 | Medium | store/sqlite.py, store/postgres.py |
supersede did not check the UPDATE rowcount, so the loser of a concurrent supersede received a false success carrying the winner's actor/reason. Both backends now report a lost race as None (provenance fidelity, SEC-10). |
| F-004 | Low | execution/patterns.py |
The rm -rf / deny missed //, /*, and /. (caught at T3 but not by the global deny wall). Extended the pattern; bumped PATTERNS_VERSION 3 to 4. |
| F-008 | Low | collectors/talos.py |
The non-JSON status fallback stored unbounded attacker text. Capped at 4096 chars with a truncation marker (invariant 8). |
| F-003 | Low | actuation/opentofu.py |
OpenTofu passed an unconfined chdir as -chdir (dead code: chdir is not a RunActionArgs field). Removed the unconfined passthrough; safe re-add tracked as BL-105. |
Dispositioned as documented (no code behaviour change):
- F-002 (redaction is pattern-based, so an unkeyed high-entropy secret in no recognised
format is not detected): documented in
SECURITY.md. The load-bearing controls remain that output bodies are never logged and secret-named keys are always redacted. - F-005 (
SyslogAuditSinkdoes not SSRF-filterPRAXIS_AUDIT_SYSLOG_ADDRESS): intended. The syslog address is operator-trusted deploy configuration, not a model-influenced destination, and a local SIEM on a private address is the normal case. Documented in theSyslogAuditSinkdocstring,operate.md, and the ADR-0037 audit note. - F-009 (ADR-0015 lacked a ratification note): added, matching ADR-0024/0029.
Deferred hardening filed as backlog items: BL-105 (OpenTofu workspace selection via a
PRAXIS_TOFU_ROOT-confined chdir), BL-106 (timing-safe approval-token comparison
before any network-accessible submission path), BL-107 (a total-message-byte cap for a
multi-client transport), BL-108 (per-pair/per-value caps in CommandProbeCollector),
BL-109 (make compliance-controls.json proving-test lists exhaustive rather than
representative). BL-106 and BL-107 are prerequisites of the HTTP transport (BL-012).
Consequences¶
Positive: an invariant-3 robustness gap, a redaction-coverage gap (including Anthropic API keys, relevant for an MCP server), a supersede provenance race, a deny-wall miss, an untrusted-data storage gap, and a latent unconfined path are closed, each with a test. The documentation is current. The validated-solid posture is recorded for the next pass.
Negative: F-002 and F-005 are accepted, documented limitations rather than code changes. The deferred BL items remain open until the HTTP transport work schedules them.
Neutral: PATTERNS_VERSION is now 4, so audit records and approval bindings minted after
this change cite ruleset 4; the bump is the documented signal that the deny set changed.
Alternatives considered and rejected¶
- Apply the SSRF egress filter to the syslog sink (F-005). Rejected: it would block the normal LAN-SIEM case, and the destination is operator-trusted, not model-influenced.
- Entropy-based redaction (F-002). Rejected: high false-positive rate on legitimate high-entropy values; the no-bodies rule plus secret-key plus curated-shape redaction is the chosen balance.
- Narrow the invariant-3 docstring to "never raises on JSON-native args" instead of
fixing
_canonical(F-001). Rejected: invariant 3 is load-bearing; make it true by construction for any input. - Defer F-006 to a backlog item rather than fixing in-pass. Rejected: a leaking secret pattern (Anthropic keys) is cheap to close now.
Revisit triggers¶
- The HTTP transport (BL-012) is scheduled: BL-106 and BL-107 become prerequisites, and the approval-token comparison and message-byte cap must land first.
- A new provider token format appears in the field: extend the redaction value patterns (the BL-097 / F-006 set).
- The next periodic deep-audit pass.