Skip to content

fix(governance): backport crit-marker and alg checks to 8.x - #1581

Merged
bokelley merged 1 commit into
release/8.xfrom
claude/issue-1539-8x-governance-crit
Oct 24, 2026
Merged

bokelley merged 1 commit into
release/8.xfrom
claude/issue-1539-8x-governance-crit

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Backport of #1539 to release/8.x.

On this line src/adcp/governance.py was byte-identical to main before #1539, so the governance fixes apply unchanged:

  • Critical markers. verify_governance_authorization compared each claim only with a crit entry whose marker is true. So it accepted a marker or crit entry for a claim the payload doesn't carry: a crit entry with a false marker, a marker with no crit entry, or a false marker alone. The marker, its crit entry and its claim must now appear together, with the marker true (security.mdx, governance verification checklist step 4: "Verifiers MUST also reject when the critical marker and its corresponding claim do not appear together").
  • Non-string alg. A header alg that is a JSON list or object raised TypeError, because the verifier tested it for membership in a set. It is now governance_token_invalid, like the other malformed headers.

CI install. The dev extra adds fastapi>=0.100 and svix>=1.0. Every CI job installs .[dev] or .[dev,pg], so the five signing modules that skip on pytest.importorskip("fastapi") now run in CI, along with the svix Standard Webhooks interop test. With them installed, 26 tests failed on release/8.x, the same 26 as on main. The fixes are to the tests, not the SDK, and match #1539:

  • The webhook sender tests pass the required operation_id to send_mcp (24 tests).
  • The proxy test's fake client has the stream method the sender uses.
  • The dedupe retry repeats the first body's timestamp, because the idempotency key binds the payload. A second body under the same key is a 409.
  • The HTTP dedupe test signs each delivery, because the receiver refuses a replayed nonce before it deduplicates (as WebhookSender.resend expects).
  • The digest-off FastAPI case signs with the 3.1 profile, because the 3.2 signer refuses a non-empty body without content-digest.

None of them revealed an SDK defect on 8.x.

Held: strict base64url decoding

#1539 also made adcp.signing.crypto.b64url_decode strict, refusing padding, +//, other characters and non-zero trailing bits. That part is not backported. b64url_decode is exported, and the strict version would reject padded or standard-alphabet input that 8.x accepts today, including input on the allow_legacy_base64url fallback path. A key published that way would stop verifying on a minor release. test_jwk_members_are_strict_base64url from test_crypto.py, which only tests that change, is left out too.

Tests

  • New in tests/test_governance_authorization.py: test_critical_marker_crit_entry_and_claim_appear_together (3 cases) and test_a_non_string_alg_is_an_invalid_token (2 cases). Each re-signs the accepted governance vector, so only the header check can refuse it. test_a_re_signed_vector_is_accepted checks the re-signing helper.
  • With fastapi and svix installed, the original 8.x versions of the five signing modules fail 26 tests and pass 31. With this change, all of them pass.
  • tests/conformance/signing + governance: 970 passed, 2 skipped (psycopg not installed locally).
  • make lint and make typecheck-all pass.
  • make test: 13053 passed, 2426 skipped, 10 deselected, 1 xfailed (Python 3.12, local; Postgres-backed tests skip without psycopg).

🤖 Generated with Claude Code

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean backport of #1539 to release/8.x with no blocking or medium findings.

Checked:

  • src/adcp/governance.py: the new together = (name in claims) == (name in critical) == (name in header) plus header.get(name, True) is not True correctly rejects the three previously-accepted malformed combinations per security.mdx step 4; type(alg) is not str routes a non-string alg to governance_token_invalid instead of raising TypeError. Both changes are strictly stricter — no legitimate token flips to rejected (fail-closed, correct).
  • Holding back the strict base64url change is the right public-surface call: b64url_decode is exported and the change would break padded/legacy input on a minor.
  • Test-only changes (added operation_id, the stream fake, payload-bound idempotency) match existing 8.x SDK behavior now that fastapi/svix install in CI.

Gates: no critical/high findings (row 1 clear), gated_paths false, high_risk false, no no-auto-approve team match, no prior decision, zero medium findings. Falls through to row 9 → approve.

Backport of #1539 to release/8.x.

verify_governance_authorization now requires each critical marker, its
crit entry, and its claim to appear together with the marker true
(security.mdx checklist step 4), and rejects a non-string header alg as
governance_token_invalid instead of raising TypeError.

The dev extra gains fastapi and svix so the 26 signing tests that skipped
on pytest.importorskip run in CI; the tests are updated for the required
operation_id, the streaming sender, payload-bound idempotency keys,
per-delivery nonces, and the 3.2 content-digest requirement.

Strict base64url decoding from #1539 is held: it would reject padded or
standard-alphabet input 8.x accepts today.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@bokelley
bokelley force-pushed the claude/issue-1539-8x-governance-crit branch from 8d6529f to 90940cc Compare October 24, 2026 18:49

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean backport of #1539 to release/8.x with no blocking or medium findings.

Checked:

  • src/adcp/governance.py (sole production change): the non-string alg guard correctly prevents a TypeError on unhashable JSON alg, now surfacing as governance_token_invalid. The critical-marker triad check (together + header.get(name, True) is not True) is a correct fail-closed tightening that matches security.mdx step 4, verified across claim/crit/header presence combinations.
  • Test additions (operation_id, stream fake, timestamp-pinned dedupe, 3.1-profile digest-off case) align with already-required base signatures; newly armed fastapi/svix dev deps surface previously-skipped modules — CI gates remain armed, nothing disabled.
  • Deliberately holding the strict base64url change off the minor is the correct semver call.

No critical/high/medium findings. gated_paths: false, review_decision: APPROVED, no author team gate, high_risk: false. No decision-table trigger fires (rows 1–8), so this falls through to row 9 = approve. Prior decision was also approve; this is consistent.

@bokelley
bokelley merged commit c58300d into release/8.x Oct 24, 2026
46 of 50 checks passed
@bokelley
bokelley deleted the claude/issue-1539-8x-governance-crit branch October 24, 2026 20:20
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