Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3aa5780
fix(trogon-aauth-verify): make a verified signature cover the body it…
yordis Aug 7, 2026
d7362d9
fix(trogon-aauth-verify): honor what a publisher says its key is for
yordis Aug 7, 2026
160af04
chore(trogon-aauth-verify): describe the replay store that exists, no…
yordis Aug 7, 2026
bae20cf
fix(trogon-identity-types): keep private key material out of a publis…
yordis Aug 7, 2026
bad87b2
fix(trogon-gateway): make webhook dedup survive a replayed delivery
yordis Aug 7, 2026
d17554d
fix(a2a-gateway): refuse to run unverified bundles when verification …
yordis Aug 7, 2026
837fb0b
fix(a2a-redaction): hold bundle signatures to the strict check
yordis Aug 7, 2026
d00735c
fix(a2a-auth-callout): stop a token from choosing the algorithm that …
yordis Aug 7, 2026
8cf3fd8
chore(adr): record the external federation surface and correct what t…
yordis Aug 7, 2026
67e4f5c
fix(a2a-identity-types): stop a replayable credential from reaching a…
yordis Aug 7, 2026
6181ed4
fix(a2a-auth-callout): stop a replayable credential from reaching a log
yordis Aug 7, 2026
0487fb1
fix(trogon-jwks-publisher): refuse a key set no consumer can select from
yordis Aug 7, 2026
87123bc
fix(trogon-gateway): keep a replay bound from lapsing on an already-p…
yordis Aug 7, 2026
94a9172
chore(adr): keep the federation issuer key out of agent key management
yordis Aug 7, 2026
93f15ab
chore(adr): stop offboarding from reading as complete at the platform…
yordis Aug 7, 2026
192caa3
fix(trogon-nats): stop stream provisioning from overwriting what the …
yordis Aug 7, 2026
373a9df
fix(a2a-auth-callout): let the two key-policy refusals be counted apart
yordis Aug 7, 2026
9df7333
fix(trogon-identity-types): keep an unusable confirmation key out of …
yordis Aug 7, 2026
98f63a3
fix(trogon-aauth-verify): accept the Content-Digest shapes RFC 9530 a…
yordis Aug 7, 2026
1a275c4
chore(a2a-auth-callout): check every segment of the token the redacti…
yordis Aug 8, 2026
6918938
fix(trogon-identity-types): stop a peer's private key from reaching t…
yordis Aug 8, 2026
21eda5a
chore(trogon-jwks-publisher): make an unselectable published key set …
yordis Aug 8, 2026
d6dea94
chore(trogon-nats): let a test name the provisioning refusal it asked…
yordis Aug 8, 2026
07239d8
fix(trogon-nats): stop provisioning from being able to widen what it …
yordis Aug 8, 2026
fb1f50d
fix(trogon-nats): stop reconciliation from rewriting a stream that al…
yordis Aug 8, 2026
3a5bc0f
chore(adr): keep the crypto suite's federation reference navigable
yordis Aug 8, 2026
24da38c
fix(trogon-channel): stop bucket provisioning tests from resting on w…
yordis Aug 8, 2026
99167e9
chore(trogon-aauth-verify): pin the digest member this reader refuses…
yordis Aug 8, 2026
43f7533
fix(trogon-jwks-publisher): keep a set's rejection reason inside the …
yordis Aug 8, 2026
49cbd7a
chore(a2a-auth-callout): pin the RSA-only refusal the new key guards …
yordis Aug 8, 2026
19fe392
chore(trogon-aauth-verify): pin the bare-key member this reader has t…
yordis Aug 8, 2026
b771fb5
fix(trogon-aauth-verify): stop a bare sha-256 member from leaving las…
yordis Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions docs/adr/0017-aauth-agent-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,30 @@ the tier-1 SpiceDB layer already uses. Optional tuning
when unset, but an unparseable value is still a startup error rather than a
silently-ignored default.

