On July 27, 2026, curl maintainer Daniel Stenberg wrote up curl's newly merged experimental support for RFC 9421, HTTP Message Signatures — the IETF standard for cryptographically signing selected components of an HTTP request or response. The feature is off by default, sits behind an explicit build-time flag, and is scheduled to appear that way — still experimental — in the upcoming curl 8.22.0; Stenberg is explicit that it isn’t ready for production. That caution is appropriate for a new primitive, and the primitive is worth taking seriously anyway, because RFC 9421 answers a question distributed systems have needed a standard answer to for a long time: can a receiver verify, under key material it already trusts, that a specific set of request components hasn’t changed since it was signed?
It is also, unintentionally, a clean illustration of a boundary that keeps reappearing at every layer of distributed systems — the same boundary I wrote about two days ago in the context of the Hugging Face breach, just one layer further down the stack.
What a signature actually attests to
RFC 9421 lets a sender sign a defined set of message components — the method, the path, specific headers, a digest of the body, a created timestamp — under a key identifier, using a signature algorithm both sides agree on. When the receiver verifies it, what’s established is narrower than it sounds: the covered components were signed using key material the verifier has chosen to trust for that context, and those components haven’t changed since. Binding that key to a specific service, organization, or person happens outside the standard — RFC 9421 explicitly requires the verifier to determine the key material’s trustworthiness in context, and it supports HMAC, where more than one party can hold the same shared secret. In accessible prose it’s fair to say the signature “authenticates the signer” — as long as that’s understood to lean on key binding the standard doesn’t itself provide. And because only the covered components are signed, unsigned fields (and even some transformations of covered ones) can change without invalidating the signature — the guarantee is scoped, not blanket. Scoped as it is, this still closes a real gap: for service-to-service traffic, webhook delivery, and zero-trust mesh architectures, an attacker who can intercept or spoof an unsigned request currently has an easy time of it.
Notice what is not in that list. A verified signature does not establish that the key-holder was authorized to send this particular request, at this particular time, given everything else it has sent recently. It cannot, because verification happens one message at a time, and authorization — real authorization, the kind that matters — is a question about a principal's standing, its current scope, and often its history, none of which lives inside the bytes of a single signed message.
experimental, gated behind a build-time flag, and Stenberg's own post is explicit that it is not recommended for production use yet — and it stays that way, still marked experimental behind the same flag, in the upcoming curl 8.22.0. This note treats it as exactly what it is today — an early, honest implementation of a real standard, not a finished control. Source: daniel.haxx.se, July 27, 2026; standard: RFC 9421.
A key that shouldn't have been used, used correctly
Play out the failure mode. A service holds a signing key it is meant to use for reading inventory levels. Something goes wrong — the key leaks, the service is compromised, or it is simply asked (by an operator, or by an agent orchestrating it) to do something outside its intended purpose. Every request it now sends still verifies under that key. Signature verification alone does not evaluate whether a sequence of otherwise valid requests has changed purpose or accumulated into a disallowed outcome — that check happens once per message and stops there. An application can absolutely add stateful controls above verification; RFC 9421 expressly allows additional application requirements and even discusses replay protection. The standard just doesn’t provide that semantic sequence evaluation itself.
Ten individually-valid, individually-signed requests can compose into a data exfiltration run, a privilege-escalation chain, or a resource-exhaustion attack that no single request would ever be flagged for. The signature layer sees ten authentic messages. It has no layer above it asking what those ten messages, taken together, actually did.
A signature authenticates covered components. It does not sanction the action — or the sequence.
The same shape, one layer down
This is the same structural boundary as the argument in “Authorized but Composed” (DOI 10.5281/zenodo.21400261) and in the field note on the Hugging Face breach — just moved from the agent-decision layer down to the message-verification layer. There, the claim was: a per-action policy gate can approve roughly 17,000 individually-fine agent actions — the figure is from Hugging Face’s own incident disclosure, linked above — that compose into an intrusion, because the gate doesn’t evaluate the sequence. Here, the claim is narrower and more mechanical: a per-message signature check can validate an unbroken run of individually-authentic requests that compose into something nobody should have permitted, because signature verification doesn’t evaluate the sequence either. Different mechanism, same missing layer.
As more agent-to-agent and agent-to-API traffic gets wrapped in signed HTTP requests — which is a genuinely good trend, and one I expect to accelerate as agent workforces scale — this gap becomes more consequential, not less. An autonomous agent making its own tool calls, each one dutifully signed under its service's key, is precisely the kind of principal whose individual requests will all verify cleanly while its accumulated trajectory goes somewhere no one signed off on.
WHICH trusted key signed the covered components and WHETHER those components still verify are message-layer questions, and RFC 9421 answers them well. WHETHER the request — or the sequence it belongs to — was something anyone actually authorized is a decision-layer question, and no signature scheme answers it, because it was never built to. The two layers compose: verify the covered components under a trusted key, then evaluate what a signer's accumulated, authenticated actions add up to over time — across sessions or principals where the use case requires it. Skip the second layer and you get a very well-authenticated blind spot.
To be precise about the claim, because the whole point of this note is not to overreach in exactly the way a signature-only architecture does: this is not an argument against RFC 9421, and it is not a claim that curl's implementation is unsafe or premature — Stenberg's own caution about production-readiness is the right posture for new cryptographic protocol work. The argument is narrower and, I think, uncontroversial once stated: message-layer verification is necessary and not sufficient for authorization. Something has to sit above the verifier and ask what a signer's authenticated sequence composed into. That is a decision-governance problem, not a cryptography problem, and it needs a different kind of layer to answer it.
Where does your service mesh draw this line?
If your architecture is moving toward signed service-to-service or agent-to-API calls, the question this raises is direct: once a request verifies, what — if anything — is watching what a signer's authenticated requests compose into over time? The free Governance Stress Test scores your stack across identity, policy, and the decision layer, and shows you where the composition gap sits relative to your signing layer.
Building signed service-to-service or agent-to-API traffic and thinking about what sits above the verifier? Tell me where you think this argument breaks — research@cognitivethoughtengine.com.
Related reading
Authorized but Composed: the residual-moat argumentAuthorized, Composed, Breached
The Governance Engine That Remembers
Frequently Asked Questions
What does an HTTP message signature actually prove?
RFC 9421 lets a sender cryptographically sign selected components of an HTTP request or response — method, path, specific headers, a digest of the body, a timestamp — under a key identifier. A valid signature provides cryptographic evidence that the covered components were signed using key material the verifier trusts for that context, and that those components haven’t changed since. Binding that key to a specific service or person happens outside RFC 9421 — the standard leaves key-trust decisions to the verifier and supports HMAC, where more than one party can hold the signing secret. None of that says anything about whether the holder of that key was permitted to send this particular request, at this particular time, in this particular sequence.
If a request is validly signed, isn't that enough to trust it?
It is enough to trust the authenticity and integrity of the covered components. It is not enough to trust that the action it represents was authorized. A compromised-but-still-valid key, or a legitimate key used outside its intended scope, produces requests that pass signature verification every time. Signature verification alone does not evaluate whether a sequence of otherwise valid requests has changed purpose or accumulated into a disallowed outcome — an application can add stateful controls above verification, but RFC 9421 doesn't provide that semantic sequence evaluation itself. Ten individually-valid signed requests, each within the signer's permissions, can compose into a sequence no one would have authorized as a whole, and signing alone won't catch it.
Is this an argument against adopting RFC 9421?
No. Message signing closes a real, necessary gap — spoofing and tampering at the message layer — and curl merging experimental support for it is a genuine improvement. The claim is narrower: message-layer authentication is necessary but not sufficient for authorization. Something has to sit above signature verification and evaluate what a signer's sequence of authenticated requests composes into over time. That is a different layer, doing a different job, and RFC 9421 was never designed to do it.