Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions docs/PHI.md
Original file line number Diff line number Diff line change
Expand Up @@ -598,12 +598,22 @@ start**, and refuses only if the estate opts in via `[security].require_memory_e
That turns the deployment requirement from prose into a declaration of record with a standing warning
where it is absent.

**The scorecard is unchanged, and this document does not pre-empt it.** ADR 0152 holds that rung 2 is
what would justify `Fail → Partial`, and the evidence for that now exists — but 11.7.1 is scored on the
ASVS L3 assessment of record (`ASVS-L3-ASSESSMENT-2026-07-22.md`), which still reads **Fail**,
and re-scoring it is an owner decision rather than a side effect of shipping a build. A **Pass** would
in any case require SEV-SNP/TDX hardware plus a verified CPU-signed quote (ADR 0152 rung 3, not built),
and even then the CPython-heap residual above is unchanged.
**This document does not pre-empt the scorecard.** 11.7.1 is scored **`na`**: the requirement's verb is
*"full memory encryption is in use"* — a property of the hosting substrate, which
[`ASVS-ASSESSMENT-METHOD.md`](ASVS-ASSESSMENT-METHOD.md) §2 places outside the assessed software, so
rule 1 takes it out of scope. Re-scoring remains an owner decision rather than a side effect of
shipping a build.

⚠️ **The record is the scorecard itself** — `docs/security/asvs-scorecard.toml`, rendered and CI-gated
([ADR 0156](adr/0156-asvs-scorecard-as-data-a-derived-count-verified-evidence-anchors-and-a-fail-closed-drift-gate.md))
— **never a prose assessment.** ADR 0156 replaced the dated-document lineage precisely because prose
asserts facts about code and the code moves; do not cite a dated assessment file as the verdict of
record. *(This paragraph previously did exactly that, naming a dated assessment and reporting a `Fail`
that the record no longer carried.)*

**An out-of-scope verdict buys nothing operationally**, which is the point of §2.1: the CPython-heap
residual above is unchanged either way, and a deployment still needs the host-side control. Rung 3
(SEV-SNP/TDX plus a verified CPU-signed quote) remains unbuilt.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Build state at acceptance:

| | |
|---|---|
| **Rung 1** — platform read-out, report-only | **Built.** `config/memory_encryption.py`; four `memory_encryption_self_reported_*` / `..._readout_source` fields on `GET /security/posture`, plus the in-body disclaimer `memory_encryption_note` (`ENGINE_UI_SEAM` 12 → 13). |
| **Rung 1** — platform read-out, report-only | **Built.** `config/memory_encryption.py`; **seven** `memory_encryption*` fields on `GET /security/posture` — three `_self_reported_*`, plus `_readout_source`, `_operator_declared`, `_readout_contradicts_declaration`, and the in-body disclaimer `memory_encryption_note`. *(Corrected 2026-08-02: this row said "four", counting only the read-out fields and omitting the declaration, contradiction and note fields; [`api/models.py`](../../messagefoundry/api/models.py) makes the same 3+1 slip in its own comment. On the seam: `ENGINE_UI_SEAM` moved 12 → 13 for this work — release history, carried correctly in `CHANGELOG.md` — but this row is present-tense "Built.", so read as current state it was wrong; the seam is **16** today.)* |
| **Rung 2** — operator declaration | **Built.** `[security].memory_encryption_operator_declared` (default `false`); an **exposed PHI** instance without it **warns at every start**, and refuses only when the estate opts in via `[security].require_memory_encryption_declaration` (default `false`) under `enforcement = enforce`. A contradiction is warned + reported as the tri-state `memory_encryption_readout_contradicts_declaration`, never refused. See the *2026-07-22 amendment* below for why the refusal is opt-in and why the field is not called "attested". |
| **Rung 3** — cryptographic attestation | **Not built.** No quote acquisition, no signature verification, no vendor-chain handling. |
| **Windows rung 3** | **Recorded infeasible in practice** — see *Windows rung 3 — spike conclusion* below. It is **platform-blocked, not API-blocked**. |
Expand Down Expand Up @@ -39,11 +39,18 @@ it because L3 is the stated target for a PHI system, not because the conventiona
**Memory hygiene is not memory encryption, and OWASP has already ruled on that distinction.** During
the 4.x→5.0 cull OWASP **deleted** 4.0.3's V8.3.6 (overwrite sensitive memory when no longer needed)
as *"NOT PRACTICAL"* — and **kept** 11.7.1. So the zeroization/locking family is explicitly not what
this requirement asks for. We already ship that family and it does not move this cell: `_secure_zero`
over a `bytearray` via `ctypes.memset` ([`store/crypto.py:163`](../../messagefoundry/store/crypto.py)),
`VirtualLock`/`mlock` residency ([`:185-199`](../../messagefoundry/store/crypto.py)), DEK zeroization
after install ([`:314-325`](../../messagefoundry/store/crypto.py)) and plaintext zeroization
([`:462`](../../messagefoundry/store/crypto.py)) all landed in #198.
this requirement asks for. We already ship that family and it does not move this cell — all of it in
[`store/crypto.py`](../../messagefoundry/store/crypto.py): `_secure_zero()` over a `bytearray` via
`ctypes.memset`, `VirtualLock`/`mlock` residency in `_lock_memory()` / `_unlock_memory()`, DEK
zeroization after install in `_install_key()`, and plaintext zeroization at the `_secure_zero(pt)`
calls on the encrypt and decrypt paths. All landed in #198.

*(Anchors corrected 2026-08-02, and the claim is unchanged — only the pointers were wrong. These were
line citations: `:163`, `:185-199`, `:314-325`, `:462`. Every one had rotted, and the first two had
come to point at **each other's** code — `:163` at prose inside `cell_aad()`'s docstring, `:185-199` at
the body of `_secure_zero` rather than the residency helpers. Re-anchored by **symbol**: these render as
markdown link text over a target carrying no line fragment, so the link always resolves and no link
checker can catch this class. New line numbers would rot the same way at the next refactor.)*

**CPython can protect keys but not message bodies, and that asymmetry is structural.** A DEK is small,
short-lived, and we own the buffer — hence the work above. An HL7 message is not: it is `str` end to
Expand Down
Loading