Skip to content

fix: fail closed on unverifiable signatures, format downgrades, http URIs, stale records#7

Merged
imran-siddique merged 1 commit into
mainfrom
fix/fail-closed-tr-sig
Jun 11, 2026
Merged

fix: fail closed on unverifiable signatures, format downgrades, http URIs, stale records#7
imran-siddique merged 1 commit into
mainfrom
fix/fail-closed-tr-sig

Conversation

@imran-siddique

Copy link
Copy Markdown
Contributor

Summary

Fixes four fail-open paths found in a security review:

  1. TR-SIG fail-open (critical): plain trace-format records carried no verifiable signature yet TR-SIG emitted PASS ("key type is supported") plus SKIP, and the CLI counted skips as non-failures, so trace-tests verify --level 2 passed for completely unsigned JSON. TR-SIG now fails closed: FAIL at any level >= 1, and a distinct UNVERIFIED status (never PASS-only) at level 0. The CLI treats UNVERIFIED as failure at level >= 1 (defense in depth) and surfaces "record is NOT cryptographically verified" in the level 0 result line.
  2. Attacker-chosen format selection: the loader picked cmcp-runtime only when both gateway and trace keys were present, so stripping gateway downgraded a cmcp envelope to the weaker plain-trace path. Detection now keys off the positive cmcp_version marker, rejects partial cmcp envelopes (trace/gateway/signature without cmcp_version) with a LoadError, and the chosen format remains visible in the report header.
  3. http:// accepted where messages demand https: TR-ANC transparency and TR-RTE rim_uri now accept https only, matching their failure messages.
  4. No freshness max-age: TR-ENV accepted any iat since Nov 2023. Added a configurable max-age (default 24h) with a clear staleness failure and a --max-age CLI option.

Tests

  • New: tests/unit/test_loader.py (downgrade resistance), tests/unit/test_cli.py (end-to-end regression: unsigned record fails level 1/2, partial envelope rejected, stale record fails), tests/unit/test_tr_anc.py.
  • Updated: TR-SIG, TR-ENV, TR-RTE, runner tests for the new behavior.
  • Full suite: 80 passed, 8 skipped (the 8 skips are the pre-existing level 1/2 stubs, tracked separately).

🤖 Generated with Claude Code

…URIs, stale records

Security fixes for four fail-open paths:

- TR-SIG: plain trace records carried no verifiable signature yet were
  reported PASS + SKIP, so `verify --level 2` passed for unsigned JSON.
  TR-SIG now FAILs at level >= 1 and emits a distinct UNVERIFIED status
  (never PASS-only) at level 0. CLI treats UNVERIFIED as failure at
  level >= 1 and surfaces it in the result line at level 0.
- loader: format detection keyed off presence of gateway+trace, so
  stripping `gateway` downgraded a cmcp envelope to the weaker plain
  trace path. Detection now uses the positive `cmcp_version` marker and
  rejects partial cmcp envelopes outright.
- TR-ANC / TR-RTE: http:// URIs were accepted where the failure
  messages demanded https. Now https only.
- TR-ENV: iat freshness had no upper bound, so any historical record
  passed forever. Added configurable max-age (default 24h) and a
  `--max-age` CLI option.

Adds unit tests for all four fixes plus end-to-end CLI regression tests
proving unsigned records can no longer pass level 1/2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@imran-siddique imran-siddique merged commit 0d1cb83 into main Jun 11, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant