Added
- Injection-phrase FP-instrumentation (Refs #12). Shippable, non-default-changing
groundwork for eventually promotingWRD-RES-INJECT-PHRASEto default-block once field
false-positive (FP) data justifies it. No default posture changed — the fuzzy tier
stays monitor-only, andWRD-RES-INJECT-PHRASEkeeps its tier, its default action, and its
place in the error-replacement set. Added: (1) a discretematched_phrasesarray on the
finding record (JSONL) +matchedPhrasesSARIF property, so per-phrase aggregation reads
a structured field instead of parsingmessage; (2) arun-summaryJSONL record + SARIF
run property carryingframes_inspected— the base-rate denominator for a per-phrase FP
rate — plusinject_phrase_findings; (3)--block-inject-phrase-only <file>, a
default-off, per-phrase opt-in that blocks ONLY the named exact phrases while every other
curated phrase stays monitor (narrower than--block-inject-phrase; the future
deterministic-subset promotion mechanism); (4) an operator record → inspect → label
FP-collection workflow indocs/RESULT_INSPECTION.md§10. Security: the telemetry
surface emits only the curated denylist phrase, rule id, metadata, action, and counts —
never raw result content (which can carry secrets/PII); all aggregation is local-only,
no phone-home. Issue #12 stays open (the default-block flip remains gated on the FP data
this instrumentation collects). - CI coverage / lint / CVE gates. The
CIworkflow now enforces three new
standing gates: (1) a coverage floor —pytest --cov=mcp_warden --cov-fail-under=80(whole-project coverage is ~86%; the floor is pinned below
actual so it can only rise). Subprocess coverage ofguard_list_gate.py(which
only executes inside the guard child spawned by the strict-abort tests) is now
captured via[tool.coverage.run] parallel = true+COVERAGE_PROCESS_START,
taking it from a 0% visibility artifact to ~89%. (2) A ruff lint gate
(ruff check .) with config in[tool.ruff](pyflakes/pycodestyle/isort/
bugbear;line-length = 100). (3) A pip-audit CVE gate indeps-locked
that audits the resolved dependency closure and fails closed on any advisory. - Trust-root unit tests (
tests/test_signing_unit.py). 25 new tests drive
the internalsigning.pysign/verify code paths directly (mocking the sigstore
boundary — no network, OIDC, or Fulcio/Rekor traffic), liftingsigning.py
line coverage from 61% to 99%. Covers the sign path (ambient + explicit token),
the verify path (identity/issuer plumbing), and every fail-closed branch.
Changed
-
Dependency refresh (security). Bumped the hash-locked dev/CI closure:
pydantic-settings2.14.1 → 2.14.2 (clears advisory GHSA-4xgf-cpjx-pc3j),
plus current minors ofmcp(1.27.2 → 1.28.1),cryptography(→ 49.0.0),
anyio(4.13.0 → 4.14.2), and others.pytest-covadded to thedevextra. -
Runtime DNS resolution SSRF bypass detection (
WRD-RES-EXFIL-DNS-SSRF) (#11):
theguardproxy now resolves URL hostnames fromtools/callresults at runtime
and blocks (error-replace) when any resolved IP falls in a deny range
(SSRF_NETWORKS— link-local, loopback, RFC1918, IPv6 ULA/loopback/link-local).
This closes the bypass whereWRD-RES-EXFIL-IP-LITERALcould not fire because the
result contained a DNS hostname (e.g.169.254.169.254.nip.io) rather than a raw
IP literal. Resolution is bounded by 1 s across all hostnames per result frame,
fail-open (any DNS error = no hit), and opt-out via--no-block-exfil-dns-ssrf
(or--no-block-deterministic). Raw IP literals and the offlineinspectcommand
are unchanged. New moduleres_dns.py; 23 new tests.