Skip to content

Commit 35f581d

Browse files
bokelleyclaude
andauthored
feat(signing): key_origins check + brand.json chain error codes (#350 stage 4) (#775)
* feat(signing): key_origins consistency check + brand.json chain error codes (#350 stage 4) Per ADCP request-signing spec #3690, an agent advertising signing posture declares ``identity.key_origins`` on its capabilities response — keyed by purpose (request_signing / webhook_signing / governance_signing / tmp_signing) and valued with the origin URI that hosts the JWKS for that purpose. After resolving an agent's keys via the brand.json chain, the verifier MUST confirm the resolved jwks_uri host matches the declared origin for the purpose under check. **4a — new error codes** (errors.py): Nine new ``request_signature_*`` codes covering the brand.json discovery chain (#3690 §"Discovering an agent's signing keys") and the ``identity.key_origins`` consistency check: - _brand_json_url_missing (capabilities.identity.brand_json_url absent) - _capabilities_unreachable (transport failure fetching capabilities) - _brand_json_unreachable (transport failure fetching brand.json) - _brand_json_malformed (strict-parse failure on brand.json) - _brand_origin_mismatch (agent eTLD+1 ≠ brand eTLD+1 + no delegation) - _agent_not_in_brand_json (agent URL absent from agents[]) - _brand_json_ambiguous (multiple agents[] entries byte-equal) - _key_origin_mismatch (resolved jwks_uri host ≠ declared origin) - _key_origin_missing (signing posture asserted, declaration absent) Plus the nine ``webhook_signature_*`` mirror constants and entries in REQUEST_TO_WEBHOOK_CODE so the webhook verifier wrapper retags request-family codes into webhook-family ones without each callsite re-declaring the mapping. **4b — key_origins consistency check** (key_origins.py, new): ``check_key_origin_consistency(jwks_uri, key_origins, purpose, posture=None, code_family="request")`` — the standalone primitive for the consistency check. Pure function on (resolved jwks_uri, declared key_origins map, purpose); raises SignatureVerificationError with the right spec code. ``code_family="webhook"`` swaps to the webhook code family. Canonicalization: ASCII-lowercase + stdlib host.encode("idna") to A-label form. The spec asks for IDNA-2008 strictly while stdlib encodings.idna is IDNA-2003; this divergence is rare in practice and matching the package's existing convention (jwks.py:201, ip_pinned_transport.py:110, revocation_fetcher.py:380) keeps the canonicalization story coherent. A future IDNA-2008 migration would update all four callsites together. The verifier integration (calling this check after RFC 9421 verification when the JWKS source was a brand.json walk vs. a publisher pin) belongs to stage 5's dispatch wire-up — the primitive lands here, ready to compose with the BrandAuthorizationResolver gate. 12 new tests cover: success (declared matches resolved), missing declaration, mismatch on different host, mismatch on subdomain drift, case-insensitive comparison, posture propagation in diagnostics, bare-host declarations, ``None`` and empty key_origins maps, invalid jwks_uri (fail-closed), and webhook code-family routing. Re-exports from ``adcp.signing`` so adopters can import: - check_key_origin_consistency - All 9 new REQUEST_SIGNATURE_* code constants (Webhook mirrors stay submodule-only; webhook code routing is internal to the webhook verifier wrapper.) Refs #350, adcontextprotocol/adcp#3690 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(signing): expert-review fixes for key_origins consistency check Folds three expert review passes into the stage 4 PR (ad-tech-protocol-expert, code-reviewer, security-reviewer all run in parallel on the original commit 62d2379). **1. Spec-mandated structured detail fields (MUST-FIX, protocol expert)** ADCP #3690 security.mdx step 7 mandates that ``request_signature_key_origin_mismatch`` carry ``{purpose, expected_origin, actual_origin}`` and ``_key_origin_missing`` carry ``{purpose, posture}`` as structured fields, not just an opaque message string. Middleware adapters surface them on the 401 / in a DLQ; without this, the SDK can't be spec- conformant once Stage 5 wires the verifier. - Extend ``SignatureVerificationError`` with optional ``detail: Mapping[str, str] | None``. ``str(exc)`` still renders the free-form message for unstructured logs; structured callers read ``exc.detail``. - ``check_key_origin_consistency`` now passes the spec-mandated keys. - Two new tests pin the detail shape for both code paths. **2. Bare-host vs URL canonicalization asymmetry (HIGH, security reviewer)** The previous ``_origin_host`` fallback path for bare-host inputs only guarded against ``/`` and space — accepting ``user@host``, ``host:port``, ``host?query``, ``host#fragment`` verbatim. An attacker with capability-write access could declare a bare-host-with-port (``keys.brand.com:8443``) to force a mismatch against the operator's brand.json origin and DoS the honest verification path. Fix: factor a ``_extract_host`` helper that re-parses bare-host inputs through ``urlsplit`` with a synthetic ``https://`` scheme prepended. URL form and bare-host form now strip port / userinfo / query / fragment symmetrically. Two new tests cover ``host:port`` and ``user@host`` declaration shapes. **3. Trailing-dot FQDN asymmetry (HIGH, security reviewer)** ``host.example.`` and ``host.example`` are the same FQDN — the trailing dot denotes the root zone. Previous code preserved the dot, so a brand.json serving the dot form against a capability declaring the no-dot form (or vice versa) byte-mismatched. An attacker controlling capabilities could weaponize this to deny verification against the real counterparty. Fix: strip a single trailing dot before IDNA-encoding. Test covers both directions. **4. IDN U-label vs A-label equivalence test (NICE, all three)** The docstring promised IDN canonicalization but no test exercised it. Added a test using ``münchen.example`` ↔ ``xn--mnchen-3ya.example`` in both directions. **5. Carve-out for publisher-pin source in function docstring (MEDIUM, security)** Previously the publisher-pin skip was documented only in the module docstring. A caller reading just the function doc would miss it. Added a "Caller contract" paragraph at the top of the function docstring flagging that callers MUST skip this call for publisher-pinned tuples. **6. Symmetric fail-closed test on declared side (LOW, security)** The existing ``test_consistency_raises_mismatch_on_invalid_jwks_uri`` covers the resolved side but not the declared side. Added a symmetric test so a future refactor can't silently invert the fail direction on one side. **Deferred to follow-up issues** (not in scope for this PR): - Plumbing the ``source`` discriminant ("brand.json walk" vs "publisher adagents.json pin") through ``BrandJsonJwksResolver`` so Stage 5 can enforce the carve-out automatically. Belongs in the Stage 5 verifier-integration PR. - Migrating all four codebase callsites (jwks.py, ip_pinned_transport.py, revocation_fetcher.py, key_origins.py) from stdlib IDNA-2003 to the ``idna`` PyPI package's IDNA-2008 in one commit. Package-wide conformance pass, separate concern. Tests: 20 in test_key_origins (up from 12). Full signing surface (582 tests across tests/test_*.py + tests/conformance/signing/) remains green. ruff + mypy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(storyboard): vendor AAO reference-formats fixture into @adcp/sdk install Three of the four storyboard runners (seller_agent.py, sales-proposal-mode, v3 reference seller (translator)) have been failing on main and on every PR for the same upstream reason: ``@adcp/sdk@latest`` (which the storyboard jobs install unpinned for drift-detection) does not ship ``aao-reference-formats.json`` in its npm tarball. Every step that touches AAO format resolution rejects with: AAO catalog (reference-formats.json) not found. Looked in: .../node_modules/@adcp/sdk/test/lib/v2-projection-fixtures/aao-reference-formats.json .../node_modules/@adcp/sdk/.context/adcp-3307/server/src/creative-agent/reference-formats.json Vendor a copy at test/lib/v2-projection-fixtures/aao-reference-formats.json. The SDK's error message itself recommends vendoring the file at the expected path. Upstream tracking issue is adcp#3307. Fix: - Vendor the file at ``tests/fixtures/aao-reference-formats.json`` (sourced from ``adcontextprotocol/adcp:server/src/creative-agent/reference-formats.json``, the canonical reference catalog the SDK is supposed to ship). - Add three lines to each of the four storyboard jobs' ``Pre-install @adcp/sdk`` steps to drop the file into ``$(npm root -g)/@adcp/sdk/test/lib/v2-projection-fixtures/`` after the npm install. Idempotent — if upstream later ships the file the ``cp`` overwrites with the same bytes; if upstream moves it the overwrite is a safer floor than the missing-file failure. The four storyboard jobs affected: - AdCP storyboard runner — examples/seller_agent.py - AdCP storyboard runner — examples/multi_platform_seller (PlatformRouter) (currently passing — doesn't exercise the AAO path — but covered for symmetry against future drift) - AdCP storyboard runner — v3 reference seller (translator) - AdCP storyboard runner — sales-proposal-mode (proposal_finalize) The vendored file is 145 KiB JSON with 50 format entries. Not committed to the package distribution (lives under ``tests/fixtures/``). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(storyboard): also vendor v1-canonical-mapping schema fixture The AAO reference-formats fix in the previous commit cleared one of the two missing-fixture errors and unmasked a second one of the same shape: v1-canonical-mapping.json not found. Looked in: .../node_modules/@adcp/sdk/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json .../node_modules/@adcp/sdk/schemas/cache/3.1.0-beta.1/registries/v1-canonical-mapping.json .../node_modules/@adcp/sdk/schemas/cache/3.1.0-beta.0/registries/v1-canonical-mapping.json .../node_modules/@adcp/sdk/schemas/cache/latest/registries/v1-canonical-mapping.json Run `npm run sync-schemas` for a 3.1+ AdCP version. Same root cause: ``@adcp/sdk`` published an npm tarball that omits the v1-canonical-mapping schema cache for 3.1.0-beta.* versions. The storyboard runner's v1→v2 format projection walks this registry; missing the file causes a cascade of step failures (and incorrectly-routed error codes like ``PRODUCT_NOT_FOUND`` instead of ``TERMS_REJECTED`` because the canonical mapping is consulted before product lookup). Vendor ``v1-canonical-mapping.json`` from ``adcontextprotocol/adcp:dist/schemas/3.1.0-beta.2/registries/`` (14 KiB) into ``tests/fixtures/`` and drop it into the SDK install path the SDK looks up first (the SDK's lookup order falls back through .2 → .1 → .0 → latest, so vendoring .2 is sufficient). Same four storyboard jobs as the prior fix: - examples/seller_agent.py - examples/multi_platform_seller (PlatformRouter) - v3 reference seller (translator) - sales-proposal-mode (proposal_finalize) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c23c9d5 commit 35f581d

7 files changed

Lines changed: 6033 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,25 @@ jobs:
391391
# own CI running AdCP's own canonical runner — tracking latest
392392
# surfaces protocol drift as soon as it ships, which is the
393393
# point of this job.
394+
#
395+
# Vendor missing fixtures into the SDK install:
396+
# ``@adcp/sdk`` does not ship two fixtures its storyboard runner
397+
# needs — ``aao-reference-formats.json`` (AAO format catalog,
398+
# upstream adcontextprotocol/adcp#3307) and
399+
# ``v1-canonical-mapping.json`` (v1→v2 format mapping registry
400+
# at schemas/cache/3.1.0-beta.2/registries/). The SDK's own
401+
# error messages recommend vendoring both at the exact paths
402+
# below. We keep copies under ``tests/fixtures/`` and drop them
403+
# into the SDK's expected locations post-install; idempotent if
404+
# upstream later ships them in the npm tarball.
394405
run: |
395406
npm install -g @adcp/sdk@latest
396407
adcp --version
408+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
409+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
410+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
411+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
412+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
397413
398414
- name: Install dependencies
399415
run: |
@@ -548,9 +564,16 @@ jobs:
548564
pip install "sqlalchemy>=2.0" "asyncpg>=0.29" "respx>=0.20"
549565
550566
- name: Pre-install @adcp/sdk (once, then call binary directly)
567+
# See the comment on the storyboard job's install step for the
568+
# AAO reference-formats fixture rationale (upstream adcp#3307).
551569
run: |
552570
npm install -g @adcp/sdk@latest
553571
adcp --version
572+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
573+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
574+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
575+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
576+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
554577
555578
- name: Start JS mock-server upstream
556579
run: |
@@ -757,9 +780,16 @@ jobs:
757780
${{ runner.os }}-npm-
758781
759782
- name: Pre-install @adcp/sdk
783+
# See the comment on the storyboard job's install step for the
784+
# AAO reference-formats fixture rationale (upstream adcp#3307).
760785
run: |
761786
npm install -g @adcp/sdk@latest
762787
adcp --version
788+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
789+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
790+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
791+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
792+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
763793
764794
- name: Install dependencies
765795
run: |
@@ -858,9 +888,16 @@ jobs:
858888
${{ runner.os }}-npm-
859889
860890
- name: Pre-install @adcp/sdk
891+
# See the comment on the storyboard job's install step for the
892+
# AAO reference-formats fixture rationale (upstream adcp#3307).
861893
run: |
862894
npm install -g @adcp/sdk@latest
863895
adcp --version
896+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
897+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
898+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
899+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
900+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
864901
865902
- name: Install dependencies
866903
run: |

src/adcp/signing/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,23 @@
162162
)
163163
from adcp.signing.digest import compute_content_digest_sha256, content_digest_matches
164164
from adcp.signing.errors import (
165+
REQUEST_SIGNATURE_AGENT_NOT_IN_BRAND_JSON,
165166
REQUEST_SIGNATURE_ALG_NOT_ALLOWED,
167+
REQUEST_SIGNATURE_BRAND_JSON_AMBIGUOUS,
168+
REQUEST_SIGNATURE_BRAND_JSON_MALFORMED,
169+
REQUEST_SIGNATURE_BRAND_JSON_UNREACHABLE,
170+
REQUEST_SIGNATURE_BRAND_JSON_URL_MISSING,
171+
REQUEST_SIGNATURE_BRAND_ORIGIN_MISMATCH,
172+
REQUEST_SIGNATURE_CAPABILITIES_UNREACHABLE,
166173
REQUEST_SIGNATURE_COMPONENTS_INCOMPLETE,
167174
REQUEST_SIGNATURE_COMPONENTS_UNEXPECTED,
168175
REQUEST_SIGNATURE_DIGEST_MISMATCH,
169176
REQUEST_SIGNATURE_HEADER_MALFORMED,
170177
REQUEST_SIGNATURE_INVALID,
171178
REQUEST_SIGNATURE_JWKS_UNAVAILABLE,
172179
REQUEST_SIGNATURE_JWKS_UNTRUSTED,
180+
REQUEST_SIGNATURE_KEY_ORIGIN_MISMATCH,
181+
REQUEST_SIGNATURE_KEY_ORIGIN_MISSING,
173182
REQUEST_SIGNATURE_KEY_PURPOSE_INVALID,
174183
REQUEST_SIGNATURE_KEY_REVOKED,
175184
REQUEST_SIGNATURE_KEY_UNKNOWN,
@@ -219,6 +228,7 @@
219228
verify_detached_jws,
220229
verify_jws_document,
221230
)
231+
from adcp.signing.key_origins import check_key_origin_consistency
222232
from adcp.signing.keygen import generate_signing_keypair, pem_to_adcp_jwk
223233
from adcp.signing.middleware import (
224234
unauthorized_response_headers,
@@ -336,14 +346,23 @@ def __init__(self, *args: object, **kwargs: object) -> None:
336346
"NEGATIVE_CACHE_TTL_SECONDS",
337347
"NONCE_BYTES",
338348
"PgReplayStore",
349+
"REQUEST_SIGNATURE_AGENT_NOT_IN_BRAND_JSON",
339350
"REQUEST_SIGNATURE_ALG_NOT_ALLOWED",
351+
"REQUEST_SIGNATURE_BRAND_JSON_AMBIGUOUS",
352+
"REQUEST_SIGNATURE_BRAND_JSON_MALFORMED",
353+
"REQUEST_SIGNATURE_BRAND_JSON_UNREACHABLE",
354+
"REQUEST_SIGNATURE_BRAND_JSON_URL_MISSING",
355+
"REQUEST_SIGNATURE_BRAND_ORIGIN_MISMATCH",
356+
"REQUEST_SIGNATURE_CAPABILITIES_UNREACHABLE",
340357
"REQUEST_SIGNATURE_COMPONENTS_INCOMPLETE",
341358
"REQUEST_SIGNATURE_COMPONENTS_UNEXPECTED",
342359
"REQUEST_SIGNATURE_DIGEST_MISMATCH",
343360
"REQUEST_SIGNATURE_HEADER_MALFORMED",
344361
"REQUEST_SIGNATURE_INVALID",
345362
"REQUEST_SIGNATURE_JWKS_UNAVAILABLE",
346363
"REQUEST_SIGNATURE_JWKS_UNTRUSTED",
364+
"REQUEST_SIGNATURE_KEY_ORIGIN_MISMATCH",
365+
"REQUEST_SIGNATURE_KEY_ORIGIN_MISSING",
347366
"REQUEST_SIGNATURE_KEY_PURPOSE_INVALID",
348367
"REQUEST_SIGNATURE_KEY_REVOKED",
349368
"REQUEST_SIGNATURE_KEY_UNKNOWN",
@@ -395,6 +414,7 @@ def __init__(self, *args: object, **kwargs: object) -> None:
395414
"build_signature_base",
396415
"canonicalize_authority",
397416
"canonicalize_target_uri",
417+
"check_key_origin_consistency",
398418
"compute_content_digest_sha256",
399419
"content_digest_matches",
400420
"decode_standard_webhook_secret",

src/adcp/signing/errors.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,34 @@
77

88
from __future__ import annotations
99

10+
from collections.abc import Mapping
11+
1012

1113
class SignatureVerificationError(Exception):
12-
"""Raised when a request signature fails any step of the verifier checklist."""
14+
"""Raised when a request signature fails any step of the verifier checklist.
15+
16+
``detail`` carries the spec-mandated structured fields for codes that
17+
require them — e.g. ``request_signature_key_origin_mismatch`` carries
18+
``{purpose, expected_origin, actual_origin}`` per ADCP #3690
19+
security.mdx step 7, and ``request_signature_brand_json_url_missing``
20+
carries ``{agent_url}`` per the same section's rejection-code table.
21+
Middleware adapters surface these as structured fields on the 401
22+
response or in a DLQ payload; ``str(exc)`` continues to render the
23+
free-form message for unstructured logs.
24+
"""
1325

1426
def __init__(
1527
self,
1628
code: str,
1729
*,
1830
step: int | str | None = None,
1931
message: str | None = None,
32+
detail: Mapping[str, str] | None = None,
2033
) -> None:
2134
super().__init__(message or code)
2235
self.code = code
2336
self.step = step
37+
self.detail = dict(detail) if detail is not None else None
2438

2539

2640
REQUEST_SIGNATURE_REQUIRED = "request_signature_required"
@@ -42,6 +56,28 @@ def __init__(
4256
REQUEST_SIGNATURE_JWKS_UNTRUSTED = "request_signature_jwks_untrusted"
4357
REQUEST_SIGNATURE_RATE_ABUSE = "request_signature_rate_abuse"
4458

59+
# brand.json discovery chain (ADCP #3690). Verifiers bootstrap an agent's
60+
# signing keys via ``identity.brand_json_url`` on the agent's
61+
# ``get_adcp_capabilities`` response → brand.json → ``agents[]`` →
62+
# ``jwks_uri``. Each step has a dedicated rejection code so callers can
63+
# disambiguate retryable transport failures (``*_unreachable``) from
64+
# misconfiguration (``*_missing`` / ``*_malformed`` / ``*_mismatch``).
65+
REQUEST_SIGNATURE_BRAND_JSON_URL_MISSING = "request_signature_brand_json_url_missing"
66+
REQUEST_SIGNATURE_CAPABILITIES_UNREACHABLE = "request_signature_capabilities_unreachable"
67+
REQUEST_SIGNATURE_BRAND_JSON_UNREACHABLE = "request_signature_brand_json_unreachable"
68+
REQUEST_SIGNATURE_BRAND_JSON_MALFORMED = "request_signature_brand_json_malformed"
69+
REQUEST_SIGNATURE_BRAND_ORIGIN_MISMATCH = "request_signature_brand_origin_mismatch"
70+
REQUEST_SIGNATURE_AGENT_NOT_IN_BRAND_JSON = "request_signature_agent_not_in_brand_json"
71+
REQUEST_SIGNATURE_BRAND_JSON_AMBIGUOUS = "request_signature_brand_json_ambiguous"
72+
73+
# identity.key_origins consistency check (ADCP #3690). For every purpose
74+
# declared under capabilities ``identity.key_origins``, the resolved
75+
# ``jwks_uri`` host MUST equal the declared origin (after IDNA-A-label
76+
# canonicalization). Mismatch → ``_key_origin_mismatch``. Missing
77+
# declaration when signing posture is asserted → ``_key_origin_missing``.
78+
REQUEST_SIGNATURE_KEY_ORIGIN_MISMATCH = "request_signature_key_origin_mismatch"
79+
REQUEST_SIGNATURE_KEY_ORIGIN_MISSING = "request_signature_key_origin_missing"
80+
4581
# Webhook-signing error taxonomy — adcp#2423 / webhooks.mdx + security.mdx.
4682
# Distinct strings from the request-signing family so receivers can route the
4783
# 401 response through webhook-specific observability.
@@ -64,6 +100,20 @@ def __init__(
64100
WEBHOOK_SIGNATURE_JWKS_UNTRUSTED = "webhook_signature_jwks_untrusted"
65101
WEBHOOK_SIGNATURE_RATE_ABUSE = "webhook_signature_rate_abuse"
66102

103+
# brand.json discovery chain mirrors for the webhook profile. The chain
104+
# walks identically (capabilities → brand.json → agents[] → jwks_uri),
105+
# just consulting the ``webhook_signing`` purpose under
106+
# ``identity.key_origins`` instead of ``request_signing``.
107+
WEBHOOK_SIGNATURE_BRAND_JSON_URL_MISSING = "webhook_signature_brand_json_url_missing"
108+
WEBHOOK_SIGNATURE_CAPABILITIES_UNREACHABLE = "webhook_signature_capabilities_unreachable"
109+
WEBHOOK_SIGNATURE_BRAND_JSON_UNREACHABLE = "webhook_signature_brand_json_unreachable"
110+
WEBHOOK_SIGNATURE_BRAND_JSON_MALFORMED = "webhook_signature_brand_json_malformed"
111+
WEBHOOK_SIGNATURE_BRAND_ORIGIN_MISMATCH = "webhook_signature_brand_origin_mismatch"
112+
WEBHOOK_SIGNATURE_AGENT_NOT_IN_BRAND_JSON = "webhook_signature_agent_not_in_brand_json"
113+
WEBHOOK_SIGNATURE_BRAND_JSON_AMBIGUOUS = "webhook_signature_brand_json_ambiguous"
114+
WEBHOOK_SIGNATURE_KEY_ORIGIN_MISMATCH = "webhook_signature_key_origin_mismatch"
115+
WEBHOOK_SIGNATURE_KEY_ORIGIN_MISSING = "webhook_signature_key_origin_missing"
116+
67117
# Code-family translation used by the webhook verifier wrapper. The verifier
68118
# pipeline raises request_signature_* codes; the wrapper retags them into
69119
# webhook_signature_* before exposing to callers. Keeps the 300-line verifier
@@ -87,4 +137,13 @@ def __init__(
87137
REQUEST_SIGNATURE_JWKS_UNAVAILABLE: WEBHOOK_SIGNATURE_JWKS_UNAVAILABLE,
88138
REQUEST_SIGNATURE_JWKS_UNTRUSTED: WEBHOOK_SIGNATURE_JWKS_UNTRUSTED,
89139
REQUEST_SIGNATURE_RATE_ABUSE: WEBHOOK_SIGNATURE_RATE_ABUSE,
140+
REQUEST_SIGNATURE_BRAND_JSON_URL_MISSING: WEBHOOK_SIGNATURE_BRAND_JSON_URL_MISSING,
141+
REQUEST_SIGNATURE_CAPABILITIES_UNREACHABLE: WEBHOOK_SIGNATURE_CAPABILITIES_UNREACHABLE,
142+
REQUEST_SIGNATURE_BRAND_JSON_UNREACHABLE: WEBHOOK_SIGNATURE_BRAND_JSON_UNREACHABLE,
143+
REQUEST_SIGNATURE_BRAND_JSON_MALFORMED: WEBHOOK_SIGNATURE_BRAND_JSON_MALFORMED,
144+
REQUEST_SIGNATURE_BRAND_ORIGIN_MISMATCH: WEBHOOK_SIGNATURE_BRAND_ORIGIN_MISMATCH,
145+
REQUEST_SIGNATURE_AGENT_NOT_IN_BRAND_JSON: WEBHOOK_SIGNATURE_AGENT_NOT_IN_BRAND_JSON,
146+
REQUEST_SIGNATURE_BRAND_JSON_AMBIGUOUS: WEBHOOK_SIGNATURE_BRAND_JSON_AMBIGUOUS,
147+
REQUEST_SIGNATURE_KEY_ORIGIN_MISMATCH: WEBHOOK_SIGNATURE_KEY_ORIGIN_MISMATCH,
148+
REQUEST_SIGNATURE_KEY_ORIGIN_MISSING: WEBHOOK_SIGNATURE_KEY_ORIGIN_MISSING,
90149
}

0 commit comments

Comments
 (0)