main already builds the dictionary Signature-Agent form correctly — this is about the published release, and specifically about it failing quietly rather than loudly.
On web-bot-auth@0.1.3 (current latest, published 2026-03-09), key in SignatureParams is accepted, type-checked, and has no effect on the signature base. A caller asking for the dictionary binding gets the superseded form back with no error and no warning.
Environment
web-bot-auth@0.1.3 (npm latest; the version list ends at 0.1.3)
- Node 24.19.0
Reproduction
A signer that records the base instead of signing it:
const { signatureHeaders } = require("web-bot-auth");
const KEYID = "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";
const NONCE =
"n9p433xm+NJ3ph3upfBIGmsuwHw387YV7Q/F+6BSpGCVjYCqQw6rznNA8PVVLySrAWsv0hQtFioQb6E1YsauiA==";
const out = {};
const signer = {
alg: "ed25519",
keyid: KEYID,
async sign(data) { out.base = data; return new Uint8Array(64); },
};
await signatureHeaders(
{
url: "https://example.com/path/to/resource",
method: "GET",
headers: { "signature-agent": 'agent2="https://signature-agent.test"' },
},
signer,
{
created: new Date(1735689600000),
expires: new Date(4889289600000),
nonce: NONCE,
key: "agent2", // asks for the dictionary binding
components: ["@authority", "signature-agent"],
},
);
console.log(out.base);
Expected
Per draft-meunier-webbotauth-httpsig-protocol-01 §5.2.1 and Appendix E.2.2, and matching packages/web-bot-auth/test/test_data/web_bot_auth_architecture_v2.json[3] at 57fc787:
"@authority": example.com
"signature-agent";key="agent2": "https://signature-agent.test"
"@signature-params": ("@authority" "signature-agent";key="agent2");created=1735689600;…
Actual
"@authority": example.com
"signature-agent": agent2="https://signature-agent.test"
"@signature-params": ("@authority" "signature-agent");created=1735689600;…
The component parameter is absent from both the covered-component line and @signature-params; the raw header value is serialised instead. key made no difference — the same base is produced with key omitted entirely.
Passing the object component form that main's recommendedComponents() returns is rejected on 0.1.3:
components: ["@authority", { header: "signature-agent", key: "agent2" }]
// throws: signature-agent is required in params.components when included as a header param
and recommendedComponents is not part of the 0.1.3 export surface. So on the published release the dictionary form is not reachable by any calling convention I could find.
Cause
#63 ("Support draft-directory-04 with sf-dictionary signature-agent") was merged 2026-05-26. v0.1.3 was published 2026-03-09, about two and a half months earlier. The gap is release timing, not implementation — I read packages/web-bot-auth/src/index.ts at 57fc787 and recommendedComponents() returns the object component form as expected.
Scope
Narrow, and worth stating so this isn't read as broader than it is. Comparing bases against an independent implementation, 0.1.3 agrees exactly on:
- E.2.1 (
@authority only)
- E.2.3 (the legacy sf-string
Signature-Agent form)
@method, @authority, @path
- all signature parameters, their values, and their ordering
The only divergence is the dictionary Signature-Agent form.
Suggestion
A release carrying #63 would resolve it. If one isn't imminent, it may be worth having 0.1.x reject key rather than ignore it — throwing "dictionary Signature-Agent requires vX" would turn a silent wrong base into an obvious upgrade prompt. As it stands, a signer built on latest emits the superseded form while its author believes it asked for the dictionary form, and the failure only surfaces at a verifier that enforces the draft.
Not a security report
This is a wire-format difference. No signature is forgeable because of it, nothing is bypassed, and I'm making no claim about the behaviour of any deployed Cloudflare verifier — this is the npm library only. Filing it as an interoperability and ergonomics issue.
Happy to open a PR for the key-rejection guard if that would be useful.
mainalready builds the dictionarySignature-Agentform correctly — this is about the published release, and specifically about it failing quietly rather than loudly.On
web-bot-auth@0.1.3(currentlatest, published 2026-03-09),keyinSignatureParamsis accepted, type-checked, and has no effect on the signature base. A caller asking for the dictionary binding gets the superseded form back with no error and no warning.Environment
web-bot-auth@0.1.3(npmlatest; the version list ends at 0.1.3)Reproduction
A signer that records the base instead of signing it:
Expected
Per
draft-meunier-webbotauth-httpsig-protocol-01§5.2.1 and Appendix E.2.2, and matchingpackages/web-bot-auth/test/test_data/web_bot_auth_architecture_v2.json[3]at57fc787:Actual
The component parameter is absent from both the covered-component line and
@signature-params; the raw header value is serialised instead.keymade no difference — the same base is produced withkeyomitted entirely.Passing the object component form that
main'srecommendedComponents()returns is rejected on 0.1.3:and
recommendedComponentsis not part of the 0.1.3 export surface. So on the published release the dictionary form is not reachable by any calling convention I could find.Cause
#63 ("Support draft-directory-04 with sf-dictionary signature-agent") was merged 2026-05-26. v0.1.3 was published 2026-03-09, about two and a half months earlier. The gap is release timing, not implementation — I read
packages/web-bot-auth/src/index.tsat57fc787andrecommendedComponents()returns the object component form as expected.Scope
Narrow, and worth stating so this isn't read as broader than it is. Comparing bases against an independent implementation, 0.1.3 agrees exactly on:
@authorityonly)Signature-Agentform)@method,@authority,@pathThe only divergence is the dictionary
Signature-Agentform.Suggestion
A release carrying #63 would resolve it. If one isn't imminent, it may be worth having 0.1.x reject
keyrather than ignore it — throwing "dictionary Signature-Agent requires vX" would turn a silent wrong base into an obvious upgrade prompt. As it stands, a signer built onlatestemits the superseded form while its author believes it asked for the dictionary form, and the failure only surfaces at a verifier that enforces the draft.Not a security report
This is a wire-format difference. No signature is forgeable because of it, nothing is bypassed, and I'm making no claim about the behaviour of any deployed Cloudflare verifier — this is the npm library only. Filing it as an interoperability and ergonomics issue.
Happy to open a PR for the
key-rejection guard if that would be useful.