| Internet-Draft | AIRP Provenance Seal and Register | August 2026 |
| Flores | Expires 8 February 2027 | [Page] |
A response served by an inference provider carries no verifiable statement of what produced it. A recipient cannot determine which model generated a given output, nor whether the endpoint that served it was authorized by the party whose name is on it. Attribution today rests on the serving party's own account of events, offered after the fact and at its own discretion.¶
This document specifies two mechanisms that together make that determination decidable by a recipient. The Provenance Seal is a detached signature by which a provider binds a model identifier, its own identity, and a timestamp to the exact bytes of a served response. The Serving Register is a signed document listing, for each provider, the endpoints authorized to serve its models, the public keys that validate its seals, and whether the provider declares that it seals every response. A DNS record under the provider's own domain binds that domain to its register entry and to its declared sealing policy, so that a suppressed seal is detectable rather than merely absent.¶
The design follows electronic mail authentication: the seal is patterned on DKIM, the register on SPF, and the declared sealing policy on the published policy record of DMARC.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 8 February 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Inference responses arrive as unattributed text. A recipient holding a response has no mechanism for establishing which model produced it, whether the model named in the response metadata is the model that ran, or whether the host that answered was one the named provider had authorized to answer on its behalf. Every element of that chain is asserted by the serving party and verified by no one.¶
The consequences are ordinary rather than exotic. A router may substitute a cheaper model for the one a caller selected and bill for the one selected. Weights obtained outside a provider's distribution may be served under that provider's name. A response may be altered between the serving host and the recipient. When an incident occurs and the parties disagree about what happened, no party holds evidence, and the account that prevails is the account of whoever controlled the pipeline.¶
Electronic mail faced the structurally identical problem and solved it with three composable pieces: a signature binding a message to a signing domain, a published list of hosts authorized to send for a domain, and a published policy stating what a receiver should do when the first two are absent or fail. This document adapts that pattern.¶
The two mechanisms specified here belong to the Accountable Inference Reputation Protocol (AIRP), described in [AIRP]. AIRP is pronounced "AI-Rep".¶
An earlier revision of this document was published as
draft-flores-aidp-provenance-00, under the name Accountable Inference
Delivery Protocol. That acronym is already carried by
[I-D.vandoulas-aidp], which reached its third revision before this
work was first posted. The name was changed rather than contested. Deployments
of the earlier name are addressed in Section 7.1.¶
This document specifies the wire artifacts and the verification procedure: the Provenance Seal, the Serving Register, the DNS binding between them, and the ordered checks a verifier performs. It does not specify what a recipient does with a verification result. Deferring delivery, annotating output, accumulating findings over time, and refusing further exchange are policy behaviors outside the scope of this document.¶
The mechanisms here are transport-agnostic in principle. Section 3.8 defines a binding for HTTP, which is where inference APIs are deployed today.¶
A valid Provenance Seal establishes that a party holding a registered private key asserted that a named model produced exactly these bytes at approximately this time. It establishes nothing else. In particular, a seal is not evidence that the named model actually performed the computation. A provider that signs a false model identifier produces a cryptographically valid seal carrying a false claim.¶
What the seal changes is the character of that falsehood. An unsigned false claim is deniable; a signed one is a durable, attributable artifact held by the party who was deceived. This document specifies the mechanism that makes such a claim non-repudiable. It offers no mechanism for detecting it, and readers should not treat seal validity as a statement about model behavior, output quality, or safety.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document defines two seals. A pre-seal binds the provider's identity, the model identifier, the exchange identifier, and the request digest, and carries no content. A terminal seal binds the same fields together with the served content.¶
A terminal seal is the artifact that carries the guarantee. A pre-seal exists so that a verifier can perform the checks that do not depend on content before any of the response is rendered: entry selection, entry status, key resolution, model authorization, and freshness. Every failure class in that list is decidable at admission time, and moving them there is the difference between refusing a response and withdrawing one already shown.¶
Which seals a response carries is determined by its transport, as specified in Section 3.8. A non-streamed response carries a terminal seal, because its content is complete when its header fields are emitted. A streamed response carries a pre-seal in its header fields and a terminal seal in its final event, because a signature over complete content cannot exist before the content does.¶
A seal of either type consists of the following fields. All are REQUIRED.¶
ed25519, denoting Ed25519 as specified in [RFC8032].
This field is advisory. A verifier does not select its verification routine
from it; see Section 6.7.¶
Model identity travels as signed plaintext in the model field and is
verified by checking the signature against a public key obtained from the register
entry. It is not derived from, encoded within, or extracted from the signature
value itself.¶
A provider SHOULD name a model at the granularity at which it holds the weights fixed, using a fully qualified snapshot identifier rather than an alias it re-points over time. A coarse identifier weakens the guarantee in proportion to how much it covers: a seal naming an identifier under which the provider serves several variants establishes only that one of them answered. Section 4.2 states the corresponding requirement on register entries.¶
The exchange-id field binds a seal to one exchange. It is supplied by
the requesting party on the request, as specified in
Section 3.8.1, and echoed by the provider into every seal
covering the response.¶
A requesting party that intends to verify MUST generate the value
from a cryptographically secure random source, MUST NOT reuse a
value across exchanges, and MUST retain it for the duration of the
exchange. A provider MUST copy the received value verbatim into
the seals it emits. A verifier MUST treat a seal whose
exchange-id does not equal the value it supplied as invalid.¶
Client supply rather than provider generation is what makes the field useful. A provider-generated identifier distinguishes one exchange from another but is drawn from a space the provider controls, so a captured response replayed to the same client carries an identifier the client cannot recognize as belonging to a different exchange. A value the client generated and remembers is one the client can check. The cost to a provider is echoing a value it already received.¶
The field also binds the two seals of a streamed response to each other. Without it, a pre-seal captured from one exchange and a terminal seal captured from another both validate, and the pair asserts a response that was never served.¶
The request-digest field carries a digest computed by the provider over
the request it received. Its value is the token sha-256= followed by the
SHA-256 digest of the request content, encoded in base64url without padding
[RFC4648]. Over HTTP the request content is the octets of the
request body after removal of any content coding and before any parsing; a
transport binding for another transport states what it is there.¶
A provider MUST compute the digest over the request as received and MUST carry the same value in every seal covering the response.¶
Every other field of a seal describes the response. Without this one, a seal binds a response to a provider and to an exchange but not to the request that provoked it, and an intermediary holding the request in cleartext can substitute its own text, forward it under the exchange identifier the client supplied, and relay a response the provider sealed honestly. Every check in Section 6 passes. The client is shown an attributed answer to a request it did not make, with the attribution machinery of this document vouching for it.¶
Comparison is a reporting obligation rather than grounds for refusal. Intermediaries alter requests for reasons that are ordinary and often disclosed: system instructions, tool declarations, retrieved context, and safety wrappers are all added on the path. Section 6.11 specifies the finding.¶
The canonical payload of Section 3.6 is a line-oriented format in which header fields are separated by LF and the header block is terminated by an empty line. A field value containing LF or CR could therefore introduce additional apparent header lines within the signed region, producing bytes that two conforming implementations parse differently while both compute the same valid signature.¶
Accordingly, the values of register-entry, selector,
alg, model, provider, exchange-id,
request-digest, and signed-at MUST NOT
contain LF (0x0A) or CR (0x0D). A signer MUST reject such a value
rather than sign it, and a verifier MUST treat a seal carrying such
a value as invalid without evaluating its signature.¶
The response content is exempt from this constraint. It is the final element of the terminal-seal payload and its length in bytes is bound in the header block above it, so no content byte sequence can be reinterpreted as a header field. A pre-seal payload carries no content and terminates at its header block.¶
A signature is computed over one of the two byte sequences below, in which
<LF> denotes a single LF octet (0x0A), field values are substituted
verbatim, and content-length is the length of the sealed content in
bytes.¶
The terminal-seal payload is:¶
airp-seal/v1<LF> register-entry:<register-entry><LF> selector:<selector><LF> alg:<alg><LF> model:<model><LF> provider:<provider><LF> exchange-id:<exchange-id><LF> request-digest:<request-digest><LF> signed-at:<signed-at><LF> content-length:<content-length><LF> <LF> <content>¶
The pre-seal payload is:¶
airp-preseal/v1<LF> register-entry:<register-entry><LF> selector:<selector><LF> alg:<alg><LF> model:<model><LF> provider:<provider><LF> exchange-id:<exchange-id><LF> request-digest:<request-digest><LF> signed-at:<signed-at><LF>¶
Field order is fixed as shown and MUST NOT vary. No whitespace surrounds the colon separator. The pre-seal payload ends with the LF that terminates its final field and has no empty line and no content. The terminal-seal payload has no trailing LF after the content. The header block of either payload is encoded as UTF-8; the content of a terminal-seal payload is appended as octets, without re-encoding. The two payloads differ only in their version token and in the presence of the content block, so a single parser serves both.¶
The version token is the first line and is inside the signed region. A payload constructed under a future version cannot be validated as a version 1 payload, and a pre-seal payload cannot be validated as a terminal-seal payload. The distinct tokens are what keep a metadata-only signature from being presented as a signature over content.¶
The alg field is inside the signed region for the same reason. An
algorithm identifier carried alongside a signature rather than within it is
modifiable by any party on the path. Section 8.3 states why the
verifier nonetheless takes its verification routine from the key rather than from
this field.¶
No canonicalization is performed on the content. The signature covers the served bytes exactly as served. A verifier MUST NOT normalize whitespace, line endings, or Unicode representation before verifying.¶
A provider signs a canonical payload with the private key corresponding to the
public key published under the stated selector in its register entry. The value of
signed-at MUST be the time at which that signature was
computed. Where a response carries two seals, each carries its own
signed-at.¶
A provider that declares a sealing policy of all
(Section 4.4) MUST seal every response it serves,
with the seals its transport requires under Section 3.8.¶
Over HTTP, a seal is carried in the AIRP-Seal response header field or in
a terminating event of a streamed body. Its value in either position is the JSON
object representation of the seal fields, encoded in base64url without padding.¶
The JSON member names are registerEntryId, selector,
alg, model, providerIdentity, exchangeId,
requestDigest, signedAt, and signature, corresponding
to the fields of Section 3.2. Member order in the JSON
representation is not significant; the canonical payload of
Section 3.6 is what is signed, and it fixes its own order. A
representation carrying a duplicate member name MUST be treated as
invalid without evaluating its signature.¶
A requesting party that intends to verify MUST carry its
exchange identifier in the AIRP-Exchange-Id request header field. The
value is at least 128 bits drawn from a cryptographically secure random source,
encoded in base64url without padding [RFC4648]. A retried
request is a new exchange and MUST carry a new value.¶
A provider MUST copy the received value verbatim into the
exchange-id field of every seal covering the response. Where the
request carried no such field, the provider MUST set
exchange-id to the empty string. A verifier that supplied a value
MUST treat any other value, the empty string included, as
invalid.¶
A response sealed under an exchange identifier is specific to one request and MUST NOT be stored by a shared cache, since a cache hit delivers to one client a seal echoing another client's identifier, which that client is required to reject.¶
A non-streamed response MUST carry a terminal seal in the
AIRP-Seal header field. The sealed content is the octets of the
response body after removal of any content coding and before any parsing.¶
Content coding is removed before sealing because the encoded octets are frequently unavailable to the party expected to check them. Intermediaries re-encode representations in transit, and the runtimes most likely to host a verifier decompress before application code observes the body and offer no way to recover the encoded form. Sealing the encoded octets would place the signed artifact out of reach of most verifiers and would fail against any intermediary that recompressed an otherwise untouched response. Removal of a content coding is deterministic, so signer and verifier compute the same octets.¶
A seal is carried in the header field only. This document defines no body-carried seal, because removing a member from a JSON body and agreeing on the remaining octets is a canonicalization, and two conforming implementations would disagree about whitespace and separators while both believing they had reconstructed the signed payload.¶
A streamed response MUST carry a pre-seal in the
AIRP-Seal header field and a terminal seal in a terminating event of
the body.¶
Where the body is a server-sent event stream [HTML], the
terminal seal is carried as an event whose data is a JSON object with the
single member airpSeal whose value is the seal object of this
section. That event MUST be the
last event carrying data before the stream's own end-of-stream event, and
MUST follow every event carrying content:¶
data: {"airpSeal": {"registerEntryId": "...", ...}}
data: [DONE]
¶
The sealed content is the concatenation, in the order served, of the octets each event of the stream contributes. A content binding specifies, for one API shape, which member of which event type contributes octets and in what encoding. Every event type a binding does not name contributes zero octets and is not otherwise represented in the payload.¶
A provider serving streamed responses MUST publish a content
binding and MUST name it in the contentBinding member
of its register entry (Section 4.2), so that a verifier
obtains it without prior knowledge of the provider. A verifier that does not
hold the binding an entry names MUST report the response as
unattributed rather than guessing at a delta shape.
Appendix B gives bindings for the event shapes in common
use.¶
An event carrying content MUST NOT follow the terminal-seal event. A verifier MUST treat content arriving after the terminal-seal event as a refusing finding and MUST NOT release it. The requirement is on the verifier and not only on the signer because a verifier that stops accumulating at the seal event validates the signature over the content that preceded it and then releases content no signature covers, which is a working injection against an implementation that believes it verified.¶
A response MUST carry at most one AIRP-Seal header
field and at most one terminal-seal event. A verifier MUST treat
a response carrying more than one of either as invalid rather than selecting
among them.¶
The seal is not carried in an HTTP trailer field. Trailers are inconsistently exposed to the software most likely to verify, and the terminating event costs a verifier nothing it is not already parsing.¶
The integration this requires of a provider is narrow. Streaming continues unchanged, the content accumulated for logging and billing is the content that gets signed, one signature is computed when generation ends, and one event is emitted. Nothing is buffered, no latency is added, and what the client receives mid-stream does not change.¶
A terminal seal covers what was served, as served. Each transport binding defines what that means for the transport it covers, and the definitions differ: the octets of a body in Section 3.8.2, the concatenated deltas of a stream in Section 3.8.3. A provider offering both signs a different artifact in each case. This is correct rather than inconsistent, because a verifier always knows which transport delivered the response it holds.¶
Sealing served bytes rather than text extracted from them binds a seal to the response format, so a provider that changes its response envelope invalidates seals made under the old one, and every API shape requires its own binding statement. That cost is accepted. The alternative delegates extraction to the reader, and two conforming verifiers that disagree about whether tool-call blocks, reasoning blocks, or refusals are assistant text will compute different payloads from identical bytes and reach different verdicts on the same valid signature.¶
A Serving Register document is a JSON object with the following members:¶
1.¶
issuedAt beyond which this document
MUST NOT be used, as specified in
Section 4.6.¶
The document is accompanied by a detached signature over its exact bytes, computed by the registrar.¶
Each entry is a JSON object with the following members:¶
register-entry field and the value a provider publishes in the
e tag of Section 4.7. An identifier
MUST be unique within the document, MUST NOT be
assigned to a different provider, and MUST NOT be reused after
the entry it names is revoked. It MUST consist of characters
drawn from A-Z, a-z, 0-9, hyphen, period, and underscore, so that it carries in
a DNS TXT record without ambiguity against the tag syntax of
Section 4.7.¶
provider field of its seals.¶
active, probationary, or revoked.¶
all or none, as specified in Section 4.4.¶
Each key object carries a selector, a publicKeyPem holding the
public key in PEM-encoded SubjectPublicKeyInfo form [RFC8410], and
a status of current, rotating, retired, or
compromised. A key carrying either of the last two
MUST additionally carry retiredAt, an Internet date/time
string [RFC3339] giving the time from which that status applies.¶
Selectors MUST be unique within an entry and
MUST NOT be reused for a different key. A verifier
MUST accept keys with status current or
rotating. It MUST reject a seal validated under a key
with status compromised, and MUST reject a seal validated
under a key with status retired whose signed-at value is at or
after that key's retiredAt.¶
The two statuses differ in whether they reach backwards, and the difference is
the difference between rotation and theft. A key is retired when its
holder has stopped signing under it in the ordinary course. Seals made before
that time remain valid, because nothing about the rotation suggests the key was
ever held by anyone else. A key is compromised when the provider cannot
vouch for who held it, and every seal made under it fails whatever its date,
because signed-at is asserted by whoever holds the key and a party that
took one can date its output to any time it chooses.¶
A provider rotating a key publishes the new key as rotating alongside the
current key, begins signing under the new selector once the updated
register has propagated, and subsequently marks the old key retired with
a retiredAt no earlier than the last seal it made under that key.
Section 8.7 states what the distinction is worth to a party
holding a seal after the fact.¶
A provider's sealPolicy declares whether the absence of a seal is
meaningful. A value of none means the provider does not commit to sealing,
and an unsealed response is simply unsealed. A value of all means the
provider commits to sealing every response it serves, and an unsealed response
purporting to come from it is therefore invalid on its face rather than merely
unattributed.¶
Under all, every response carries the seals its transport requires:
a terminal seal for a non-streamed response, and a pre-seal together with a
terminal seal for a streamed one. A streamed response carrying a valid pre-seal
and no terminal seal does not satisfy the commitment;
Section 6.2 specifies how a verifier reports it.¶
This is the mechanism by which stripping a seal becomes detectable. Without it, an
attacker able to remove the AIRP-Seal header field would reduce a sealed
exchange to an unsealed one and gain the treatment afforded to providers that
never sealed at all.¶
A verifier obtains a register document and its detached signature by any means, and validates the signature against a registrar public key it holds independently. The registrar identification carried inside the document is descriptive only. A verifier MUST NOT validate a register document against a key obtained from that document.¶
A verifier MUST NOT use a register document whose signature does not validate against an independently held registrar key.¶
Because the document is signed and validated independently of how it arrived, the transport carries no security burden. A register served over HTTPS, retrieved from a transparency log, or shipped with the verifier is subject to the same check.¶
A register document states its own expiry. A verifier MUST NOT use
a document whose issuedAt plus maxAge precedes the present time,
and MUST NOT use a document that carries no maxAge. A
registrar MUST reissue before expiry. The
RECOMMENDED value of maxAge is 3600 seconds.¶
This bound is what gives revocation a propagation time. The revoked
status of Section 4.2 takes effect for a given verifier when
that verifier next obtains a document, and without a stated maximum age there is
no time by which that is guaranteed to have happened. A revoked entry in an
indefinitely cached document is indistinguishable from an active one.¶
Where a verifier holds an expired document and cannot obtain a current one, it MUST treat responses evaluated against the expired document as unattributed rather than falling back to it. This is the same fail-closed disposition Section 4.5 takes toward an unsigned document, and for the same reason: an attacker who can prevent a verifier from refreshing should not thereby extend the life of an entry the registrar has withdrawn.¶
This document does not specify a push mechanism for revocation. A registrar MAY offer one, and a verifier holding a document SHOULD refresh well before expiry rather than at it. Neither substitutes for the bound, which is what makes the worst case finite.¶
A provider binds its own domain to its register entry by publishing a TXT record
at the _airp underscored node name [RFC8552] beneath that
domain. The record value is a sequence of tag-value pairs separated by
semicolons:¶
_airp.provider.example. IN TXT
"v=airp1; e=provider.example.entry;
r=https://register.example.net/airp; p=all;
k=Ur9dK3xQ7mB2vN5cJ8fH1gL4oR6tY0aE3wS7zX9pQ2M"
¶
airp1 and MUST be first.¶
all or none.¶
A record MUST carry v first and
MUST NOT carry a tag more than once. A verifier
MUST ignore tags it does not recognize, so that later tags can be
added without breaking deployed verifiers, and MUST treat a
duplicated tag, or the presence of more than one record carrying
v=airp1, as a lookup that did not complete. A record split across
several character strings is the concatenation of those strings, in order, with
nothing inserted between them.¶
The record is anchored at the provider's identity domain, not at any serving endpoint. A provider serving from many hosts, from a content delivery network, or from infrastructure it does not administer publishes one record under the domain whose name its seals assert.¶
The e and r tags establish which entry a verifier consults and
where to obtain it. They do not establish what that entry contains. A party
holding a registrar signing key can author an entry bearing the identifier the
provider's zone names, populate it with keys of its own, and satisfy a verifier
that checked only those two tags: the identifier matches, the document validates
against the registrar key the verifier holds, and
Section 4.5 places no security burden on how the document
arrived.¶
The k tag is what closes that path. Key material is committed to in the
provider's own zone, so a forged entry requires both the registrar's signing key
and control of the provider's domain, and those are held by different parties.
This restores the property mail authentication obtains by publishing the key
itself: the party whose name is asserted is the party that says which keys are
its own.¶
Only https is permitted in r. A verifier that retrieves a
register document from a r value without operator confirmation
MUST refuse a URI naming any other scheme, and
SHOULD refuse one resolving to a loopback, link-local, or private
address, since the tag is content of a zone the verifier's operator does not
control.¶
Where the DNS record and the register entry both declare a sealing policy and the
two disagree, a verifier MUST apply the policy all if
either source declares it. Where the DNS lookup does not complete, a verifier
MUST apply the policy declared in the register entry and
MUST NOT treat the failed lookup as a declaration of none.¶
The key set digest of an entry is the SHA-256 digest of the concatenation, taken
over every key object of the entry's keys array in ascending byte order
of selector, of the selector, an LF octet, the base64 encoding of the key's DER
SubjectPublicKeyInfo, and a second LF octet. That encoding is the body of the
publicKeyPem member with its armor lines and all whitespace removed. The
digest is encoded in base64url without padding [RFC4648].¶
Key status is outside the digest. A provider commits to which keys are its own, and the registrar retains authority over whether a given key is current, rotating, retired, or compromised, so that a compromise can be acted on without waiting for a zone to change and so that ordinary status changes do not require the provider to touch DNS. Section 8.6 states what a compromised registrar can still do within that division.¶
Where a k tag is available, a verifier MUST compute the
key set digest of the selected entry and MUST treat the response
as unattributed where the two differ. This is a refusing finding.¶
Where no k tag is available, whether because the provider publishes none
or because the lookup did not complete, a verifier MUST NOT treat
the entry as confirmed. Verifiers SHOULD cache observed key set
digests and SHOULD treat the disappearance of a previously
observed k tag as suspicious rather than as a routine update, for the
reason given in Section 8.5.¶
Both artifacts are now in view, and two bodies of existing work bear on them closely enough to be worth addressing before the verification procedure that turns on them. The first is where this design comes from. The second is what a reader who has signed HTTP messages before will ask about first.¶
The correspondence to mail authentication is close enough to be worth stating plainly, both to credit the design and to help implementers reason by analogy.¶
The Provenance Seal corresponds to DKIM [RFC6376]: a detached signature over a message, validated with a key published by the signing party under a selector. This document departs from DKIM deliberately in two places.¶
The first is entry selection. DKIM takes the signing domain from the signature header itself, which is sound in mail because the result feeds an alignment check against a separately obtained identity. This document has no equivalent later alignment step, so Section 6.3 forbids the seal from selecting its own validating entry rather than permitting it and correcting for it afterward.¶
The second is algorithm selection. DKIM signs its algorithm tag as part of the tag
list and takes the algorithm from it. This document signs alg as part of
the canonical payload but takes the algorithm from the key, for the reasons given
in Section 8.3. Signing the field is necessary and not sufficient: it
prevents an attacker from altering the declaration a legitimate signer made, and it
does nothing about a payload the attacker signed entirely, which is the case that
matters once more than one algorithm is registered.¶
The Serving Register corresponds to SPF [RFC7208]: a published statement of which hosts may act for a named party. It is carried as a signed document rather than in DNS because the data is larger, changes more often, and benefits from audit history, while DNS retains the jobs it does well: proof of domain control and the binding of Section 4.7.¶
The declared sealing policy of Section 4.4 corresponds to the published policy of DMARC [RFC7489]. Its function is identical: to convert the absence of authentication from an ambiguous condition into a declared failure.¶
[RFC9421] defines a general mechanism for signing an HTTP message.
A signer names a list of components, header fields and a few derived values such
as the status code, and signs a base string constructed from them. The body is
not a component. A signer covering the body carries its digest in a
Content-Digest field [RFC9530] and names that field in
the component list, so body integrity is reached indirectly.¶
Several elements of this document have counterparts there. The keyid
parameter identifies a key as selector does. The alg parameter
carries an algorithm declaration, and the guidance accompanying it agrees with
Section 8.3 that a verifier should determine the algorithm from the
key rather than from the message. The nonce parameter is available for
the purpose exchange-id serves. A Content-Digest over a request
expresses what Section 3.4 expresses. In each case
[RFC9421] supplies a slot and leaves its meaning to the
application, and the requirements of Section 6.3 and
Section 6.11 are what filling those slots looks like.¶
The departure is streamed content, and it is not a matter of preference. A terminal seal covers the concatenation of the content deltas of a stream, which is not a header field, not the message body as any component names it, and not in existence when the header block is emitted. The signature must therefore travel after the content it covers, which within HTTP means a trailer, and Section 3.8.3 gives the reasons a trailer does not reach the software expected to verify. What remains is a signature carried inside the event stream, over a payload assembled from the stream rather than from the HTTP message. That is not a use of [RFC9421] but a construction wearing its shape, and calling it one would mislead an implementer about what a verifier is entitled to assume.¶
A non-streamed response is a different matter. It could be expressed as a
component list covering Content-Digest and a field carrying the
provenance metadata, and doing so would remove the canonical payload of
Section 3.6 from this document along with the field constraints
that attend it. This document does not take that path because it would leave
two signing constructions in one protocol, one for responses that stream and one
for responses that do not, verified by different code and failing in different
ways. The saving is real and the cost is a seam through the middle of the only
thing the protocol produces.¶
The remaining difference is one of subject rather than mechanism. What is novel here is not how a signature is computed but what a provider is made to assert while computing it: which model produced a response, under which registered identity, against which entry, answering which request. [RFC9421] is silent on all of it, as it should be, and an implementation built on it would still need every field of Section 3.2 and every check of Section 6 defined exactly as they are here.¶
A verifier evaluates a response against the register entry of the provider it intended to contact. The checks below are performed in order. Each check that fails produces a finding; findings marked as refusing mean the response MUST NOT be treated as attributed to the named provider.¶
A response carrying a pre-seal is evaluated twice. The checks that do not depend on content run at admission, against the pre-seal, before any of the response is rendered: entry selection (Section 6.3), entry status (Section 6.4), seal presence (Section 6.5), freshness (Section 6.6), key resolution and signature (Section 6.7), provider identity (Section 6.8), model authorization (Section 6.9), endpoint authorization (Section 6.10), and request digest (Section 6.11). A refusing finding at admission MUST be produced before the first content token is rendered.¶
The same checks run again at completion against the terminal seal, which additionally binds the content. A response carrying only a terminal seal is evaluated once, at completion.¶
The division is what a pre-seal is for. Wrong provider, unregistered model, retired key, revoked entry, unauthorized endpoint, stale timestamp, and missing register entry are all decidable before a token is shown. What remains for the terminal seal is tampering with content, which is the case that warrants interrupting a response already in progress.¶
Where a streamed response carries a valid pre-seal and the stream ends without a terminal seal, the verifier MUST report an incomplete exchange. This is a refusing finding, and it MUST be distinct from unattributed.¶
The two mean different things. An unattributed response is one whose claimed origin failed to verify. An incomplete exchange is one whose origin verified and whose content did not arrive under signature, which is the shape of a connection that dropped as much as it is the shape of an attack. A verifier that collapses them tells a user their provider is not who it claims to be when the truth is that the response ended early.¶
The pre-seal and terminal seal of one response MUST agree in every
field they share. A verifier MUST treat disagreement in any of
register-entry, selector, model, provider,
exchange-id, or request-digest as a refusing finding.¶
The verifier MUST select the register entry from the intended
provider. It MUST NOT select the entry using the
register-entry field of the received seal.¶
Where the verifier holds an identity domain for the intended provider, it
MUST resolve _airp beneath that domain
(Section 4.7) and take the entry identifier from the
e tag, the register location from the r tag, and the key set
digest from the k tag. Where the lookup does not complete, it
MUST use the entry identifier its own configuration holds for that
provider. In neither case does an identifier come from the response.¶
Where the selected identifier appears more than once in the register document,
or appears not at all, the verifier MUST treat the response as
unattributed rather than choosing among entries or falling back to a match on
providerIdentity.¶
Where a seal is present and its register-entry field does not equal the
identifier of the selected entry, the verifier MUST treat the
response as unattributed. This is a refusing finding and is not a direction to
consult the entry the seal named.¶
The requirement exists because a signed object that selects its own validating authority proves only self-consistency. A response naming an entry that legitimately holds the key that signed it produces a seal that verifies perfectly and attests to a provider the user never chose to contact.¶
Where the selected entry is not present in the register, the response
MUST be treated as unattributed. Where the entry carries status
revoked, the response MUST be treated as unattributed
regardless of seal validity.¶
Where no seal is present and the applicable sealing policy is none, the
verifier reports the response as unsealed and continues. This is not a failure.¶
Where no seal is present and the applicable sealing policy is all, the
verifier MUST treat this as a downgrade and the response
MUST NOT be treated as attributed.¶
Presence is evaluated against the seals the response's transport requires under Section 3.8. A streamed response arriving with no header-carried pre-seal is a downgrade at admission. A streamed response whose pre-seal is present and whose terminal seal never arrives is an incomplete exchange (Section 6.2) rather than a downgrade, because the commitment was met at admission and the stream did not finish.¶
Where a seal is present, the verifier MUST evaluate its
signed-at value against the receipt time of the response that carried it.¶
Receipt time is the time the first octet of the response was received, for a pre-seal and for a terminal seal carried in a header field, and the time the last octet was received, for a terminal seal carried in a terminating event. A long generation may separate the two by minutes, and evaluating both against a single instant would either fail the terminal seal of a slow response or admit a stale pre-seal.¶
A seal dated more than a permitted forward skew after its receipt time MUST be treated as invalid. Signing necessarily precedes receipt, so any forward offset is clock disagreement. The RECOMMENDED forward skew allowance is 300 seconds.¶
A seal dated more than a permitted age before its receipt time MUST be treated as invalid. The RECOMMENDED maximum age is 300 seconds.¶
Freshness is evaluated against the receipt time of the response that carried the seal, and not against the verifier's clock at the moment of evaluation. This distinction matters where a verifier re-evaluates a stored exchange: verification of a retained response yields the same verdict later that it yielded at delivery, which would not hold if the comparison were against present time.¶
A seal whose signed-at value is not a parseable Internet date/time string
MUST be treated as invalid.¶
The verifier locates the key in the selected entry whose selector equals the
selector field of the seal. Where no such key exists, the response
MUST be treated as unattributed. Where the located key carries
status compromised, or status retired with a
retiredAt at or before the seal's signed-at, the response
MUST be treated as unattributed (Section 4.3).¶
A signature MUST be validated under the strict verification criteria of [RFC8032], rejecting non-canonical encodings. A verdict that is intended to hold up later cannot depend on which permissive verifier happened to evaluate it.¶
The verifier MUST determine the signature algorithm from the
located key. The publicKeyPem member is SubjectPublicKeyInfo
[RFC8410], which carries an algorithm identifier, and that
identifier selects the verification routine. The verifier
MUST NOT select a verification routine using the alg
field of the seal.¶
Where the alg field of the seal names an algorithm other than the one
the located key carries, the seal MUST be treated as invalid. The
field is verified for agreement; it is never obeyed.¶
The verifier reconstructs the canonical payload of Section 3.6 from the seal fields and, for a terminal seal, the received content, and validates the signature against the located public key under the algorithm the key names. Where the signature does not validate, the response MUST be treated as unattributed.¶
Where the signature validates, the verifier MUST check that the
provider field of the seal equals the providerIdentity member
of the selected entry. A seal naming any other provider MUST be
treated as invalid.¶
The field is what a verifier displays and what an artifact carries into a later dispute, and until this check runs nothing constrains it. A provider can otherwise sign under a name the register does not associate with it and the seal still validates: the signature is good, the entry is the one the verifier selected for its own reasons, and only the identity on the face of the response is unsupported.¶
The verifier MUST check the endpoint actually contacted against the
authorizedEndpoints array of the selected entry. A contacted URI matches
a registered URI when all of the following hold:¶
Matching MUST NOT be performed by string prefix comparison on the
URIs. Such a comparison authorizes a contacted URI of
https://api.example.com/v1evil against a registered URI of
https://api.example.com/v1, because the registered string is a prefix of
the contacted string without being a prefix of its path segments.¶
Where the contacted endpoint does not match and no valid seal is present, the response MUST be treated as unattributed.¶
Where the contacted endpoint does not match and a seal over the response validates against the selected entry, the verifier MUST report the response as relayed rather than unattributed. This is not a refusing finding. The verifier MUST report the contacted endpoint alongside the attribution, so that the party the response passed through is visible to whoever reads the finding.¶
Relay is permitted because forbidding it would make the substitution this document
exists to detect undetectable. Consider a router that a caller asked for model X
and that serves cheaper model Y. It has three options. It can relay no seal, which
trips the downgrade finding of Section 6.5 wherever the policy
is all. It can forge a seal, which it cannot do without the provider's
private key. Or it can relay Y's honest seal, in which case the model
field reads Y, the caller asked for X, and the substitution is on the face of the
response. Only the third path is available to an honest intermediary, and only the
third path exposes a dishonest one. A verifier that refuses relayed seals leaves
the router with nothing to relay.¶
What endpoint authorization gives up here is less than it appears. A detached signature over content survives relay intact, so where a signature validates, the endpoint check is confirming something the signature has already established. The case it still covers is a party holding the provider's private key and serving from an unregistered host, which is key compromise, and against key compromise endpoint matching was never the defense.¶
A verifier that retained the request it sent MUST compute the
digest of Section 3.4 over that request and compare it with
the request-digest field of the seal. Where the two differ, the verifier
MUST report that the request was modified in transit, and
MUST report it distinctly from every other finding of this
section.¶
This is a reported finding and not a refusing one. The response is attributable, the provider sealed what it in fact received, and the modification may be one the user's own deployment performs deliberately. What the finding establishes is that the answer replies to something other than what was asked, which is a fact the asking party is entitled to and which no other check here surfaces.¶
IANA is requested to register the following entry in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" defined in [RFC9110]:¶
IANA is further requested to register:¶
Deployments predating this document carry a seal in a field named
X-AIDP-Seal or AIDP-Seal, under the protocol's former name.
Neither is registered. X-AIDP-Seal is deprecated in accordance with
[RFC6648], and both are superseded by AIRP-Seal.¶
A verifier MAY accept either legacy field name for compatibility
with existing deployments and MUST prefer AIRP-Seal where
more than one is present. A seal carried under a legacy field name signs a
canonical payload whose version token is aidp-seal/v1 rather than
airp-seal/v1. A verifier that accepts such a seal
MUST validate it against that token and
MUST NOT validate a payload carrying one token as though it
carried the other.¶
IANA is requested to register the following entry in the "Underscored and Globally Scoped DNS Node Names" registry defined in [RFC8552]:¶
IANA is requested to create a registry titled "AIRP Provenance Seal Algorithms",
with registration policy Specification Required [RFC8126]. Each
entry carries an algorithm token, a reference, and a status of either
current or deprecated.¶
The initial contents are:¶
| Token | Status | Reference |
|---|---|---|
| ed25519 | current | This document, [RFC8032] |
A registrar MUST NOT publish a key whose SubjectPublicKeyInfo names
an algorithm absent from this registry or present with status
deprecated, and a verifier MUST treat a seal validated
under such a key as invalid. The registry constrains the algorithms that may
appear in register entries. It does not constrain the alg token of a
seal, which under Section 6.7 selects nothing.¶
A valid seal establishes that a holder of a registered private key asserted the named model, the named provider identity, and the given time over exactly the received bytes. It does not establish that the named model performed the computation. Section 1.2 states this limit; it bears repeating here because the failure mode is a verifier that reports a valid seal in language a user reads as a guarantee about what answered them.¶
Implementations SHOULD surface seal validity in terms of attribution rather than in terms of correctness, safety, or identity of computation.¶
Section 3.5 forbids LF and CR in header field values. The constraint is enforced at signing rather than only at verification so that an ambiguous payload cannot be produced at all. A signer that enforces the constraint only on verification remains capable of emitting a seal that a divergent parser reads differently, which is the condition the constraint exists to prevent.¶
Section 6.7 requires that the verification routine be chosen
from the located key rather than from the alg field of the seal. The
field travels with the signature and is therefore modifiable by any party that can
modify the signature, which is every party on the path. A verifier that selects
on it lets an attacker choose the algorithm under which the attacker's own bytes
are checked.¶
This is the algorithm confusion pattern familiar from signed-token formats that place the algorithm identifier in an attacker-modifiable header. Its consequences range from denial of service, where a rewritten token names an algorithm the verifier does not implement, to forgery, where the named algorithm admits a cheaper proof than the one the signer used. This document registers a single algorithm today, so only the first is presently reachable. The second becomes reachable the moment a second algorithm is registered, and by then the deployed verifiers are already written.¶
SubjectPublicKeyInfo [RFC8410] carries an algorithm identifier as part of the key. Taking the routine from there costs a verifier nothing and removes the choice from the wire entirely.¶
The alg field remains in the seal and inside the signed region. It
declares what the signer used, and disagreement between it and the key is a signal
worth acting on, which is why Section 6.7 makes that
disagreement invalidating. It is checked rather than followed.¶
A seal is bound to its content, so a captured seal cannot be attached to different
content. Replay of a response together with its original seal is addressed by the
exchange-id field of Section 3.3: a verifier that
generated the value it supplied recognizes a seal echoing a different one, and a
replayed response therefore fails against the exchange it is replayed into.¶
This holds only where the requesting party generates the value unpredictably and does not reuse it. A verifier that reuses an identifier, or accepts one the provider chose, retains the exposure the field exists to remove, and the freshness bounds of Section 6.6 are then all that limits the window.¶
The residual case is replay to a party that is not the verifier holding the exchange identifier. A response and its seals remain a valid, attributable artifact wherever they are carried, which is a property this document intends: a seal is durable evidence, and durable evidence can be shown to anyone. What such a third party learns is that the named provider served these bytes in some exchange, not that it served them in the exchange the third party is looking at.¶
The declared sealing policy is the sole defense against seal stripping, and it is
only as reliable as the verifier's ability to learn it. An attacker positioned to
strip the AIRP-Seal field is frequently also positioned to interfere with
the verifier's DNS resolution.¶
Section 4.7 therefore requires that a failed DNS lookup fall back
to the register-declared policy rather than to none. Verifiers
SHOULD additionally cache observed policy declarations and
SHOULD treat a transition from all to none for a
previously observed provider as suspicious rather than as a routine update.¶
The same reasoning covers the k tag. An attacker who suppresses DNS
resolution removes the confirmation of Section 4.8 and
returns the verifier to trusting a register document on the registrar signature
alone, which is why a previously observed k tag that stops appearing is
treated as a signal rather than as an absence. Plaintext DNS bounds what this
costs: an attacker on the resolution path can deny attribution and can raise
false alarms, and can forge neither a seal nor an entry.¶
A registrar signing key is a trust anchor. Its compromise would otherwise permit forged entries, and forged entries permit attribution of arbitrary responses to arbitrary providers. The key set digest of Section 4.8 is what prevents it. An attacker holding a registrar key can author an entry bearing a provider's identifier, but cannot make keys of its own match the digest that provider publishes in its own zone.¶
What a registrar key alone still reaches is everything the digest does not cover.
It can mark an active entry revoked, denying attribution to a provider
serving honestly. It can restore a key the provider had retired, which is why
Section 4.3 makes compromised the status a provider reaches
for whenever a key may be in other hands, since that status is retroactive and
restoring it does not un-sign anything. It can widen
authorizedEndpoints or models, neither of which confers an
ability to sign. And it can forge entries outright for any provider publishing no
k tag.¶
Verifiers SHOULD distinguish, in whatever they report, between an entry confirmed by a key set digest and an entry taken from a register document alone.¶
This document does not specify registrar governance, registrar accreditation, or the process by which a provider obtains an entry.¶
Section 1.2 describes a seal as a durable artifact held by the party that was deceived. That durability is bounded by the register, which states only its present contents. A verdict reached today against today's register is not necessarily the verdict the same seal draws a year from now against the register of that day, and the party with an interest in the later verdict is frequently the party that publishes the entry.¶
The retiredAt of Section 4.3 narrows this. Ordinary rotation
no longer disturbs seals that predate it, which removes the largest routine cause
of a verdict changing under a seal that nothing was ever wrong with. What remains
is that a provider may report a key compromised, which invalidates its
output retroactively and by design, and a third party holding a seal cannot
distinguish a genuine compromise from a convenient one. A verifier that intends
to rely on a finding later SHOULD retain the verdict it reached at
receipt together with the register document it reached it against, rather than
re-deriving the verdict from a register obtained afterward.¶
Attribution that holds against a party able to rewrite its own register requires the register's history to be independently observable. This document does not specify that. An append-only transparency log over register documents would supply it, and is left to future work.¶
A verifier that resolves _airp beneath a provider's domain before or
during an exchange discloses to its resolver, and to any observer of that
resolution, which inference providers a user is contacting and approximately when.
The DNS queries are a side channel that the exchange itself, carried over TLS to a
possibly shared host, may not otherwise expose.¶
Verifiers SHOULD cache DNS bindings for their full TTL rather than resolving per exchange, and SHOULD use encrypted DNS transport where available.¶
The register document itself carries no user-specific information, and retrieving it in full rather than querying for individual entries avoids disclosing which entries a verifier is interested in. Verifiers SHOULD retrieve whole register documents for this reason.¶
The digest of Section 3.4 establishes that the request the provider sealed over is or is not the request the client sent. It establishes nothing about what the provider did with it, and it is computed by the provider, so it is worth exactly what the rest of the seal is worth: a signed assertion by a registered key holder, non-repudiable and unverified.¶
A verifier that did not retain its request cannot perform the comparison, and the field is then inert. Verifiers SHOULD retain the digest of each request they send for the life of the exchange, which costs no more than the exchange identifier already costs and is what makes the check available at all.¶
The endpoint check of Section 6.10 establishes that the contacted URI is one the provider registered. It is not a substitute for authenticating the connection. A verifier MUST continue to perform normal TLS server authentication; a registered endpoint reached over an unauthenticated connection offers no assurance.¶
This section records the implementation status of this document in accordance with [RFC7942].¶
An open-source implementation of the verification procedure of Section 6 is available at [ADVOCATE] under the Apache License 2.0. It implements entry selection including the resolution of Section 4.7, entry status, seal presence and policy, freshness, key resolution and key status, signature validation, provider identity, model authorization, endpoint authorization, and the request digest comparison, together with the key set digest of Section 4.8 and both content bindings of Appendix B. It refuses content arriving after a terminal-seal event. Each of those requirements is covered by a test. It ships a mock signing provider so that the signing half is exercisable without a cooperating provider, over both the streamed and the non-streamed transport, and it exercises both.¶
Two demonstration provider identity domains publish the record of
Section 4.7, each carrying a k tag, and the register
document is served at the r location with its detached signature
alongside. The implementation parses the records, refuses unsafe r
targets, computes the key set digest of Section 4.8 over the
selected entry, and compares it with the published tag. Entries that set no
identity domain are reported as unconfirmed, which is the condition
Section 8.6 requires be distinguishable from a confirmed one.
The implementation does not yet retrieve the register over HTTPS.¶
The digests published for the two demonstration domains were computed independently from Section 4.8 by a second party and agreed with the implementation's own values, which is some evidence that the construction is specified unambiguously.¶
No part of the implementation signs or verifies a pre-seal. The canonical pre-seal payload of Section 3.6 is built and tested, and the streamed parser recognizes and skips a pre-seal event, but the agreement checks of Section 6.2 are not implemented.¶
The deployed Serving Register described in Section 4.5 does not exist. The implementation loads a signed register document from local storage and validates it against a pinned registrar key, which is the same check a retrieved document receives. No production register, registrar, or accreditation process is operating.¶
No inference provider known to the author currently signs its responses. Every response reaching a deployed verifier today is unsealed, which this document treats as a reportable condition rather than an error precisely so that verification can be deployed before signing exists.¶
This section is to be removed before publication as an RFC.¶
This appendix gives two content bindings, covering the two server-sent event
[HTML] shapes in widest use among inference APIs at the time of
writing. They exist so that implementations have something concrete to interoperate
on before providers publish bindings of their own. Neither is normative. A provider
serving one of these shapes adopts the corresponding binding by naming its
identifier in the contentBinding member of its register entry.¶
The event framing itself does not vary between them, since it is fixed by
[HTML]: a run of data lines terminated by an empty line,
with the values of several such lines within one event joined by LF. What varies is
the shape of the JSON object those lines carry, and that is the whole of what a
binding specifies.¶
Neither binding covers anything other than assistant text. Tool call arguments, reasoning or thinking output, and every other member of a delta contribute no octets and fall outside the sealed content, which means a seal made under either binding says nothing about them. A provider whose responses carry such members and that intends to seal them publishes a further binding under a further identifier rather than extending one of these, because a verifier holding one of these identifiers will not include them, and two verifiers disagreeing about what a binding covers is the failure Section 3.8.4 exists to prevent.¶
In both bindings, an event that contributes nothing contributes zero octets and appears nowhere in the payload, and content is concatenated in the order served without regard to any index or block a delta may name.¶
Binding identifier: sse-chat-delta-v1.¶
This binding covers the shape in which each event carries a JSON object holding an array of choices and each choice holds a delta object.¶
An event contributes the octets of the UTF-8 encoding of the string value of the
content member of the delta object of the first element of the
choices array of its data object, where each of those members is present
and the value is a string. Every other event contributes nothing: an event whose
data is not a JSON object, an event carrying no choices array, an event
whose delta carries no content member or carries one that is not a
string, the terminal-seal event of Section 3.8.3, and the
end-of-stream event.¶
Binding identifier: sse-content-block-delta-v1.¶
This binding covers the shape in which each event's data object carries a
type member naming the kind of event, text arrives on events of type
content_block_delta, and a stream carries further event types marking
the start and end of the message and of each content block.¶
An event contributes the octets of the UTF-8 encoding of the string value of the
text member of the delta object of its data object, where the
data object's type is content_block_delta, the delta object's
own type is text_delta, and the text member is present
and is a string. Every other event contributes nothing: an event whose data is
not a JSON object, an event whose data object carries any other type,
an event whose delta carries any other type, the terminal-seal event of
Section 3.8.3, and the end-of-stream event.¶
Selection is on the type member of the data object rather than on the
event field of the SSE framing. A stream of this shape ordinarily
carries both, and the two agree, but only the data object is inside what a seal
covers, and a binding that read the framing would let an intermediary alter the
selection without altering a sealed octet.¶
The architecture from which these two mechanisms are drawn is described in [AIRP].¶