Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/identity/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ A concrete example that illustrates several of these challenges is Palo Alto Net
| 1 | **Chain Integrity**<br/>*Can the full delegation chain be named and verified end-to-end?* | A single agent task can chain through five to ten identities: user → orchestrator → sub-agent → tool → downstream API. Two failures compound: the chain is not *named* (audit collapses into "the agent did it"), and the chain is not *verifiable* (downstream resources cannot confirm the chain without trusting every intermediary). In practice the full chain is often not reconstructable end-to-end, and there is a known class of attacks where the chain itself can be spliced. | **[OAuth 2.0](https://www.rfc-editor.org/rfc/rfc6749) access tokens** identify the bearer, not the chain.<br/>**[OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) ID tokens** name the human at the start but lose granularity at each hop.<br/>**[SPIFFE SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/)** identify the workload but not which call within it.<br/>**[OAuth Token Exchange (RFC 8693)](https://www.rfc-editor.org/rfc/rfc8693)** defines nested `act` claims that can express a chain, but per [§4.1](https://www.rfc-editor.org/rfc/rfc8693#section-4.1) *"prior actors identified by any nested `act` claims are informational only and are not to be considered in access control decisions."* The chain is recorded, not normatively enforceable.<br/>**[OAuth Identity Chaining draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-chaining/)** and **[Transaction Tokens draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-transaction-tokens/)** address parts but specify neither chain verification rules nor revocation propagation as required runtime behavior.<br/>**[W3C Trace Context](https://www.w3.org/TR/trace-context/)** correlates but [does not authenticate](https://www.w3.org/TR/trace-context/#privacy-of-traceparent-field). | Every action must produce, at the moment of execution, a signed record that names the specific link in the chain and lets any participant (including the downstream resource) verify the entire chain end-to-end without trusting any intermediary. Cryptographic chain structure, mandatory scope attenuation at every hop, revocation propagation. | Pending |
| 2 | **Over-Privilege**<br/>*Was this action within the agent's purpose?* | Agents inherit broad standing privileges. Service accounts. Long-lived API keys. OAuth scopes designed for human applications. One compromised agent has the union of every permission anyone has ever granted to it. The fix is not bigger scopes; the fix is no standing privilege. | **[OAuth scopes (RFC 6749 §3.3)](https://www.rfc-editor.org/rfc/rfc6749#section-3.3)** are coarse, deployment-defined strings (e.g. `read`, `write`, `repo`). Per **[RFC 9396 §1](https://www.rfc-editor.org/rfc/rfc9396#section-1)**, scope is "sufficient... for coarse-grained authorization requests" but "not sufficient to specify fine-grained authorization requirements" — it does not express per-action, per-resource, per-data-cell intent.<br/>**RBAC and ABAC** scope by role or attribute, granted once, evaluated coarsely.<br/>**AIMS** does not require **[RAR (RFC 9396)](https://www.rfc-editor.org/rfc/rfc9396)** and treats scope semantics as deployment-specific. | Credentials minted just before the call, scoped to one downstream callee, structured (per-action, per-resource), expired in minutes. Authorization evaluated per-call against the immutable intent for the run, not per-session. | Pending |
| 3 | **Token Theft Resistance**<br/>*If a token leaks from agent memory, is it useless to the attacker?* | The agent threat model breaks OAuth's bearer-token assumption entirely. Prompt injection can exfiltrate tokens from memory. Tool outputs can leak tokens. Sub-agents can replay tokens. Logs and crash dumps capture tokens. Long-lived refresh tokens in agent memory are the worst case: a durable credential held in storage that any indirect prompt injection can read. | **[OAuth 2.0 Bearer Tokens (RFC 6750 §1.2)](https://www.rfc-editor.org/rfc/rfc6750#section-1.2)** explicitly treat possession as sufficient: *"Using a bearer token does not require a bearer to prove possession of cryptographic key material."*<br/>**API keys** are bearer secrets by definition.<br/>**[OAuth refresh tokens (RFC 6749 §1.5)](https://www.rfc-editor.org/rfc/rfc6749#section-1.5)** were designed for the same user returning later to resume a session, which does not describe how agents work.<br/>**[DPoP (RFC 9449)](https://www.rfc-editor.org/rfc/rfc9449)** and **[OAuth 2.0 Mutual-TLS Certificate-Bound Access Tokens (RFC 8705)](https://www.rfc-editor.org/rfc/rfc8705)** solve sender-constraint but are optional extensions that most deployments do not enforce.<br/>None of these are required by the OAuth core. | All non-trivial tokens sender-constrained (DPoP or mTLS) so a leaked token is useless without the corresponding private key. Aggressive lifetime ceilings measured in minutes or seconds, not hours. No long-lived secrets in model-visible context. Credentials fetched at task start, used within a tight window, discarded at task end. | Pending |
| 4 | **Last-Mile Enforcement**<br/>*Did the resource actually enforce the decision, on every call, inside the runtime?* | The moment the action reaches a production system. All upstream identity work either holds here or fails here. Most systems today cannot distinguish an authorized action by an agent acting in good faith from the same call by an agent hijacked by indirect prompt injection. Gateway-only enforcement misses tool calls and sub-agent invocations that never cross a gateway. Cached decisions miss revocation, scope change, and freshly-discovered taint. | **API gateways and service meshes** enforce at the edge; agent tool calls never reach them.<br/>**[OAuth 2.0 resource servers (RFC 6749 §1.1)](https://www.rfc-editor.org/rfc/rfc6749#section-1.1)** validate the token but not the action's relationship to intent.<br/>**[mTLS (RFC 8705)](https://www.rfc-editor.org/rfc/rfc8705)** and **[SPIFFE](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/)** establish workload identity but do not enforce per-action policy.<br/>None fire inside the agent runtime where the decision is made. | Enforcement fires at every tool call, inside the agent runtime, before the action executes. The runtime verifies identity, scope, taint, freshness, and intent on every call. The LLM call cost dwarfs the re-authorization cost, so caching decisions is not required for performance. Default-deny verified by a negative test in CI. | **Partially specified.** The ACS Instrument spec already defines the honoring-decisions contract in [§6.4 (Honoring Decisions, Normative)](../spec/instrument/specification.md#64-honoring-decisions-normative): the Observed Agent MUST wait for the Guardian's decision up to the negotiated timeout, MUST apply it, and MUST record every fail-open proceed as an audit event. Note the spec's current default on timeout is **fail-open with audit**, which is in tension with this row's default-deny / no-cache goal; whether identity-tier actions should tighten that to fail-closed is an open work item for this workstream. |
| 4 | **Last-Mile Enforcement**<br/>*Did the resource actually enforce the decision, on every call, inside the runtime?* | The moment the action reaches a production system. All upstream identity work either holds here or fails here. Most systems today cannot distinguish an authorized action by an agent acting in good faith from the same call by an agent hijacked by indirect prompt injection. Gateway-only enforcement misses tool calls and sub-agent invocations that never cross a gateway. Cached decisions miss revocation, scope change, and freshly-discovered taint. | **API gateways and service meshes** enforce at the edge; agent tool calls never reach them.<br/>**[OAuth 2.0 resource servers (RFC 6749 §1.1)](https://www.rfc-editor.org/rfc/rfc6749#section-1.1)** validate the token but not the action's relationship to intent.<br/>**[mTLS (RFC 8705)](https://www.rfc-editor.org/rfc/rfc8705)** and **[SPIFFE](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/)** establish workload identity but do not enforce per-action policy.<br/>None fire inside the agent runtime where the decision is made. | Enforcement fires at every tool call, inside the agent runtime, before the action executes. The runtime verifies identity, scope, taint, freshness, and intent on every call. The LLM call cost dwarfs the re-authorization cost, so caching decisions is not required for performance. Default-deny verified by a negative test in CI. | **Partially specified.** The ACS Instrument spec already defines the honoring-decisions contract in [§6.4 (Honoring Decisions, Normative)](../spec/instrument/specification.md#64-honoring-decisions-normative): the Observed Agent MUST wait for the Guardian's decision up to the negotiated timeout, MUST apply it, and MUST record every decision failure as an audit event. Note the spec's current default on timeout is **fail-open with audit**, which is in tension with this row's default-deny / no-cache goal; whether identity-tier actions should tighten that to fail-closed is an open work item for this workstream. |
| 5 | **Proof of Intent**<br/>*Was the action bound to an immutable, declared purpose that survives mid-task reframing?* | OAuth scopes say what the user *can* do. They do not say what the user *is doing right now*. Without intent binding at runtime, every agent action is a guess about user authorization. The agent's reasoning can be reframed mid-task by an instruction hidden in a document or a tool output. The original intent has to survive that. This is the hardest of the five, because it is the only one OAuth cannot patch with an extension: the underlying problem is non-deterministic client behavior under adversarial inputs. | **None at the identity layer.** Intent is implicit in the OAuth consent screen and discarded after token issuance.<br/>**[RFC 9396 RAR](https://www.rfc-editor.org/rfc/rfc9396)** can carry purpose claims as `authorization_details` data but does not bind them as runtime invariants against the LLM client.<br/>**IBAC (Intent-Based Access Control)** is research-stage with no IETF/W3C standard track.<br/>No production standard treats intent as a first-class, immutable, normatively-enforced runtime invariant. | Intent treated as a first-class governance concept with a normative immutability invariant. Intent must flow across all three ACS pillars: Instrument fires decisions against it, Trace records it as a span attribute, Inspect catalogs what it authorized. Intent extension permitted only through an explicit, human-approved path. | Pending |

---
Expand Down
152 changes: 152 additions & 0 deletions docs/spec/conformance-claim-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# ACS conformance claim

One page. Copy it once per implementation, fill every cell, publish the filled copy where a
reader can fetch it unchanged.

## 1. Claim header

| Field | Value |
|---|---|
| Implementation under test | `<name and version>` |
| Specification version claimed against | `<tag>` at `<full commit SHA>` |
| Date of claim | `<date>` |
| Claimant | `<organisation or individual>` |
| Published at | `<URL that resolves to fixed bytes>` |
| SHA-256 of this filled copy, computed with the digest replaced by the literal `<digest>` | `<digest>` and `<filename it belongs to>` |

Pin the specification by tag and full commit SHA. A tag can move, and `version.txt` has
disagreed with the latest tag before (`Issue #143`). A full SHA is computed from the bytes and
cannot name anything else.

## 2. Trust topology

| Field | Value |
|---|---|
| Who operates the Observed Agent | `<party>` |
| Who operates the Guardian | `<party>` |
| Same party operates both | `<yes / no>` |
| If no, which profile carries integrity beyond the shared secret | `<acs-crypto / acs-audit / none, and why>` |

This block is not optional and it is not a formality. ACS-Core's baseline integrity is
HMAC-SHA256 with an HKDF-derived per-session key from deployment-provided key material. It is
symmetric, so it authenticates possession of that key but cannot give the non-repudiation and
external verifiability that ACS-Crypto adds. Both ends hold the same key, so a signed decision
is not attributable to the Guardian against the Observed Agent (§8.6).

Two deployments can fill the rest of this page identically and truthfully while one operates
both ends and the other governs a customer's agents behind an independently operated Guardian.
The second is making a much weaker claim, and without this block the page does not show it.

## 3. Profile matrix

One row per profile, one column per role. The unit is the profile times the role, not the
deployment: `profiles_supported` is declared by the Observed Agent in ClientHello and
`profiles_accepted` by the Guardian in ServerHello, so a profile active at one end and not
claimed at the other is a normal outcome of per-session negotiation, not an inconsistency.

Every cell carries a state token and, where the state is not `EXERCISED`, a reason code from
section 4. **A blank cell is not a claim of any kind and voids the row.**

| Profile | Observed Agent (`profiles_supported`) | Guardian (`profiles_accepted`) |
|---|---|---|
| `acs-core` | | |
| `acs-trace` | | |
| `acs-inspect` | | |
| `acs-inspect-dynamic` | | |
| `acs-provenance` | | |
| `acs-crypto` | | |
| `acs-audit` | | |

**States.**

`EXERCISED` the profile is declared and something in the deployment demonstrates it.
`NOT CLAIMED` deliberately not declared. Carries a reason code.
`NOT ESTABLISHED` the claimant cannot say either way from what they have. Carries a reason code.

The third state is the one that disappears when a form offers a checkbox, because an unticked
box reads as the second. A reader who cannot separate a deliberate non-claim from an unmeasured
one sees a clean sheet in both cases.

## 4. Reason codes

Closed vocabulary. One code per non-exercised cell, no free text in the matrix. Free text turns
a one-pager into prose; no reason at all turns it back into a checkbox. A closed list is also
what lets two claims be compared later, which is the property a registry would need.

For `NOT CLAIMED`:

| Code | Meaning |
|---|---|
| `not-implemented` | The methods are not implemented. |
| `not-offered` | The product does not offer this profile. |
| `governed-elsewhere` | The control is provided outside this deployment. |
| `out-of-scope` | Deliberately outside the scope of this claim. |

For `NOT ESTABLISHED`:

| Code | Meaning |
|---|---|
| `no-test` | No test in this run covers it. |
| `no-sink` | The required sink or recorder is not installed. |
| `not-negotiated` | The peer never negotiated the profile in any observed session. |
| `not-observed` | The path exists but no traffic was seen traversing it. |

Anything that does not fit a code above is `NOT ESTABLISHED` plus a note in section 6, not a new
code invented on the page.

## 5. ACS-Core decision posture

Required whenever `acs-core` is `EXERCISED`. ACS-Core mandates decision honoring, and its
defaults are permissive, so a claim that omits this reports the default posture as evidence of
enforcement.

| Field | Value |
|---|---|
| `on_decision_failure` posture as configured (§6.4) | `<proceed / deny>` |
| Startup posture on handshake failure (§4.1) | `<proceed / refuse>` |
| Sessions observed in this run | `<n>` |
| Decision failures recorded, by failure class (`sessionEnd` `summary.decision_failures`) | `<class: n, ...>` |
| Sessions started unguarded after handshake failure | `<n>` |
| Where the decision-failure audit events were recorded | `<Guardian audit / deployment audit log / not recorded>` |

An action permitted by policy and an action permitted because no decision happened are
indistinguishable on the wire and both look like conformance. Section 6.4 requires every
decision failure to be recorded as an audit event, fail-open or fail-closed, and `sessionEnd`
reports the count for each failure class in `summary.decision_failures`. A session started
unguarded has no Guardian to receive that event, so it belongs in the deployment's own log.

## 6. Limits of this claim

State what the claim does not cover, in the claimant's own words. At minimum, any cell marked
`NOT ESTABLISHED`, whether the environment in which the run happened still exists, and who
authored the policy and answered the ASKs.

| Field | Value |
|---|---|
| Policy authorship | `<human-authored / generated / vendor default>` |
| ASK responses during this run | `<human-answered / service-answered / mixed / no ASKs occurred>` |

ACS-Core checks that the channel is authenticated and the Observed Agent honors decisions. It
does not check who wrote the policy or who answered an ASK. A deployment whose policy was
generated and whose ASKs were all answered by a service is fully conformant, and a claim that
stays silent on it lets a reader supply oversight that ACS never checked.

`<free text>`

## 7. What this page is

Quoted from `docs/spec/conformance.md` so that it travels with the claim:

> What "ACS-Core conformant" guarantees: the channel is authenticated and the Observed Agent
> honors the Guardian's decisions. It does NOT assert that a deployment's policies are strict,
> nor that the audit chain is tamper-evident against a compromised Guardian (that is the
> ACS-Crypto and ACS-Audit profiles, since the HMAC baseline is symmetric). A permissive Guardian
> is a conformant but permissive deployment, not a violation.

> Who verifies a conformance claim: nobody, in v0.1.0. `profiles_supported` and
> `profiles_accepted` are self-declaration on the wire. This release ships no conformance test
> suite, no registry of conformant implementations, and no steward body to arbitrate a disputed
> claim.

This page records what an implementer asserts about itself. A claim form that does not repeat
that limit inside itself will be quoted without it.
Loading
Loading