Signature-Agent header
Every signed request must carry a Signature-Agent header: a structured-field dictionary whose members are https URIs. For the default directory type the value is an origin. A missing or malformed header fails; the legacy string form warns.
What this check verifies
The Signature-Agent header tells a verifier where to find the key that signed the request. The protocol draft (draft-ietf-webbotauth-httpsig-protocol-00) requires it on every signed request (sections 4.3 and 5.2.1). Its value is an RFC 8941 structured-field dictionary. Each member value is a quoted string holding an https URI, and each member may carry a type parameter naming how the URI resolves to keys (section 5.5):
directory, the default whentypeis absent: the value is an origin, and the keys are at its well-known path/.well-known/http-message-signatures-directory.jwks_uri: the value is the URL of a JSON Web Key Set.cimd: the value is the URL of a Signature Agent Card (an OAuth Client ID Metadata Document) that carries the keys.
Signature-Agent: sig1="https://example.com"
Signature-Agent: sig1="https://example.com/jwks.json";type=jwks_uri
The check grades the header like this:
- No header: fail. The draft requires signers to send it (section 4.3). A verifier that already holds the key can still verify such a request, but it can attribute the request only to the key holder, not to an operator’s directory, so the check grades the signer and fails.
- A header that is neither a dictionary nor a quoted string: fail.
- A member value, or the legacy string, that is not an https URI: fail.
- A
directorymember (or a member withouttype) whose value is not an origin, for examplehttps://example.com/.well-known/http-message-signatures-directory: warning. Verifiers that follow the draft ignore such a member; the message names the origin to send instead. An origin with a trailing slash (https://example.com/) counts as an origin, since the draft lets verifiers accept the empty path/. - A member with a
typethe draft does not define: warning, because verifiers ignore members with a type they do not support. - The legacy form, a bare quoted string such as
"https://example.com": warning. If the string carries a path, the message names the origin to use in the dictionary form. - A dictionary whose members are all https URIs, each
directorymember an origin: pass.
On the legacy form: earlier versions of the protocol defined Signature-Agent as a bare string, and deployments still send it (section 5.2.1, Appendix E.1.2). The draft requires signers to send the dictionary form and allows verifiers to accept the string form. Signers face a conflict today: Cloudflare’s verifier and Fingerprint’s document that they verify only the legacy string and reject the dictionary form. That is why the checker warns on the legacy form instead of failing it.
Why it matters
Without Signature-Agent, a verifier has no URL to attribute the request to. It can still verify the signature if it already holds the key, but then the identifier is only the key thumbprint, and a new key is a new identity (section 4.3).
With the header, any verifier can resolve the member, find the key named by keyid, and attribute the request to that URL. The dictionary form also matters for intermediaries: the member key (sig1 in the example) is part of the signature base, so a proxy may relabel the signature but must not change the member key (section 6.6.1).
How to fix it
-
Send the header on every signed request, with your origin as the value and https as the scheme. Serve the directory at that origin’s well-known path, directly with status 200.
-
Choose the form by the verifiers you need to reach:
Signature-Agent: sig1="https://example.com" Signature-Agent: "https://example.com"The first line is the dictionary form the draft requires. This check passes it, but verifiers that accept only the string form reject it. The second line is the legacy form. Those verifiers accept it, and this check warns. This site’s own requests send the legacy form for that reason.
-
Cover the header in
Signature-Input: as"signature-agent";key="sig1"for the dictionary form, or as"signature-agent"for the legacy form. The covered components check fails a signature that leaves it out. -
To point at a JWK Set elsewhere, use the dictionary form with
;type=jwks_uriand the full URL of the key set.
References
- The Web Bot Auth protocol draft (draft-ietf-webbotauth-httpsig-protocol-00): section 5.2.1 defines the header and the legacy form, section 5.5 the member types and the origin rule, section 4.3 the case without a URL, and section 6.6.1 member keys under relabeling.
- RFC 8941 defines structured field dictionaries, string items, and parameters.
- Cloudflare’s Web Bot Auth reference and Fingerprint’s implementation guide describe which
Signature-Agentform those verifiers accept. - RFC 9421 defines HTTP Message Signatures, including the
keyparameter for dictionary members. - How grading works explains how a failed signature check affects the score.
How the checker scores this
- Tier
- Signature
- Role
- Advisory. Failing this never caps the grade or changes the verdict.
- Point deduction
- A failure deducts 30 points; a warning deducts 8.