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
6 changes: 5 additions & 1 deletion DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ scope-honesty box and makes no compliance/regulatory claim.
| [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) | **(v0.1)** Positioning, trust model (TOFU + `--approve`), assets/actors, the four threat classes (MCP-DRIFT / MCP-CAPSURF / MCP-SECRET / MCP-SUPPLY), explicit out-of-scope limits, deliberate cuts |
| [`docs/THREAT_MODEL_V2.md`](docs/THREAT_MODEL_V2.md) | **(v0.2)** Addendum extending the v0.1 model: T-RESULT vectors, the defends (BLOCK) / monitors (fuzzy) / still-does-NOT-defend (T-BEHAVE) table, runtime trust-model notes, retained + added cuts, shadow-default positioning |
| [`docs/AGENT_TRUST_KERNEL.md`](docs/AGENT_TRUST_KERNEL.md) | **(DSE-714, design contract)** Normative invariants for the future deterministic Agent Trust Kernel: trust boundaries, complete mediation, default deny, non-overridable critical classes, evidence-before-effect, offline operation, residual risks, and bindings for DSE-715 through DSE-717. MCP-Warden v1.1 is explicitly not yet ATK-conformant |
| [`docs/CONTENT_ENVELOPE.md`](docs/CONTENT_ENVELOPE.md) | **(DSE-715, implemented foundation)** Strict immutable V1 content envelope, domain-separated exact-byte digests, canonical metadata boundary, bounded one-hop lineage, monotonic taint, stable code-only errors, and secret-safe public projection. Evidence only; no authority or whole-ATK conformance claim |
| [`docs/WARDEN_LOCK_SCHEMA.md`](docs/WARDEN_LOCK_SCHEMA.md) | **mcp-warden implementation of [`docs/SPEC.md`](docs/SPEC.md) (MCP Lock Format v1).** `warden.lock` format, RFC 8785 canonicalization + SHA-256 hashing, field/entry/overall digests, the normative drift definition + severities; **§5.1/§6.2 structural schema diff** (normalized per-tool `schema_skeleton`, `schema_version` 3 — skeleton added at v2, in-document `$ref` resolution at v3 (#29), granular `WRD-DRIFT-SCHEMA-*` taxonomy + severities, v1 fallback); **§8.1/§8.2 (v0.3, #19)** structured out-of-digest provenance (`pinner` / `attestations` / `rotation_count`, `PROVENANCE_VERSION`, B4 `bound_digest` format) + `lock rotate` digest-invariant semantics + the #16 signing implication; **§11 (v0.2)** optional per-tool inspection policy (`expected_output_charset` / `may_return_urls` / `secret_echo_applies`, fail-safe defaults, digest impact) |
| [`docs/WARDEN_LOCK_EXAMPLE.md`](docs/WARDEN_LOCK_EXAMPLE.md) | Illustrative full `warden.lock` + a post-`lock rotate` `pin` block (archived from WARDEN_LOCK_SCHEMA §9 to keep that core doc under the line cap) |
| [`docs/CHECKS.md`](docs/CHECKS.md) | The deterministic `WRD-*` static-check catalog (capability/secret/supply/robustness), the shared tokenizer, severity→SARIF mapping, redaction rule, CUT list. **Reused by v0.2** `WRD-RES-SECRET-ECHO` (the `WRD-SEC-*` patterns + redaction) |
Expand All @@ -97,6 +98,7 @@ scope-honesty box and makes no compliance/regulatory claim.
| Plan | Purpose |
|---|---|
| [`docs/plans/2026-07-18-agent-trust-kernel-design.md`](docs/plans/2026-07-18-agent-trust-kernel-design.md) | **Non-normative execution record.** Records the DSE-714 design decision and verification plan; binding requirements live in `docs/AGENT_TRUST_KERNEL.md` |
| [`docs/plans/2026-07-18-content-envelope-design.md`](docs/plans/2026-07-18-content-envelope-design.md) | **Non-normative DSE-715 execution record.** Records the reviewed strict-TDD plan; verified behavior is documented in `docs/CONTENT_ENVELOPE.md` |

---

Expand Down Expand Up @@ -130,6 +132,7 @@ scope-honesty box and makes no compliance/regulatory claim.
| Module | Responsibility | Spec anchor |
|--------|----------------|-------------|
| `src/mcp_warden/hashing.py` | `canon()` (RFC 8785) + `hash()` + field hashes | WARDEN_LOCK_SCHEMA §3 |
| `src/mcp_warden/content_models.py` · `content_envelope.py` | **(DSE-715)** Strict immutable evidence models; typed domain-separated byte hashing call surface; canonical constructors/parser; bounded derivation and one-hop lineage; explicit secret-safe projection | CONTENT_ENVELOPE.md / AGENT_TRUST_KERNEL ATK-01/02/05/06/09/12 |
| `src/mcp_warden/tokenizer.py` | Shared tokenizer + capability derivation (single source of truth) | CHECKS §3 / WARDEN_LOCK_SCHEMA §5.4 |
| `src/mcp_warden/capture.py` | Declared-surface capture over stdio (argv array, no shell) or Streamable HTTP (`--url`); shared list normalization, timeouts, and errors | THREAT_MODEL §3.3 / WARDEN_LOCK_SCHEMA §4.1 |
| `src/mcp_warden/models.py` | Pydantic models for captured surface + lock (incl. `Pinner`/`Attestation` provenance) | WARDEN_LOCK_SCHEMA §2–§8 |
Expand Down Expand Up @@ -169,6 +172,7 @@ scope-honesty box and makes no compliance/regulatory claim.
| File | Covers |
|------|--------|
| `tests/test_hashing.py` | JCS+SHA-256 reproducibility, canonical-form pins, null handling |
| `tests/test_content_envelope.py` | **(DSE-715)** Strict/frozen models, typed digest domains, nine ingress kinds, canonical/cap boundary, deterministic lineage, parser rejection matrix, atomic digest verification, and planted-secret output/error/log scans |
| `tests/test_tokenizer.py` | Segment-exact tokenization + capability derivation |
| `tests/test_checks.py` | Capability/secret/supply/robustness checks + redaction |
| `tests/test_drift.py` | Drift per class (added/removed/modified/server-identity/unapproved) |
Expand All @@ -188,7 +192,7 @@ scope-honesty box and makes no compliance/regulatory claim.
| `tests/test_guard_v3.py` | **(v0.3)** opt-out demotes to shadow (`--no-block-*`/`--allow-exfil-domain`/`--no-block-deterministic`), `tools/list_changed` gate block+shadow, policy deny block+shadow, audit-only override, cancel/progress passthrough, **server-crash → `-32002` for every pending id**, client-disconnect child reap (no orphan), truncated + oversized frame fail-open |
| `tests/test_guard_strict.py` | **(#21)** `--strict` fail-CLOSED: 4 terminate sites (request-policy / result-inspect / list-gate / nested-hash re-raise) → exit `3` + one `strict_abort` stderr line + `-32003` client frame + child reaped; negatives (truncated/over-cap/unparseable/clean) do NOT abort; default `--no-strict` byte-identical fail-open regression; secret-leak redaction; CLI threading; double-emission single line; `StrictInspectionAbort` is `BaseException`-not-`Exception` + anyio `ExceptionGroup` unwrap |
| `tests/test_inspect_parity.py` | **(v0.2)** guard↔inspect finding parity on the same recorded frames + inspect exit codes |
| `tests/fuzz/` (`test_fuzz_framing.py` · `test_fuzz_ansi.py` · `test_fuzz_domain.py` · `test_fuzz_redact.py`) | **(#17)** `hypothesis` property-fuzzing of the live runtime attack surface: framer XOR/never-raise + mode-equivalence + truncation→parse_error + `_parse_content_length` never-negative (Finding A fix) + read_frame never-hang + Content-Length composition consistency; ANSI construction-based liveness/soundness + completeness + idempotence + strip∘redact order-independence; exfil-domain soundness (no invented hits) + URL/bare-host liveness + anchoring/IDN/trailing-dot; redactor format-structure + leak-bound + #38 short-secret contract. `ci`/`fuzz` profiles in `tests/fuzz/conftest.py`; deep soak via `make fuzz` |
| `tests/fuzz/` (`test_fuzz_framing.py` · `test_fuzz_ansi.py` · `test_fuzz_domain.py` · `test_fuzz_redact.py` · `test_fuzz_content_envelope.py`) | **(#17 / DSE-715)** `hypothesis` property-fuzzing of the live runtime attack surface plus content-envelope repeatability, round-trip, parent permutation, taint monotonicity, mutation rejection, malformed-byte bounded termination, flat-cycle non-recursion, registry drift, and planted-secret absence. `ci`/`fuzz` profiles in `tests/fuzz/conftest.py`; deep soak via `make fuzz` |
| `tests/fixtures/clean_server.py` · `mutated_server.py` | Real MCP SDK stdio fixtures |
| `tests/fixtures/poison_server.py` | **(v0.2)** result-poisoning fixture server (ANSI/secret-echo/exfil/inject/clean tools) |
| `tests/fixtures/crash_server.py` · `listchange_server.py` · `clean_listchange.warden.lock` | **(v0.3)** raw-stdio lifecycle fixtures: crash-mid-call (`-32002`) and `tools/list_changed` rug-pull + its pinned clean lock |
Expand Down
18 changes: 10 additions & 8 deletions SYSTEM_CONTEXT_DIAGRAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ integrity drift between two EXISTING locks by reusing `compute_drift` (no captur
logic) plus a separate informational provenance section. It never prints raw
`server.command`/`args` (secret-safe); default exit 0, `--exit-code` → 1 on integrity drift only.

> **Agent Trust Kernel status (DSE-714):** [`docs/AGENT_TRUST_KERNEL.md`](docs/AGENT_TRUST_KERNEL.md)
> is the normative contract for a future deterministic reference monitor. It is **design-only**
> until DSE-715 through DSE-717 implement provenance/taint, PDP/PEP complete mediation, and
> signed evidence-before-effect. The current `guard` path is not represented as ATK-conformant.
> In C1, the dashed ATK edge and `Future Agent Trust Kernel runtime` node are conceptual
> documentation scope only—not a deployed component, shipped data flow, or implementation claim.
> **Agent Trust Kernel status:** [`docs/AGENT_TRUST_KERNEL.md`](docs/AGENT_TRUST_KERNEL.md)
> is the normative contract. DSE-715 implements the isolated content-envelope foundation
> (untrusted ingress, deterministic lineage, monotonic taint, secret-safe projection), but it
> is not wired into `guard` and grants no authority. DSE-716/717 must still deliver PDP/PEP
> complete mediation and signed evidence-before-effect. The current `guard` path is not
> represented as ATK-conformant.

> `conclave` (the 4-model adversarial council referenced in `docs/THREAT_MODEL.md`)
> is a **dev-time design reviewer** that shaped this contract. It is **NOT** a
Expand Down Expand Up @@ -65,8 +65,10 @@ flowchart TB
conclave -. "security review" .-> atk
end

future["Future Agent Trust Kernel runtime\nDSE-715–717 · NOT IMPLEMENTED"]
atk -. "governs future conformance" .-> future
envelope["Content Envelope V1\nDSE-715 · implemented evidence foundation\nNOT wired to guard · grants no authority"]
future["Future PDP/PEP + signed evidence\nDSE-716–717 · NOT IMPLEMENTED"]
atk -. "governs partial foundation" .-> envelope
envelope -. "required input" .-> future

subgraph ci["CI pipeline (GitHub Actions / local)"]
warden["mcp-warden CLI\npin · check · policy · lock rotate · diff"]
Expand Down
17 changes: 15 additions & 2 deletions docs/AGENT_TRUST_KERNEL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Agent Trust Kernel — Normative Security Contract

**Status:** Design contract (DSE-714). Implementation conformance is pending DSE-715,
DSE-716, and DSE-717.
**Status:** Design contract (DSE-714). DSE-715 implements the isolated content-envelope
foundation; whole-kernel conformance remains pending DSE-716 and DSE-717.
**Scope:** Deterministic reference monitor beneath agents and protocol adapters.
**Document owner:** Security. Changes require threat-model review.

Expand Down Expand Up @@ -427,6 +427,19 @@ evidence-before-effect path. DSE-717 MUST NOT be considered complete until DSE-7
mediation conformance gates pass. No downstream ticket may weaken an invariant silently. A
proposed exception requires a security-contract change and review before implementation.

### DSE-715 implementation status

`ContentEnvelopeV1` now implements and tests the DSE-715 portions of ATK-01, ATK-02, ATK-05,
ATK-06, ATK-09, and ATK-12: literal untrusted ingress, immutable deterministic lineage,
monotonic registered taint, strict bounded canonical parsing, domain-separated digests, no
authority-bearing field/API, and digest-only secret-safe public output. Its lineage verifier is
intentionally bounded and one-hop. It does not verify source truth, artifact authenticity,
transitive ancestry, policy, authorization, mediation, or evidence-before-effect.

This partial implementation does not change the current-product boundary: no shipped runtime
may claim ATK conformance until DSE-716/717 integrate the envelope through the complete
mediation and durable evidence paths and every conformance gate below passes.

## 11. Conformance gates

A component or adapter may claim ATK conformance only through a mechanically executable suite.
Expand Down
97 changes: 97 additions & 0 deletions docs/CONTENT_ENVELOPE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Deterministic Content Envelope V1

**Status:** Implemented foundation (DSE-715). This is evidence plumbing, not an authority or
whole Agent Trust Kernel conformance claim.

`ContentEnvelopeV1` records exact content bytes as a domain-separated SHA-256 digest plus a
strict, immutable, RFC 8785 wire object. Every envelope is explicitly `untrusted`; every taint
set contains `core:untrusted`. The API has no operation or field that grants authority, marks
content trusted, or removes taint.

## Implemented behavior

- Nine ingress kinds cover tool results, web, documents, email, database text, MCP prompts,
MCP resources, bundle metadata, and agent messages. Derived envelopes use the code-owned
`derived` source kind.
- Opaque content and signature evidence are hashed as exact bytes. Structured source,
transform, and bundle metadata must arrive as exact canonical RFC 8785 bytes; malformed,
duplicate-key, noncanonical, non-finite, deep, or oversized inputs reject before use.
Every byte API requires the exact built-in `bytes` type; mutable buffers and subclasses
reject rather than silently widening the byte contract.
- Every digest is `sha256(domain + NUL + payload)` using the closed `DigestDomain` enum.
- Transform identity is code-owned. Callers choose an exact `TransformKindV1` enum, not raw
ID/version text: ingress accepts only `INGRESS_CAPTURE`, derivation accepts only
`DETERMINISTIC`, and an immutable append-only V1 registry supplies the serialized
`(id, version)` pair. An exact reverse registry binds every ingress source context to the
capture pair and every derived source context to the deterministic pair in the model and
parser—not only in constructors. Self-coherent pair swaps reject as `ENV-MALFORMED` for
roots and `ENV-LINEAGE-INVALID` for derived envelopes. Adding a registry entry is a
security-sensitive change requiring a new enum member, updated model/parser context
enforcement, and explicit cross-pair and wire-forgery tests. The implementation digest
binds the exact executing bytes to the identity; deciding whether those bytes are
pre-approved belongs to DSE-716.
- Parent references are sorted immutable `{envelope_digest, content_digest}` pairs. Derivation
recomputes every parent self-digest before accepting its refs or taints, then preserves the
complete bounded local taint union. Lineage verification is bounded and one-hop; it does not
claim transitive graph verification. Parent and taint inputs are consumed through fixed
`cap + 1` loops, bounding consumed elements and preventing unbounded logical materialization
from infinite iterables that continue yielding. The loop cannot prevent a blocking
synchronous `__next__` call from stalling its calling thread; deadline enforcement is the
caller's responsibility and is outside this boundary.
- Derived source identity and claims are recomputed from the exact sorted parent refs and
transform evidence during parsing and lineage verification; self-coherent caller-forged
source digests reject. Model-level invariants also enforce the source-context/transform-pair
binding and prevent roots with parents, derived envelopes without parents, and
bundle-metadata roots without bundle evidence.
- Every accepting public boundary completely revalidates the top-level envelope and each
nested frozen model. Pydantic instance revalidation plus explicit field-specific checks
prevent `model_copy(update=...)` from bypassing digest, media, source-kind, transform,
parent-order, taint-order, or bundle invariants. A storage-level required-field/type
preflight also rejects incomplete `model_construct(...)` objects before any field is
dereferenced or any parent digest is recomputed. Hostile-subclass rejection is enforced at
every current public accepting boundary through an exact-type check before attribute access;
this test-enforced convention is required for every future public accepting boundary.
- Parsing rejects unknown fields/versions, missing `bundle`, noncanonical bytes, invalid trust
or taint state, malformed lineage, and self-digest mismatch. Errors expose a stable `ENV-*`
code only and retain no third-party exception context.
- Public output explicitly selects digest-only V1 fields. Raw content, source claims,
implementation metadata, parameters, signatures, and bundle policy claims are discarded
after hashing and are never serialized. Pydantic validation is configured to hide input
values, including when callers instantiate strict public models directly.
- Supported direct model boundaries—construction, `model_validate`, `model_validate_json`,
`model_validate_strings`, and frozen assignment/deletion—convert validation failures to
code-only `ContentEnvelopeError` values. Pydantic `TypeAdapter`, private
`__pydantic_validator__`, core-schema APIs, and unsafe `model_construct` are not validation
boundaries and MUST NOT receive untrusted input. Public envelope APIs still reject a
constructed or copied invalid model before use. Secret-safety and stable-error guarantees
apply at these documented boundaries, not to unsupported Pydantic introspection paths.
- The legacy generic `canon()` logger is excluded from this subsystem. An AST import guard
forbids importing the `mcp_warden.hashing` module as a dynamic attribute handle and permits
only the named `hash_bytes` import, keeping raw ingress metadata off legacy canonicalization,
hashing, and logging paths.

Hard caps are 16 MiB content, 64 KiB envelope, 16 JSON levels, 4,096 JSON nodes, 64 parents,
32 taints, and 256 bundle dependencies. Component-specific caps are defined in
`content_models.py`. The pre-parser depth scan handles JSON string quoting/escaping without
allocation proportional to nesting and rejects over-depth inputs before `json.loads`.
Bundle input requires exact bytes and an exact tuple of byte dependencies at runtime; invalid
shapes fail with a stable bundle code before iteration or hashing.
Regression tests exercise exact-cap acceptance and cap-plus-one rejection for every structured
component, bundle component/signature/dependency, JSON depth/nodes/keys/string bytes, envelope
bytes, parent count, taint input count, and dependency count.

## API boundary

The pure API is `create_ingress`, `derive_envelope`, `serialize_envelope`, `parse_envelope`,
`verify_envelope`, `verify_lineage`, `to_public_dict`, and `to_public_bytes`. It performs no
I/O, network, clock, environment, policy decision, or effect. Bundle fields are descriptive
evidence only; DSE-716 must verify artifacts and enforce the PDP/PEP load gate. DSE-717 must
provide durable signed receipts and evidence-before-effect.

## ATK coverage

Tests provide the DSE-715 foundation for ATK-01, ATK-02, and the envelope portions of ATK-05,
ATK-06, ATK-09, and ATK-12: untrusted state, monotonic taint, no authority fields, strict
fail-closed parsing, canonical explicit inputs, and secret-safe output. MCP-Warden remains
nonconformant with the whole ATK contract until DSE-716 and DSE-717 land and the complete
conformance suite passes.
Loading
Loading