You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@target-uri / @authority canonicalization now strips a trailing FQDN-root dot from the host. This is a wire-visible change and the AdCP spec does not define the behaviour either way, so it is filed here to make it visible and to ask that upstream decide it.
Landed in #985 (feat(signing): reject malformed authorities, convert IDN hosts to A-labels).
Why the change was forced
#985 makes the signing path call _idna_canonicalize.canonicalize_host for non-ASCII hosts, which is the package's designated host normalizer and already has six consumers. That helper strips one trailing root dot as part of its UTS-46 preparation. The pre-existing ASCII path did a bare host.lower(), which keeps it.
Leaving both as they were would have produced a branch-dependent authority:
input
ASCII branch
IDNA branch
https://xn--bcher-kva.example./p
xn--bcher-kva.example.
—
https://bücher.example./p
—
xn--bcher-kva.example
Same host, two spellings, two different @authority values — so a signer emitting the A-label form and a verifier deriving the host from a Host header carrying the U-label would compute different signature bases and every signature between them would fail to verify. That is precisely the silent canonicalization drift the single-algorithm design exists to prevent.
Three options were available, none of them free:
Strip once, before the branch (chosen). Both branches agree, and they agree with the designated normalizer. Costs a wire-visible change for FQDN-root URLs.
Detach and re-append around the branch. Preserves today's bytes exactly, but it is bespoke normalization inside canonical.py — which would make it the seventh host-normalizing implementation in the tree, the duplication fix(signing): reject malformed authorities, convert IDN hosts to A-labels #985 exists to reduce.
Reject a trailing root dot as a malformed authority. Unambiguous, but invents a rejection the spec does not state, and rejecting traffic the spec may require accepting is worse than the ambiguity.
What this issue is actually asking for
The spec should define root-dot handling and ship a vector for it.example.com. and example.com are the same DNS name; whether they are the same signature base is a protocol question, not an SDK question, and every implementation is currently guessing.
Note that the SDK could not simply add a local vector to settle it. tests/conformance/vectors/request-signing/ is vendored verbatim and pinned by SHA-256 in vector_manifest.json; test_vendored_tree_matches_manifest_exactly fails on extra files as well as modified ones, and its own message states the policy:
"Vectors are vendored verbatim from the spec; add the file upstream first, then re-vendor and regenerate the manifest."
That pin is #975/#980's whole point, so working around it locally would have reintroduced the drift those issues exist to stop. The behaviour is therefore pinned by an SDK-local test (test_trailing_root_dot_is_stripped_on_both_host_branches) which is explicitly marked as superseded once a real vector exists.
Suggested upstream ask (adcontextprotocol/adcp): add a root-dot-* case to dist/compliance/<version>/test-vectors/request-signing/canonicalization.json fixing whichever behaviour the spec wants, and state the rule in the @target-uri canonicalization steps alongside the existing scheme/port/path rules.
Impact
Low, but real. Affects only callers signing or verifying URLs whose host carries an explicit trailing dot. A new-SDK signer and an old-SDK verifier disagree for those URLs during rollout. Shipping as a minor (#985 is titled feat:) rather than a patch for this reason.
Summary
@target-uri/@authoritycanonicalization now strips a trailing FQDN-root dot from the host. This is a wire-visible change and the AdCP spec does not define the behaviour either way, so it is filed here to make it visible and to ask that upstream decide it.Before:
https://example.com./p→ authorityexample.com.After:
https://example.com./p→ authorityexample.comLanded in #985 (
feat(signing): reject malformed authorities, convert IDN hosts to A-labels).Why the change was forced
#985 makes the signing path call
_idna_canonicalize.canonicalize_hostfor non-ASCII hosts, which is the package's designated host normalizer and already has six consumers. That helper strips one trailing root dot as part of its UTS-46 preparation. The pre-existing ASCII path did a barehost.lower(), which keeps it.Leaving both as they were would have produced a branch-dependent authority:
https://xn--bcher-kva.example./pxn--bcher-kva.example.https://bücher.example./pxn--bcher-kva.exampleSame host, two spellings, two different
@authorityvalues — so a signer emitting the A-label form and a verifier deriving the host from aHostheader carrying the U-label would compute different signature bases and every signature between them would fail to verify. That is precisely the silent canonicalization drift the single-algorithm design exists to prevent.Three options were available, none of them free:
canonical.py— which would make it the seventh host-normalizing implementation in the tree, the duplication fix(signing): reject malformed authorities, convert IDN hosts to A-labels #985 exists to reduce.What this issue is actually asking for
The spec should define root-dot handling and ship a vector for it.
example.com.andexample.comare the same DNS name; whether they are the same signature base is a protocol question, not an SDK question, and every implementation is currently guessing.Note that the SDK could not simply add a local vector to settle it.
tests/conformance/vectors/request-signing/is vendored verbatim and pinned by SHA-256 invector_manifest.json;test_vendored_tree_matches_manifest_exactlyfails on extra files as well as modified ones, and its own message states the policy:That pin is #975/#980's whole point, so working around it locally would have reintroduced the drift those issues exist to stop. The behaviour is therefore pinned by an SDK-local test (
test_trailing_root_dot_is_stripped_on_both_host_branches) which is explicitly marked as superseded once a real vector exists.Suggested upstream ask (
adcontextprotocol/adcp): add aroot-dot-*case todist/compliance/<version>/test-vectors/request-signing/canonicalization.jsonfixing whichever behaviour the spec wants, and state the rule in the@target-uricanonicalization steps alongside the existing scheme/port/path rules.Impact
Low, but real. Affects only callers signing or verifying URLs whose host carries an explicit trailing dot. A new-SDK signer and an old-SDK verifier disagree for those URLs during rollout. Shipping as a minor (#985 is titled
feat:) rather than a patch for this reason.Related