Skip to content

v1.2.0

Latest

Choose a tag to compare

@ernestprovo23 ernestprovo23 released this 04 Sep 20:46
340e49a

Added

  • doctor — zero-config MCP posture scan (DSE-1516). One command, no arguments:
    discovers every MCP client config already on the machine (Claude Code user + project,
    Claude Desktop, Cursor, VS Code, Windsurf, Codex — per-platform table in
    docs/DOCTOR.md), runs the existing auth audit and WRD-SUP-*
    launch checks over each configured server, reports every server no warden.lock pins
    (WRD-DOCTOR-NO-LOCK, low), and prints the exact, shell-quoted, secret-masked pin
    command per uncovered server plus the Action snippet. It composes existing engines
    and adds no detection catalog. Static by default — no spawn, no network, no DNS,
    asserted by a test that makes each of those raise. A discovered path with a symlink
    component is skipped with a warning so a planted link cannot widen the read set.
    --pin is the single opt-in that launches servers; it refuses in a non-interactive
    session without --yes. Exit 0 clean / 1 any finding / 2 unreadable config (fail closed).
    Hardened after security review before merge — the input files are attacker-reachable
    (a cloned repo ships .mcp.json), so: every config-controlled string is control-character
    neutralised before it reaches the terminal (no \n-injected second pin line, no
    \x1b repaint); --pin only spawns servers from a --config file the user named and
    prints every argv before asking; the printed pin command masks a doctor-local flag set
    (--key, --header, -H, Key: value, JSON-object args) plus auth-shaped URL query
    params and token-like path segments; a matching lock with pin.approved: false is
    reported as WRD-DOCTOR-LOCK-UNAPPROVED (medium) instead of silently counting as
    coverage; a malformed discovered file warns and the scan continues (exit 2 at the end);
    VS Code JSONC parses; a skipped config (symlink, > 8 MiB) is never a green exit; the
    project walk-up stops at the first .git or home and never walks at all from outside
    home without a .git boundary; --pin never overwrites a lock. A second review pass
    closed the residuals: every JSON config is loaded as the union of mcpServers and
    servers (a benign decoy map can no longer hide the one VS Code loads —
    WRD-DOCTOR-AMBIGUOUS-SERVER on a conflicting name); the JSONC trailing-comma pass
    is string-aware; safe_text also neutralises C1 controls, NEL, zero-width marks,
    U+2028/U+2029, and the bidi-override blocks (Trojan Source); underscored auth
    flags (--openai_api_key) mask; a URL fragment is redacted and a clean URL is
    printed byte-for-byte; --config is de-duplicated by resolved path. See
    docs/DOCTOR.md.

