Skip to content

Vendored request-signing conformance vectors are incomplete, and the loaders cannot detect it #975

Description

@KonstantinMirin

Summary

The vendored AdCP request-signing conformance vectors under tests/conformance/vectors/request-signing/ are an incomplete copy of the spec set, and the tests that grade against them are structurally incapable of noticing.

Both graders load vectors by globbing and then assert only that the result is non-empty:

  • tests/conformance/signing/test_verifier_vectors.py:77-78_load() does glob("*.json") then assert result, f"no vectors under ..."
  • tests/conformance/signing/test_canonicalization.py:24-31 — same shape for vectors carrying expected_signature_base

Any non-empty subset satisfies that assertion. A missing vector is indistinguishable from a complete set, so the suite reports green on a partial copy — which is exactly what has been happening.

The gap

Measured against AdCP 3.1.1, the version src/adcp/ADCP_VERSION already targets (spec repo dist/compliance/3.1.1/test-vectors/request-signing/):

spec 3.1.1 vendored missing
positive/ 12 8 009-percent-encoded-unreserved-decoded, 010-percent-encoded-slash-preserved, 011-ipv6-authority, 012-ipv6-authority-default-port-stripped
negative/ 28 20 021-duplicate-signature-input-label, 022-multi-valued-content-type, 023-multi-valued-content-digest, 024-unquoted-string-param, 025-jwk-alg-crv-mismatch, 026-non-ascii-host, 027-webhook-registration-authentication-unsigned, 028-unsigned-protocol-method-required
canonicalization.json ships absent the standalone signing-base fixture

canonicalization.json is the conspicuous one: the vendored README.md documents it in the file-layout tree and devotes a full section to it, describing it as "the fastest way to surface cross-implementation divergence" and recommending SDKs "run canonicalization.json on every commit". The file was never copied in.

Three further vectors had silently drifted from their upstream contents: 016-replayed-nonce, 017-key-revoked, 020-rate-abuse. 017 is the consequential one — it was vendored at a revision that revokes test-ed25519-2026, the primary signing key, rather than the dedicated test-revoked-2026 keypair the spec now uses. keys.json had never been refreshed to carry that key either.

Why it matters

Signature-base construction is the known-hard part of RFC 9421 interop, and the missing vectors sit exactly on its edges: 009/010 are percent-encoding, 011/012 are IPv6 authority, 026 is a non-ASCII host, 028 is the protocol_methods_required_for enforcement path.

A verifier can pass every currently-vendored vector and still reject validly-signed requests from a conformant counterparty. And because the SDK owns canonicalization on behalf of its consumers, these gaps are untested in every downstream implementation too, not just in this suite — this was found while adopting adcp.signing in a seller implementation that delegates canonicalization entirely to the SDK.

Fix

PR #980 vendors the complete 3.1.1 set and, more importantly, replaces the non-empty assertion with a completeness pin so the set cannot silently drift again at the next spec bump. Adding the 12 files without that guard would only reset the clock.

The pin is over content hashes, not filenames — a filename-only manifest would not have caught the 016/017/020 drift. It is tied to get_adcp_spec_version(), so bumping the targeted spec version without re-vendoring fails loudly.

Grading the new vectors turns up 12 real conformance failures

They are filed separately and marked strict-xfail in the PR rather than fixed there, since they are production behavior changes in a security-critical verifier:

  • The verifier has no step-1 strict-parse stage, so malformed/ambiguous input flows through to a later check and is rejected under the wrong error code (negative 021, 022, 023, 026). Conformance requires byte-for-byte error codes, so "rejected anyway" is not a pass.
  • @target-uri canonicalization applies no UTS-46 A-label conversion, drops a trailing empty query, and accepts five malformed authority shapes the spec requires be rejected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions