Skip to content

fix(verify): enforce canonical base64url and reject private JWK members in kill_switch and claim verification (#804) - #808

Open
drk-ram wants to merge 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-verify-kill-switch-804
Open

drk-ram wants to merge 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-verify-kill-switch-804

Conversation

@drk-ram

@drk-ram drk-ram commented Oct 23, 2026

Copy link
Copy Markdown

What

Enforce canonical unpadded base64url and reject private JWK parameters in cmcp_verify.kill_switch and cmcp_verify.verify.

Why

Closes #804.

Security impact

Prevents malleable base64url keys/signatures in kill-switch refusal receipts and TRACE claims and rejects unredacted private key material (d, p, q, dp, dq, qi, oth, k) in trace.cnf.jwk.

Test plan

  • pytest passes (pytest tests/unit/test_verify.py tests/unit/test_verify_malformed_claim_shapes.py -q)
  • ruff check passes (ruff check src/cmcp_verify/kill_switch.py src/cmcp_verify/verify.py tests/unit/test_kill_switch_evidence.py)
  • mypy passes (mypy src/cmcp_verify/kill_switch.py src/cmcp_verify/verify.py)
  • Manual test performed

DCO sign-off

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential LOW
Overall MEDIUM

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label Oct 23, 2026
carloshvp
carloshvp previously approved these changes Oct 24, 2026

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed exact head c827a497a3c4d111b1de6c6926369f71276a28a8 in an isolated checkout.

I independently signed a valid claim, changed only unused signature pad bits while preserving the decoded signature bytes, and confirmed that this head rejects the alias while the actual merge-base verifier accepts it. The intact signed control passes. A claim freshly re-signed after adding private JWK material is also rejected here and accepted by the old verifier, so that check is not merely an invalid-signature test. The kill-switch required fields and canonical decoder are consistent with the changed contract.

Local hash-locked Python 3.12.10 checks: 2,526 unit tests passed, 20 skipped; pip check, Ruff, mypy and Bandit passed, and commit sign-offs are present. Please trim the extra blank line at test_kill_switch_evidence.py EOF. No blocking source finding; required hosted checks and maintainer approvals remain outstanding.

Reviewed with OpenAI Codex.

@noah-ing noah-ing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head c827a497a3c4d111b1de6c6926369f71276a28a8. The implementation passed my independent signed Claim and refusal-receipt controls: intact signatures verified, noncanonical signature/key encodings and private JWK members were rejected, and empty required receipt fields failed. The local unit suite passed (2,526 passed, 20 skipped), along with Ruff, mypy, Bandit, and dependency consistency checks.

I am requesting a regression-test change before this security fix merges. The new claim_with_priv fixture has an invalid all-zero signature and no nonce. On the merge base, _verify_signature and _verify_key_binding already reject it, so the committed rejection assertions do not establish that this patch closes a previously accepted case. Please add a valid signed control, then test noncanonical signature and key encodings and private JWK members through the public Claim verifier; cover the new required receipt-field rules through verify_kill_switch_refusal as well. The source behaved correctly in my independent controls. Hosted CI remains blocked by the existing locked-dependency audit, and required maintainer gates are outstanding.

@drk-ram

drk-ram commented Oct 24, 2026

Copy link
Copy Markdown
Author

Thanks @noah-ing and @carloshvp! Updated test_kill_switch_and_claim_verify_reject_non_canonical_b64url_and_private_jwk in tests/unit/test_kill_switch_evidence.py to build a valid signed control claim and signed refusal receipt that pass verify_trace_claim and verify_kill_switch_refusal, and then exercise non-canonical base64url signature/key pad-bit aliases, freshly re-signed private JWK members (d), and empty/non-string required receipt fields (agent_id, session_id, refused_at) through the public verifiers, plus trimmed the EOF blank line.

@noah-ing

Copy link
Copy Markdown
Collaborator

Thanks for the follow-up. I reviewed head 4d37e36bf312e674f4c085280877572aff6cc629. The valid signed control, re-signed private-JWK case, and refusal-field tests address most of my request; the full local unit suite passes (2,526 passed, 20 skipped). One key-encoding regression still needs isolation: the new noncanonical JWK x case changes x without re-signing the Claim. The merge-base verifier already rejects that stale-signature fixture. In an independent control, a re-signed same-byte alias passes the merge base and fails this head. Please re-sign the mutated test Claim and assert its decoded key bytes are unchanged. Ruff also fails on the new test import order (I001, tests/unit/test_kill_switch_evidence.py:198). I am keeping the changes request open for those two focused fixes; I found no source-code blocker in this follow-up.

@dinakarjs dinakarjs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I prepared a signed adoption companion addressing the outstanding review finding: dinakarjs#8

Validation: final unit+conformance+integration+confinement suite 2654 passed/29 skipped. Ruff src/tests, strict mypy and Bandit pass. Disabling only signature-path key canonicality makes the signature assertion fail; separately disabling only key-binding canonicality makes the binding assertion fail, retaining all test names. Both guard removals are reverted.

Please cherry-pick/adopt the companion and request independent re-review of the resulting head. I have contributed the follow-up and am not approving my own contribution.

@drk-ram

drk-ram commented Oct 24, 2026

Copy link
Copy Markdown
Author

Thanks @noah-ing! Updated key_alias_claim in tests/unit/test_kill_switch_evidence.py to assert that base64.urlsafe_b64decode(alias_x + "=") == key.public_key_bytes and freshly re-sign the claim over the mutated trace.cnf.jwk.x before calling verify_trace_claim, and fixed the I001 import ordering.

@drk-ram

drk-ram commented Oct 25, 2026

Copy link
Copy Markdown
Author

Thanks @dinakarjs and @noah-ing! Adopted @dinakarjs's signed companion commit 82707be (fix/review-808-20261025) directly as the branch head with authorship and signature preserved.

@drk-ram
drk-ram force-pushed the drk-ram-fix-verify-kill-switch-804 branch from 32e8a46 to 82707be Compare October 25, 2026 04:48

@noah-ing noah-ing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head 82707bec6a927e0ea42843b77fd5e5b341bda418. The new key-alias regression re-signs the modified Claim, preserves the decoded key bytes and updates the nonce binding, so it now tests the intended canonical-encoding check. My independent signed control confirmed the old signature/key-binding paths accept that same-byte alias and this head rejects it; the intact signed control still passes those paths. Focused tests passed (80/80); the broader unit, conformance, integration and confinement suite passed (2,640 passed, 43 skipped). Ruff, changed-source mypy, dependency and whitespace checks passed. This resolves my prior requested change, so I approve this scoped verifier fix. The synthetic control is not a full hardware-attested Claim, and normal hosted checks still need to run under the repository gate.

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed exact head 82707bec6a927e0ea42843b77fd5e5b341bda418 in an isolated checkout.

The runtime guards are unchanged from the earlier reviewed implementation; this head strengthens the committed end-to-end controls. They use freshly signed Claims/refusal receipts, same-byte noncanonical encoding aliases, re-signed private-JWK cases, bound JWK thumbprints and required receipt-field cases. Those controls and the full suite pass. No blocking source/test finding; the assertions distinguish signature/key binding from an overall hardware-attestation verdict.

Independent validation in a fresh repository-hash-locked Python 3.12.10 environment: 2,526 tests passed, 20 skipped; pip check, configured Ruff, mypy, Bandit and diff whitespace checks passed. Commit sign-offs are present. Hosted CI and applicable independent maintainer/security gates remain separate prerequisites to merge.

Reviewed with OpenAI Codex.

…rs in kill_switch and claim verification (agentrust-io#804)

- Enforce canonical unpadded base64url in cmcp_verify.kill_switch._b64url_decode and require non-empty string agent_id, session_id, and refused_at on refusal receipts.
- Reject RFC 7517/7518/8037 private or symmetric JWK parameters on trace.cnf.jwk and enforce canonical unpadded base64url on trace.cnf.jwk.x and signature in cmcp_verify.verify.

Closes agentrust-io#804

Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com>
@drk-ram
drk-ram force-pushed the drk-ram-fix-verify-kill-switch-804 branch from 82707be to 4ab488a Compare October 31, 2026 15:52

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(verify): enforce canonical base64url and reject private JWK members in kill_switch and claim verification

4 participants