Repository navigation
feat(signing): accept any well-formed adcp_use purpose in keygen - #1708
Conversation
generate_signing_keypair, pem_to_adcp_jwk and adcp-keygen --purpose took a closed Literal["request-signing", "webhook-signing"] and refused every other value, so the SDK could not mint or publish a key for response-signing (the designated-task response JWS profile, core/response-payload-jws-envelope.json), governance-signing (which governance.py already verifies), or a surface the vocabulary does not name yet. The helpers they delegate to already took a str. purpose is now validated by syntax instead of membership: a lowercase hyphenated token of at most 64 characters. Malformed values (empty, upper case, underscores, whitespace, leading/trailing/double hyphens, non-str) still raise ValueError, and the CLI exits 2 before writing a file. An open vocabulary is safe because purpose matching is fail-closed: a verifier accepts a key only when adcp_use equals the purpose of the surface it verifies, so a key under any other purpose verifies nothing there. A test pins that against the request and webhook verifiers. adcp.signing exports ADCP_USE_REQUEST, ADCP_USE_WEBHOOK (marked deprecated upstream), and the new ADCP_USE_RESPONSE and ADCP_USE_GOVERNANCE, so callers need not retype literals; governance.py uses the constant. The docstrings no longer say request- and webhook-signing keys must be distinct for every verifier: 3.3 webhook verifiers accept request-signing keys, and new webhook signers publish request-signing. Closes #1677 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean PR, no blocking or medium findings.
Checked: widening keygen purpose from a closed Literal to a syntactically-validated open str is a contravariant input change (non-breaking, correctly shipped under feat(signing):). Reviewer verified the fail-closed claim holds against the real verifier (membership-checked adcp_use), re.fullmatch anchors token validation, and the CLI exits 2 before writing. Tests cover every new branch.
High-risk flag is true only because three src/adcp/signing/** files were modified, but no medium-or-higher finding attaches to them, so the modification is presumed safe (no deletions). No gated paths, no author team gate, no escalation trigger. Falls through the decision table to row 9.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
Delta since the prior approve is a single additive change: tests/fixtures/public_api_snapshot.json gains four adcp.signing constants (ADCP_USE_GOVERNANCE/REQUEST/RESPONSE/WEBHOOK). Values match constants.py and the __all__ additions verbatim, ordering is consistent, and the feat(signing): prefix is correct for additive exports plus a non-breaking Literal→str widening of purpose.
Checks:
- No critical/high/medium findings in this run.
- high_risk is true only due to (modified) entries under
src/adcp/signing/**, but the reviewer found no medium-or-higher concerns on those files — modifications presumed safe. No (deleted) reasons, so row 3 does not fire. - gated_paths: false; review_decision: APPROVED — no gate blocks.
- No no-auto-approve team match.
- Prior decision was
approve, so no sticky escalation applies.
No escalation trigger, no team gate, zero medium findings → falls through to row 9 (approve).
generate_signing_keypair, pem_to_adcp_jwk and adcp-keygen --purpose took a closed Literal["request-signing", "webhook-signing"] and refused every other value, so the SDK could not mint or publish a key for response-signing (the designated-task response JWS profile, core/response-payload-jws-envelope.json), governance-signing (which governance.py already verifies), or a surface the vocabulary does not name yet. The helpers they delegate to already took a str. purpose is now validated by syntax instead of membership: a lowercase hyphenated token of at most 64 characters. Malformed values (empty, upper case, underscores, whitespace, leading/trailing/double hyphens, non-str) still raise ValueError, and the CLI exits 2 before writing a file. An open vocabulary is safe because purpose matching is fail-closed: a verifier accepts a key only when adcp_use equals the purpose of the surface it verifies, so a key under any other purpose verifies nothing there. A test pins that against the request and webhook verifiers. adcp.signing exports ADCP_USE_REQUEST, ADCP_USE_WEBHOOK (marked deprecated upstream), and the new ADCP_USE_RESPONSE and ADCP_USE_GOVERNANCE, so callers need not retype literals. The docstrings no longer say request- and webhook-signing keys must be distinct for every verifier: 3.3 webhook verifiers accept request-signing keys, and new webhook signers publish request-signing. Backport of #1708 to 9.x. Closes #1677 on main. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Problem
generate_signing_keypair,pem_to_adcp_jwkandadcp-keygen --purposenarrowedpurposetoLiteral["request-signing", "webhook-signing"]and raised on anything else. That set is narrower than the spec vocabulary and than the SDK's own internals:response-signing(the designated-task response JWS profile,core/response-payload-jws-envelope.json) could not be minted or published.governance-signingis whatadcp.governancealready requires of a governance JWK, but keygen could not produce one.adcp_use.The helpers underneath (
generate_ed25519,generate_es256,_adcp_public_jwk) already took a plainstr.Fix
purposeis validated syntactically: a lowercase hyphenated token ([a-z0-9]+(-[a-z0-9]+)*), at most 64 characters. Empty, upper-case, underscore, whitespace, leading/trailing/double-hyphen and non-strvalues still raiseValueError; the CLI exits 2 before writing a file.adcp.signingnow exportsADCP_USE_REQUEST,ADCP_USE_WEBHOOK(commented as deprecated upstream, adcp#5555), and the newADCP_USE_RESPONSEandADCP_USE_GOVERNANCE.governance.pyuses the constant.{request-signing, webhook-signing}, and new webhook signers publishrequest-signing.The signature is widened from a
Literaltostr, so existing callers are unaffected. This isn't a breaking change.Tests
response-signing,governance-signingand an unregistered token round-trip intoadcp_use, through both entry points and the CLI.response-signing,governance-signingor an unregistered purpose is refused by the request verifier (request_signature_key_purpose_invalid) and by the webhook verifier's accepted-uses set.Not changed:
WebhookSender.from_jwkstill requiresadcp_use == "webhook-signing"even though 3.3 says new webhook signers userequest-signing. That's a sender-policy question, separate from what keygen can express, so I left it as a follow-up.Closes #1677
🤖 Generated with Claude Code