### 7. Discovery mode accepts any HTTPS issuer unless an allowlist is pinned

`iss` is read from an unverified JWT claim in order to locate the key that
will verify it, so in discovery mode it is attacker-influenced input that
drives an outbound fetch. `HttpJwksResolver` bounds the damage structurally:
HTTPS only, DNS hosts only (IP literals and loopback names rejected outright
as SSRF attempts), a request timeout, and a streamed response-size cap that
holds even when `Content-Length` is absent or lies.

What it does not do by default is bound *which* issuers may be resolved at
all. `A2A_GATEWAY_AAUTH_JWKS_ALLOWED_ISSUERS` takes a comma-separated exact
list (trailing slashes ignored) and rejects any other `iss` before a network
call is made; when it is unset, any issuer that is HTTPS-reachable and serves
a parseable well-known document can mint a token this gateway will
successfully verify. That default is deliberate and follows from the draft's
self-sovereign premise, that an agent needs no pre-registration, but it means
**verification success carries no admission decision on its own**. The
authority plane of [ADR#0037](./0037-agent-identity-governance.md) is what
decides whether a verified-but-unknown agent may act, and a deployment that
knows its federation partners should pin them here rather than rely on that
plane alone. This variable is part of the fail-loud inventory above only in
the sense that a malformed value is rejected; being unset is a valid,
documented posture, not a misconfiguration.

## Consequences

- Agents authenticate to the gateway with a self-sovereign, key-bound identity
Expand All @@ -219,11 +243,24 @@ silently-ignored default.
alongside whatever auth token the Person Server issued.
- `-32118` is now reserved on the JSON-RPC-over-NATS error surface for AAuth
denials specifically; no other gateway error path may reuse it.
- Replay protection today is `InMemoryReplayStore`, process-local. A
multi-node gateway deployment can have the same nonce accepted once per
node until a shared store (NATS [JetStream](../glossary/jetstream) KV, per the doc comment in
`trogon-aauth-person`) is wired in; single-node deployments are fully
protected, multi-node deployments are not yet.
- Replay protection today is `InMemoryReplayStore`, process-local, and it is
the *only* `ReplayStore` implementation in the workspace. A multi-node
gateway deployment has the same nonce accepted once per node; single-node
deployments are fully protected, multi-node deployments are not. A shared
store (NATS [JetStream](../glossary/jetstream) KV keyed with a per-key TTL
is the intended backend) has not been built. `AAuthIngress` is generic over
`S: ReplayStore` and `ReplayError::Backend` is reserved for it, so adding
one is a construction change rather than a signature change.
- Verification success is not admission. In discovery mode with no issuer
allowlist pinned (Decision 7), any HTTPS-reachable issuer can mint a token
that verifies. Deployments that rely on AAuth as an authorization boundary
rather than an authentication one are misreading it; that boundary is
[ADR#0037](./0037-agent-identity-governance.md)'s authority plane.
- The three algorithms this ADR admits (ES256, ES384, EdDSA) are the whole
verifier allowlist, and none of them is RS256. Federating this platform's
identities *out* to an external OIDC-consuming IdP is therefore not
reachable from this surface; that boundary is
[ADR#0053](./0053-external-oidc-federation-surface.md).
- JWKS resolution is env-selected between a static file (`StaticJwks`) and
live `.well-known/{dwk}` discovery (`HttpJwksResolver`, HTTPS-only, size-
and timeout-capped, wrapped in `CachedJwksResolver`). Static deployments
Expand Down
5 changes: 5 additions & 0 deletions docs/adr/0036-agent-self-certifying-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ These do not change the genesis anchor and are added when needed:
replicated substrate (relays, a DID method, a transparency log) so parties
outside this system can resolve it. Until then the agent's event stream is
the authoritative key record.
[ADR#0053](./0053-external-oidc-federation-surface.md) settles the narrower
external-verifier case with an OIDC issuer and JWKS, which is what
non-AAuth parties actually consume; it does not close this layer, because
resolving the *identifier* is the part that still needs a DID method.
- **Key rotation**: a later signed event authorized by the current key, with
the identifier held stable by the resolution layer above. Pure self-certifying
identifiers are immutable, so rotation and resolution arrive together.
Expand Down Expand Up @@ -129,5 +133,6 @@ These do not change the genesis anchor and are added when needed:
- [ADR#0037: Agent Identity Governance: Decentralized Verification under Governed Authority](./0037-agent-identity-governance.md)
- [ADR#0038: Agent Identity Cryptographic Suite and Crypto-Agility](./0038-agent-identity-crypto-suite.md)
- [ADR#0039: Self-Authenticating Event Provenance](./0039-self-authenticating-event-provenance.md)
- [ADR#0053: External OIDC Federation Surface for Agent Identity](./0053-external-oidc-federation-surface.md)
- [Buzz: Nostr-based agent identity](https://github.com/block/buzz)
- [ADR index](./index.md)
70 changes: 64 additions & 6 deletions docs/adr/0038-agent-identity-crypto-suite.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,13 @@ decision this ADR makes: it is what lets a broken algorithm be survived by
rebinding a new key and rotating verifier policy rather than by redesigning
the identity format itself.

### 4. Conditional profiles, added as bound keys, never as root replacements
### 4. Conditional profiles, each with its trigger, never as root replacements

Two further curves are recorded as conditional profiles, each with the
trigger that would justify adopting it. Either arrives as an additional key
bound to the agent's identity through the stream-attested binding of
Decision 3, never as a replacement for the Ed25519 root:
Three further profiles are recorded as conditional, each with the trigger that
would justify adopting it. None of them replaces the Ed25519 root. The two
curve profiles arrive as an additional key bound to the agent's identity
through the stream-attested binding of Decision 3; the third is not agent-bound
at all, and its entry records where it lives instead:

- **P-256 (ES256)**, carried in [COSE](https://www.rfc-editor.org/rfc/rfc9052)
([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052) and
Expand All @@ -155,8 +156,50 @@ Decision 3, never as a replacement for the Ed25519 root:
is a governance question, recorded as a stance in
[ADR#0037](./0037-agent-identity-governance.md), not a decision this ADR
makes on its own.
- **RSA-2048 with RS256**, only where an external identity provider's
federation surface refuses everything else. This trigger is already met in
practice rather than hypothetical: Microsoft Entra's workload identity
federation supports only RS256-signed issuers, and additionally requires
that the published JWK set contain *nothing but* RSA signing keys, so EC
and OKP keys cannot merely sit alongside an added RSA one. Every algorithm
Decision 1 and the two profiles above name is excluded by that constraint.
This profile is adopted for one purpose only, signing assertions presented
to a third-party IdP, and it never becomes an agent's root anchor. It is
also the one profile here that is *not* bound to an agent identity through
Decision 3: the key is signing material belonging to the separate federation
issuer of [ADR#0053](./0053-external-oidc-federation-surface.md), whose
custody and rotation schedule are the issuer's and are governed apart from
agent key management. The agent identity an assertion speaks for travels in
that assertion's claims, not in the key that signs it, which is what keeps
one issuer key able to serve many agents without becoming any of their
anchors. The "only RSA keys" requirement additionally forces a *separate*
published key set rather than an extra key in the AAuth well-known
documents, which is why the surface itself is decided in
[ADR#0053](./0053-external-oidc-federation-surface.md) rather than here.

### 5. Post-quantum path
### 5. Where the implementation currently stands, and where it diverges

Decision 1 names Ed25519 as the default and the two curves above as
conditional. The shipped code does not match that yet, and the divergence is
recorded here rather than left to be discovered:

- `trogon-aauth-verify` admits `ES256 | ES384 | EdDSA`, which is the
allowlist Decision 3 requires and a superset of the default. Its PoP path
derives the algorithm from the confirmed key's own `kty`/`crv` rather than
from the presented header, so algorithm confusion is structurally
unavailable there, not merely filtered.
- `trogon-jwks-publisher` is P-256 only. `jwk_from_ec_pkcs8_pem` hardcodes
the curve and the Agent Provider mints with `ES256`. The Agent Provider can
therefore issue the *conditional* profile and cannot issue the declared
default. Closing this means adding Ed25519 issuance to the publisher, not
changing the decision above: P-256 issuance stays supported under its
hardware-custody trigger.

Nothing about this divergence is load-bearing for the anchor, precisely
because Decision 3 makes every key and signature name its own algorithm. It
is recorded as a gap to close, not as an amendment to the default.

### 6. Post-quantum path

Every elliptic-curve scheme named above, Ed25519, P-256, and secp256k1 alike,
falls to Shor's algorithm on a cryptographically relevant quantum computer;
Expand All @@ -182,6 +225,19 @@ threat timeline, not an emergency response to an already-broken algorithm.
presumes one fixed curve in the identifier itself, are rejected. Identifiers
and keys in this platform are always algorithm-tagged, so an identifier
never has to be reinterpreted if the algorithm it names is later retired.
- The verifier's allowlist is now enforced on three axes, not one: the key's
material (`kty`/`crv`) must match the algorithm's family, and where a
publisher declares `use`, `key_ops`, or `alg` on a JWK
([RFC 7517](https://www.rfc-editor.org/rfc/rfc7517) sections 4.2 to 4.4)
those declarations are honored, so a key published for encryption or pinned
to a different algorithm is not conscripted into signature verification
because its curve happens to line up. Absent members stay permissive, since
they are optional in the RFC and a federated deployment resolves key sets
this platform did not publish.
- Adopting the RSA profile of Decision 4 means operating a second published
key set with its own rotation, because the IdP constraint that triggers it
forbids mixing key types in one document. That cost is the reason the
profile is conditional rather than default.
- Private key custody is unaffected by this ADR and remains on the security
plane defined by [ADR#0023](./0023-secret-management-and-key-custody-direction.md)
and [ADR#0033](./0033-two-tier-key-custody-product-model.md). This ADR
Expand All @@ -197,8 +253,10 @@ threat timeline, not an emergency response to an already-broken algorithm.
- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md)
- [ADR#0037: Agent Identity Governance](./0037-agent-identity-governance.md)
- [ADR#0039: Self-Authenticating Event Provenance](./0039-self-authenticating-event-provenance.md)
- [ADR#0053: External OIDC Federation Surface for Agent Identity](./0053-external-oidc-federation-surface.md)
- [RFC 6979: Deterministic Usage of DSA and ECDSA](https://www.rfc-editor.org/rfc/rfc6979)
- [RFC 7515: JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515)
- [RFC 7517: JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517)
- [RFC 7638: JSON Web Key (JWK) Thumbprint](https://www.rfc-editor.org/rfc/rfc7638)
- [RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)](https://www.rfc-editor.org/rfc/rfc8032)
- [RFC 8037: CFRG Elliptic Curve Diffie-Hellman and Signatures in JOSE](https://www.rfc-editor.org/rfc/rfc8037)
Expand Down
7 changes: 7 additions & 0 deletions docs/adr/0049-revocation-latency-target.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ in dashboards.
- The cache TTL plus jitter (at most 330 seconds) is the hard upper bound on
staleness when the event path fails entirely; the alert on the event path
exists precisely so the backstop is never the operative mechanism.
- The target is scoped to credentials this platform resolves. It does not
extend to standing that has been federated to an external identity
provider, which never consults this platform's revocation state; that
boundary is bounded by assertion TTL plus the remote provider's own cache
and is decided in
[ADR#0053](./0053-external-oidc-federation-surface.md). An offboarding that
must hold on both planes is not complete when this histogram says it is.
- The numbers are working values. They are revisited once production stream
metrics exist, and any change lands as an amendment to this ADR.

Expand Down
Loading
Loading