From 53109af18c42443ba60da1bb603100db55d29d32 Mon Sep 17 00:00:00 2001 From: Ernest Provo Date: Sat, 13 Jun 2026 18:24:44 -0400 Subject: [PATCH] docs: sync 3-core docs to v1 state (schema_version 3, mcp-warden-cli, new modules, release infra) --- DOCUMENTATION_INDEX.md | 11 +++++++---- README.md | 2 +- SYSTEM_CONTEXT_DIAGRAM.md | 17 +++++++++++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/DOCUMENTATION_INDEX.md b/DOCUMENTATION_INDEX.md index 18f3b17..411c095 100644 --- a/DOCUMENTATION_INDEX.md +++ b/DOCUMENTATION_INDEX.md @@ -82,7 +82,7 @@ scope-honesty box and makes no compliance/regulatory claim. | [`docs/SPEC.md`](docs/SPEC.md) | **MCP Lock Format v1** — the vendor-neutral, self-contained format specification any tool can implement: on-disk `warden.lock` schema, RFC 8785 (JCS) canonicalization, SHA-256 `sha256:` hashing, `overall_digest` construction, the normative drift class + severity table, the optional per-tool inspection block, and a Conformance section + worked example. `WARDEN_LOCK_SCHEMA.md` is the mcp-warden implementation of this format | | [`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/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` 2, 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_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) | | [`docs/POLICY_MODEL.md`](docs/POLICY_MODEL.md) | Policy schema, the four high-risk shapes, constraint vocabulary, fail-closed defaults, SSRF deny ranges, lint + single-sample eval semantics. **Enforced at runtime by v0.2 `guard`** on live `tools/call` requests | @@ -112,8 +112,9 @@ scope-honesty box and makes no compliance/regulatory claim. | Doc | Purpose | |-----|---------| | [`RELEASING.md`](RELEASING.md) | Operator runbook: one-time PyPI Trusted-Publisher (OIDC) setup, cut-a-release checklist, post-release verification, rollback/yank. PyPI dist name is `mcp-warden-cli`; CLI/repo stay `mcp-warden`. | -| [`CHANGELOG.md`](CHANGELOG.md) | Keep-a-Changelog history (0.3.0 → 1.0.0) with explicit in/out-of-scope. *(Added by the CHANGELOG PR.)* | -| [`.github/workflows/release.yml`](.github/workflows/release.yml) | Publish-on-Release workflow: build sdist+wheel → publish to PyPI via OIDC Trusted Publishing (no stored token) → Sigstore-keyless sign the artifacts and attach bundles to the Release. Inert until a Release is published AND the `mcp-warden-cli` Trusted Publisher exists. | +| [`CHANGELOG.md`](CHANGELOG.md) | Keep-a-Changelog history (0.3.0 → 1.0.0 → 1.0.1) with explicit in/out-of-scope. | +| [`.github/workflows/release.yml`](.github/workflows/release.yml) | Publish-on-Release workflow: build sdist+wheel → publish to PyPI via OIDC Trusted Publishing (no stored token, **gated on repo var `PYPI_TRUSTED_PUBLISHER=true`** + `skip-existing`, #64) → Sigstore-keyless sign the artifacts and attach bundles to the Release. Live: `mcp-warden-cli` Trusted Publisher configured + the gate variable set. | +| [`requirements-dev.lock`](requirements-dev.lock) · [`.github/workflows/deps-locked.yml`](.github/workflows/deps-locked.yml) | **(#59)** Hash-pinned dev/CI dependency lock + the "Hash-locked dev/CI install" check (verifies `--require-hashes` install + that the lock stays in sync with `pyproject.toml` without floating to latest, #65). Dependency-update policy lives in [`SECURITY.md`](SECURITY.md). | --- @@ -141,11 +142,13 @@ scope-honesty box and makes no compliance/regulatory claim. | `src/mcp_warden/policy_eval.py` | Single-sample eval + runtime arg eval (fs/shell/http-SSRF/sql) | POLICY_MODEL §2, §4.2, §5 | | `src/mcp_warden/result_inspection.py` | **(v0.2)** `WRD-RES-*` catalog public entry (`inspect_result`, `ResultFinding`, `InspectionPolicy`) — single source run by guard + inspect | RESULT_INSPECTION §1–§6, §8 | | `src/mcp_warden/res_catalog.py` | **(v0.2)** per-rule evaluators + content-block text extraction | RESULT_INSPECTION §1, §3–§5 | -| `src/mcp_warden/res_rules.py` · `res_net.py` | **(v0.2)** deterministic primitives: ANSI codepoint scan + inject-phrase normalize (`res_rules`), exfil/URL host matching + seed denylists (`res_net`) | RESULT_INSPECTION §3.1, §3.3, §4.1, §5.1 | +| `src/mcp_warden/res_rules.py` · `res_net.py` | **(v0.2)** deterministic primitives: ANSI codepoint scan + inject-phrase normalize (`res_rules`), exfil/URL host matching + seed denylists + **(#54, D6)** raw-IP-literal matching in result text vs SSRF/exfil ranges — `WRD-RES-EXFIL-IP-LITERAL`, no DNS (`res_net`) | RESULT_INSPECTION §3.1, §3.3, §3.4, §4.1, §5.1 | +| `src/mcp_warden/net_rules.py` | SSRF deny CIDRs (loopback/RFC1918/link-local/IPv6) + host parse — used by `policy_eval` (http-request SSRF) and the #54 raw-IP-literal matcher | POLICY_MODEL §2.3, §5 | | `src/mcp_warden/guard.py` | **(v0.2/v0.3)** proxy runner: subprocess lifecycle, own pgrp, signal forwarding, single-loop byte pumps, teardown-path decision (client-EOF vs child-exit) | GUARD_PROXY §1, §2.3, §2.6 · V3 §2 | | `src/mcp_warden/guard_loop.py` · `guard_result.py` | **(v0.2/v0.3)** frame discipline: v0.3 default-block `GuardConfig` + c2s arg-policy + cancel/progress passthrough (`guard_loop`), s2c result inspection + `tools/list_changed` gate + on-wire block decision (`guard_result`) | GUARD_PROXY §2, §4, §7, §9 · V3 §1, §4 | | `src/mcp_warden/guard_lifecycle.py` | **(v0.3)** lifecycle teardown: `-32002` pending-id synthesis, `128+signum` exit mapping, POSIX process-group TERM→grace→KILL teardown, Windows best-effort + `WRD-RES-WIN-LIFECYCLE`; **(#21)** `-32003` `synthesize_strict_abort` | GUARD_PROXY_V3 §2, §3, §5 | | `src/mcp_warden/guard_io.py` | **(v0.3)** async stdio adapters (`wrap_recv`/`wrap_send`) bridging blocking stdio to the single-loop framer | GUARD_PROXY §2.3 | +| `src/mcp_warden/guard_banner.py` | **(#57)** startup posture banner: renders the resolved BLOCKING / MONITOR-ONLY / INACTIVE enforcement state to stderr (derived from `BLOCK_RULES` + `GuardConfig`, no server identity), so a naive `guard` run can't be misread as fully protected. Paired with `cli_guard`'s non-POSIX degraded-platform refusal (exit 2 unless `--allow-degraded-platform`) | GUARD_PROXY_V3 §3 | | `src/mcp_warden/guard_list_gate.py` | **(v0.3)** runtime `tools/list` drift gate: hashes the live list vs the lock (reuses `hashing`), fail-open on malformed payloads; **(#21)** `strict=` re-raises the nested hash error instead of swallowing it | GUARD_PROXY §4.3, §7.3 · V3 §5 | | `src/mcp_warden/framing.py` | **(v0.2)** single-reader stdio framer (Content-Length + newline), original-bytes pass-through, truncation-at-EOF capture | GUARD_PROXY §2.4, §2.5 · V3 §2.3 | | `src/mcp_warden/wire_block.py` | **(v0.2/v0.3)** on-wire block synthesis: `-32001` error-response + redacted-content (`_meta.warden.modified`); **v0.3** `-32002` `transport_error`; **(#21)** `-32003` `strict_abort_error` (non-retriable) | GUARD_PROXY §7 · V3 §2.6, §5 | diff --git a/README.md b/README.md index 5debe9f..4ce5d8b 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ and a **mutated** (rug-pulled) one. The end-to-end flow: change, server-identity change). Tool `inputSchema` changes are **structurally diffed**: each security-relevant mutation is reported per-fact and deterministically classified by severity (`docs/WARDEN_LOCK_SCHEMA.md` §6.2). A normalized schema -skeleton is stored in the lock (`schema_version` 2); pre-skeleton (v1) locks fall +skeleton is stored in the lock (`schema_version` 3); pre-skeleton (v1) locks fall back to a single high-severity `schema-modified` until re-pinned. The SARIF report (`ruleId` == the `WRD-*` / `WRD-DRIFT-*` check ID) uploads straight to GitHub code scanning. diff --git a/SYSTEM_CONTEXT_DIAGRAM.md b/SYSTEM_CONTEXT_DIAGRAM.md index 4d347b0..9dda24d 100644 --- a/SYSTEM_CONTEXT_DIAGRAM.md +++ b/SYSTEM_CONTEXT_DIAGRAM.md @@ -35,6 +35,13 @@ logic) plus a separate informational provenance section. It never prints raw > warning) and fail-closed under `--strict` (exit 2) — CI stays strict. The wrapper normalizes > cwd to the git repo root before capturing. See `README.md` §pre-commit hook. +> **`pin --sign` / `check --verify` (Issue #16)** add optional **Sigstore keyless** signing of +> the lock: `--sign` writes a `.sigstore` bundle binding `overall_digest` (survives +> `lock rotate`); `--verify` recomputes + verifies **fail-closed** (`docs/SIGNING.md`). The +> release pipeline (`.github/workflows/release.yml`) signs mcp-warden's OWN published artifacts +> the same way ("heal thyself"). Signing is the optional `mcp-warden-cli[sigstore]` extra — the +> core gate has no crypto dependency. + --- ## C1 — System context @@ -110,8 +117,9 @@ sequenceDiagram ``` > `compute_drift` structurally classifies tool `inputSchema` changes via the normalized -> `schema_skeleton` stored in the lock (`schema_version` 2): each security-relevant mutation -> is a per-fact `WRD-DRIFT-SCHEMA-*` item (`docs/WARDEN_LOCK_SCHEMA.md` §6.2). v1 locks fall +> `schema_skeleton` stored in the lock (`schema_version` 3 — skeleton added at v2, in-document +> `$ref` resolution at v3, #29): each security-relevant mutation is a per-fact +> `WRD-DRIFT-SCHEMA-*` item (`docs/WARDEN_LOCK_SCHEMA.md` §6.2). v1 locks fall > back to a single high-severity `schema-modified` until re-pinned. --- @@ -185,8 +193,9 @@ flowchart LR - No cross-call/conversational correlation; each frame is inspected independently. - No decoding of image/audio/blob/base64 result content (coverage gap recorded as `WRD-RES-UNINSPECTABLE`). -- No network calls / no DNS resolution by checks, policy, or the proxy — exfil + SSRF - match on literal host strings only. +- No network calls / no DNS resolution by checks, policy, or the proxy. Exfil + SSRF match + on literal host strings **and (#54, D6) raw IP literals** in result text/args against the + SSRF/exfil address ranges (`net_rules`) — deterministic, still no DNS-name resolution. - stdio transport only (HTTP/SSE deferred for all of v0.1/v0.2/v0.3). - The fuzzy `WRD-RES-INJECT-PHRASE` MONITOR tier is **never default-block**, even in v0.3 (opt-in only via `--block-inject-phrase`).