Skip to content

v1.9.0

Choose a tag to compare

@a-akimov a-akimov released this 24 Aug 20:37
c060cd9

Added

  • Five modern Streamable HTTP readiness checks. Four LOW rules require
    missing or contradictory standard request headers to receive HTTP 400 with
    JSON-RPC -32020 (HeaderMismatch): a missing
    MCP-Protocol-Version, a missing Mcp-Method, and mismatched Mcp-Name
    values on resources/read and prompts/get. The MEDIUM
    readiness_2026_no_get_stream rule checks that a modern-only endpoint
    rejects the removed standalone GET stream with HTTP 405. All probes are
    side-effect-free, HTTP-only, and skip when auth or an unimplemented optional
    resource/prompt surface makes the behavior unobservable. The missing-version
    and GET checks also skip dual-era endpoints, whose older-transport
    compatibility is explicitly allowed by the specification. Dual-era
    applicability uses explicit initialize-handshake provenance rather than
    inferring it from a version discovered through the modern lifecycle.
    A registry calibration found that 83–97% of judgeable modern-only servers
    fail each header check, so LOW avoids multiplying four correlated early-
    adoption findings into a 12-point HIGH penalty.
  • Sensitive MCP header detection. The new HIGH
    tools_mcp_headers_not_sensitive rule checks the MCP 2026-07-28 guidance
    that passwords, API keys, tokens, and personally identifiable information
    should not be marked with x-mcp-header, because network intermediaries can
    observe the resulting values. Its deterministic matcher is deliberately
    conservative: it reports strong credential phrases and high-confidence
    personal or financial identifiers, while excluding ambiguous identifiers
    and common non-secret pagination/token-count fields. Reports identify the
    tool, schema path, header, matched metadata field, and sensitive category;
    they never contain a credential value.
  • Cross-page cache-scope consistency. The new HIGH
    pagination_cache_scope_consistent rule traverses every paginated list
    surface declared by a modern server and enforces the MCP 2026-07-28 MUST
    that all pages of one list request use the same cacheScope. It observes
    raw wire fields rather than the SDK model, whose default of private would
    otherwise hide an omitted field. Single-page catalogs, legacy-only servers,
    unimplemented optional resource-template listings, and traversals without
    enough complete evidence skip rather than fail. The read-only traversal has
    a 30-second total deadline and a 100-page-per-surface safety bound.
  • Four capability-aware pagination rules. For tools/list,
    resources/list, resources/templates/list, and prompts/list, mcpscore
    now sends a fabricated opaque cursor and checks the MCP pagination
    recommendation—present in every dated revision—that invalid cursors return
    JSON-RPC -32602 (Invalid params). The probes are read-only and run against the established SDK
    session for legacy and dual-era servers, avoiding false failures from
    stateful servers that require initialization; modern-only servers use the
    stateless probe path. Undeclared capabilities and unavailable observations
    skip rather than fail. Because the resources capability does not separately
    advertise the optional resource-template listing, a -32601 response from
    resources/templates/list also skips only that rule.

Fixed

  • security_error_data_leak now runs. It read a field (error_response)
    that no code path ever produced, so it silently skipped on every audit. It
    now scans the response body the malformed-request probe elicits — the
    request most likely to make a server dump a stack trace, file path, or
    secret — for sensitive-data leaks, and skips as insufficient data only when
    no body was captured (stdio is not applicable). A leaked value is never
    echoed back into the report
    (only the leak type and a count), because the
    report is shareable and echoing the secret would re-leak it. Servers that
    do not leak gain the 2 points they previously never had a chance to earn —
    e.g. DeepWiki 76/85 → 78/87.
  • Corrected two stale rule docstrings that quoted an old severity-point scale
    (10 points (CRITICAL) → 5, 5 points (MEDIUM) → 2); the weights are
    RuleSeverity (CRITICAL 5 / HIGH 3 / MEDIUM 2 / LOW 1).
  • Malformed-request scoring now has real evidence. The existing
    security_malformed_request_handling rule is backed by a safe raw probe.
    It enforces the JSON-RPC 2.0 Parse error code (-32700) as the normative
    requirement, and deliberately relaxes one strict-JSON-RPC point: the
    response id may be null or absent (the id is unknowable when the
    request never parsed, and a registry calibration found conforming servers
    that omit it) — a calibrated interoperability allowance, not full Response
    Object conformance. HTTP status is deliberately not constrained because
    JSON-RPC is transport agnostic. A successful mandatory server/discover
    control prevents auth, endpoint, or optional-capability failures from
    becoming false verdicts. The probe streams and bounds the response body
    (16 KB) so a hostile server cannot exhaust memory on the deliberately
    malformed request.
  • Report rule order now matches its documentation. Rules sort by
    (group_order, group_name, rule_order, rule_id) — the tie-breaks the
    attribute docs always promised. Previously the sort key was a single
    integer with no tie-breakers, so the capabilities and security groups
    (which share a group_order) interleaved in report output by import
    order. Groups are now contiguous and the order is deterministic. Scores,
    rule ids, and severities are unchanged — only the order of entries in
    results (and the generated rules reference) moves.
  • The rule registry now rejects an empty rule_id at registration (the
    inherited default made the old check unreachable) and refuses to register
    a retired rule_id — retired ids are never reused.

Removed

  • BaseRule.sort_order. Ordering is collection policy, now computed by
    the internal rule_sort_key() at the sort sites, so a subclass override
    can no longer corrupt the sort with a stale key shape. The Python class
    surface is outside the stability contract
    (which covers the CLI, exit codes, the JSON report schema, and rule_ids,
    and explicitly reserves result positions for change in any release) —
    but if you imported sort_order anyway, sort with
    (rule.group_order, rule.group_name, rule.rule_order, rule.rule_id).
  • AuditData.error_response. A dead constructor field — nothing in the
    repo's history ever produced it, and its last reader
    (security_error_data_leak) now scans the malformed-request probe's body
    instead. Removed rather than kept as a phantom (its presence is what
    produced the never-observed-behavior confusion in the first place). The
    Python class surface is outside the stability contract as above; migration:
    drop the kwarg — any code that constructed AuditData(error_response=...)
    was setting a value no rule ever read.