Changed

  • auth audit snippets use the house redactor. WRD-AUTH-TOKEN-IN-CONFIG snippets
    were abcd...xy (a 2-character suffix), wider than redact_secret allows everywhere
    else (prefix of at most half the value, no suffix). Now abcd…(len=N). Found during the
    doctor security review, which made that snippet fleet-wide and wrote it to SARIF.

  • check --against-community — signed multi-attester lock consensus, phase 1
    (DSE-1515).
    Closes the TOFU hole a single-party lock cannot: compares the freshly
    captured surface to Sigstore-signed attestations filed by independent attesters in a
    git corpus (locks/<ecosystem>/<package>/<version>/<attester>.lock + .sigstore,
    attesters.json). Emits WRD-CONSENSUS-MISMATCH / -SPLIT (high, exit 1),
    -NOVEL (low, exit 0); an unpinnable launch (-UNRESOLVED), an unverifiable entry
    (-UNVERIFIABLE: unknown attester, missing/corrupt sidecar, lock whose entries do not
    reproduce its signed digest) or an unreachable corpus (-UNREACHABLE) is exit 2 —
    never a skip. Compares a new launch-independent surface digest (§6.1 payload minus
    server) so attesters and consumers using different runners agree. Opt-in; default
    check is byte-for-byte unchanged. Every verdict states consensus attests
    observation, not safety
    . See docs/COMMUNITY_CORPUS.md.
    Phase 2 (the public mcp-warden-locks corpus + nightly attester) is pending.
    Hardened after security review before merge: the trust root is the consumer's
    (--attester <id>=<identity>@<issuer> / --attesters-file, required; the corpus's
    attesters.json is discovery only, divergent or duplicate ids are exit 2,
    WRD-CONSENSUS-UNPINNED-TRUST); signatures bind the package coordinate via a new
    v2 statement (mcp-warden-lock-digest/v2; pin --sign --coordinate / check --verify --coordinate; v1 statement bytes are unchanged), so a relocated genuine signature
    fails; --min-attesters (default 2) with WRD-CONSENSUS-INSUFFICIENT; corpus URLs
    limited to https:///ssh:///git@ and cloned with protocol.allow=never +
    hooks/symlinks/submodules disabled, -- separator, scrubbed env; size caps (lock
    1 MiB, sidecar/attesters 256 KiB, 64 entries) and corpus-root path confinement;
    WRD-CONSENSUS-SCHEMA-MISMATCH for a corpus lock at another lock schema; any
    community option without --against-community is exit 2; whitespace/control
    characters in a coordinate are UNRESOLVED; unexpected corpus errors are exit 2 with
    the exception class only.

  • MCP Lock Format v1 conformance corpus + zero-dependency TypeScript verifier (DSE-1513).
    vectors/ is now the language-neutral, executable definition of a conforming
    implementation (docs/SPEC.md §12.1): 77 vectors — RFC 8785 canonicalization (incl. the
    UTF-16 key-order rule for astral characters), every field/entry/overall digest, every
    WRD-DRIFT-* class with its severity, ordering and redacted detail, and malformed locks
    that MUST be rejected — generated from the Python reference by vectors/tools/generate.py.
    packages/lock-ts ships @mcp-warden/lock, a verify-only TypeScript implementation with
    no runtime dependencies (verify(lock, surface), digest(surface)), so a Node MCP
    server author can verify a lock without a Python toolchain. A new CI conformance job runs
    the corpus through BOTH implementations and proves the gate bites by flipping one hex
    character and requiring both harnesses to fail.

  • Four visible spec corrections found while building the corpus. WARDEN_LOCK_SCHEMA.md
    §3.1 mandated code-point key ordering and called UTF-16 ordering "not permitted" — the
    opposite of RFC 8785 §3.2.3 and of what the shipped rfc8785 canonicalizer does; a third
    implementation written from that sentence would disagree with every lock containing an
    astral-character key. docs/SPEC.md §4 said keys
    sort by Unicode code point; RFC 8785 (and the reference) sort by UTF-16 code units, which
    differs for astral characters. §7.5 said a $ref MUST NOT be followed; since
    schema_version 3 the reference follows same-document refs and only non-resolvable refs
    stay opaque. §12 also named schema_version 1 where the current level is 3.

  • Security-review remediation of the corpus + verifier (#99). verify() now runs the
    strict reader unconditionally (the duck-typed "already parsed" shortcut let a lock with
    no overall_digest verify ok); every digest/drift/malformed vector is asserted
    through verify() itself. The JCS canonicalizer rejects unpaired UTF-16 surrogates,
    non-plain objects (Date/Map/Buffer) and nesting past 512 levels instead of
    emitting a digest the reference can never agree with; new vectors pin all three, plus
    IEEE-754 number boundaries and astral-character entry sorting. The CI mutation proof
    now runs a control pass first and requires the failure to name the mutated vector.
    @mcp-warden/lock ships its LICENSE.

  • Behavior change — a lock at a schema_version above the implemented level is now
    rejected by both readers
    (read_lock raises a validation error; parseLock throws
    LockFormatError). Previously the Python reader accepted e.g. schema_version: 4 and
    compared it under level-3 rules — a silent mis-verification (SPEC.md §14.3).

  • deploy-gate — fail-closed CI gate for agent deployments (DSE-1257). Verifies a
    deploy's evidence against a declared gate policy: required eval suites met their
    thresholds, required guardrails are active, a budget/quota is declared, and a human
    approval receipt is present when required. The gate adjudicates evidence rather than
    running evals
    — keeping verdicts reproducible from two JSON files, free of any eval
    framework's dependency tree, and making missing/malformed evidence an unambiguous
    failure instead of a silent skip. Nine WRD-GATE-* rules; exit 0 only when every
    control is satisfied, 1 on any finding, 2 on unreadable input (fail closed).
    See docs/AGENT_GATES.md.

  • auth audit — static MCP auth-posture audit (DSE-1258). Audits MCP client/server
    config for remote endpoints declaring no authentication, cleartext http:// transport,
    and credential literals committed into config (WRD-AUTH-*), reusing the existing
    vendor secret patterns from check. Static only — no server spawn, no DNS, no
    network — which keeps it safe to run against any config in CI and immune to churn in
    the MCP auth specification. Deliberately does not flag loopback servers, ${VAR}
    secret references, or local stdio servers; every credential literal is redacted in
    findings, snippets, and SARIF. Runtime capability brokering stays out of scope
    (DSE-725). See docs/AGENT_GATES.md.

Fixed

  • auth audit no longer flags Bearer ${TOKEN} as a committed credential. The
    reference check only recognised a secret reference when ${VAR} was the entire
    value, so the most common correct shape for an Authorization header was reported as
    a leaked secret — found within a day by running the audit against a real in-house
    config, despite the module having 100% test coverage (every fixture used the bare
    ${VAR} form). A reference is now recognised anywhere in the value; once all
    references are stripped, the remainder may only be a short alphabetic auth-scheme
    word, so a real literal sitting beside a reference (Bearer sk-abc… ${T}) is still
    flagged.
  • --json now emits valid JSONL. Findings are written through rich.Console,
    which wraps at 80 columns even when stdout is a pipe, splitting long findings
    mid-object and silently breaking every machine consumer. Fixed with soft_wrap=True
    at all five emit sites — this also repairs check --json, pin --json, and
    diff --json, where the same defect predates the agent gates.

Both commands reuse the check exit-code contract and the shared SARIF/JSONL emitters,
so an existing code-scanning pipeline needs no changes.


Install: pip install mcp-warden-cli==1.2.0 (CLI command stays mcp-warden). Signed sdist/wheel Sigstore bundles are attached by release.yml.