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
11 changes: 7 additions & 4 deletions DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<hex>` 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 |
Expand Down Expand Up @@ -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). |

---

Expand Down Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 13 additions & 4 deletions SYSTEM_CONTEXT_DIAGRAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<lockname>.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
Expand Down Expand Up @@ -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.

---
Expand Down Expand Up @@ -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`).
Expand Down
Loading