Skip to content
Open
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
4 changes: 4 additions & 0 deletions docs/concepts/provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Argument-level provenance lets a Guardian reason about the lineage of individual

This is what makes compaction a controllable chokepoint rather than a laundering path. Without transitive lineage, untrusted content could enter, be summarized into new `agent_generated` text, and lose the mark of where it came from.

> **Lineage spans the session boundary (normative).** A `provenance_id` is unique only within its own session, so a read in a later session cannot cite the node that was written in an earlier one. A memory write carries a `lineage_id`, the `origin_session_id` and the writer's provenance summary; every read of that entry echoes them and cites the `lineage_id` in `derived_from`. The wire rules are in [Specification §7.3](../spec/instrument/specification.md#73-memory-lineage-across-sessions-normative).

*Example.* In session A the agent fetches a web page (`origin: tool_output`) and stores a note from it in a `user`-scoped memory. In session B the note comes back as `origin: retrieved`, but its lineage still names the `tool_output` it rests on, so a Guardian does not mistake it for recall the user wrote.

*Example.* The model summarizes a retrieved web page (`origin: retrieved`). The summary text is `agent_generated`, but its `derived_from` includes the page's Provenance, so a Guardian still sees that the summary rests on retrieved content.

## Deterministic assignment
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/trust.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The fact is bound by a signature or a hash chain and is verifiable without trust

When a fact is summarized, written to memory, or passed across a boundary, its trust basis must travel with it. A derivation of asserted content is asserted; it does not become attested by being restated. This is the same rule that makes [Provenance](./provenance.md) lineage transitive, viewed from the trust side: dropping the basis at a boundary lets asserted content be treated as if it were attested, trust laundering up the spectrum.

*Example.* An agent reads an untrusted email (asserted) and writes a summary to memory. If the summary keeps the email's basis, a second agent that retrieves it still treats it as asserted. If the basis is dropped, the memory reads like trusted recall.
*Example.* An agent reads an untrusted email (asserted) and writes a summary to memory. If the summary keeps the email's basis, a second agent that retrieves it still treats it as asserted. If the basis is dropped, the memory reads like trusted recall. The wire carries the basis across the store through the write's `lineage`; see [Specification §7.3](../spec/instrument/specification.md#73-memory-lineage-across-sessions-normative).

## Availability in v0.1

Expand Down
8 changes: 6 additions & 2 deletions docs/spec/instrument/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ Schema: [`hooks/memory-context-retrieval.json`](https://genai-security-project.g

Memory read — long-term, session-scoped, or user-scoped — into the agent's working context. Provenance: `origin: retrieved`, `source_id` identifies the memory store.

**Payload:** memory entries pulled into context.
**Payload:** `query`, `memory_store` (`name`, `scope`), and `results`. Each result carries `value`, `memory_id`, `stored_at`, `provenance`, and `lineage` (`lineage_id`, `origin_session_id`, the stored `provenance`, and the writer's `provenance_summary`). `memory_id` is required on every result when the scope is `user`, `tenant` or `global`.

**Cross-session lineage.** An entry written in one session is read in another, and its trust basis has to survive the trip. The rules are normative in [Specification §7.3](./specification.md#73-memory-lineage-across-sessions-normative): the result's `provenance.derived_from` includes `lineage.lineage_id`, the Guardian classifies the entry by `lineage.provenance` rather than by the read's `origin: retrieved`, and a result without lineage is never treated as fresh or trusted.

**Decision:** ALLOW / DENY / MODIFY.

Expand All @@ -175,7 +177,9 @@ Schema: [`hooks/memory-store.json`](https://genai-security-project.github.io/age

Memory write. The standard sink for cross-session influence; mediating it prevents memory poisoning.

**Payload:** entries to be written, target store, scope (`session`/`user`/`tenant`/`global`), TTL.
**Payload:** `memory_id`, `memory_store` (`name`, `scope`: `session`/`user`/`tenant`/`global`), `operation` (`create`/`update`/`delete`), `content` (`value` and `provenance`), `lineage` (`lineage_id`, `origin_session_id`, and the writing session's `provenance_summary`), and `metadata`. `memory_id` is required when the scope is `user`, `tenant` or `global`.

**Cross-session lineage.** The write is where the trust basis is captured. Under ACS-Provenance the write carries `lineage`, and the store keeps it with the entry so every later read can echo it. See [Specification §7.3](./specification.md#73-memory-lineage-across-sessions-normative).

**Decision:** ALLOW / DENY / MODIFY.

Expand Down
25 changes: 25 additions & 0 deletions docs/spec/instrument/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,31 @@ Used both by Guardians that derive trust in policy and by vendor implementations

Provenance MUST be populated by deterministic code outside the LLM's output path. Implementations MUST NOT instruct the LLM to produce it. The agent declares `provenance_producer: "deterministic" | "none"` in the handshake; a `none` producer emits no Provenance objects, and Guardians whose policies require Provenance MUST refuse the session at handshake time rather than silently degrading enforcement. LLM-authored Provenance is not a conformant producer mode because it makes an untrusted runtime output responsible for its own lineage.

### 7.3 Memory lineage across sessions (normative)

A memory write in one session and a read in a later one is the standard memory-poisoning path, and `user`, `tenant` and `global` scopes exist to make it work. A `provenance_id` is unique only within its own session, so the read cannot cite it, and a Guardian's session-level state does not follow the entry. Without a rule, untrusted content written at step N reads back as trusted recall. These rules close that gap. They carry facts only; trust classification stays in Guardian policy (§7.1).

**On the write (`memoryStore`).**

- `memory_id` MUST be present when `memory_store.scope` is `user`, `tenant` or `global`, so the write and a later read can be joined.
- The write carries `lineage`: a `lineage_id` the framework mints for this version of the entry, the `origin_session_id` of the session that produced the content, and that session's `provenance_summary` at write time. The `provenance_id`s in `content.provenance` resolve in `origin_session_id`.
- The store MUST keep `lineage` and `content.provenance` with the entry. A store that drops them cannot serve a conformant read.
- `lineage` is REQUIRED under ACS-Provenance and OPTIONAL otherwise.

**On the read (`memoryContextRetrieval`).**

- Each result's `memory_id` MUST be present when the scope is `user`, `tenant` or `global`.
- Each result carries `lineage`, echoed from the write: `lineage_id`, `origin_session_id`, the stored `provenance`, and the writer's `provenance_summary`. It is REQUIRED on every result under ACS-Provenance.
- The result's own `provenance` is the node for the read. Its `origin` MUST be `retrieved` whenever `lineage` is present, and its `derived_from` MUST include `lineage.lineage_id`. A retrieval cannot relabel stored content as `system` or `user_input`.

**What a Guardian does.**

- It classifies an entry by `lineage.provenance` and `lineage.provenance_summary`, not by the `retrieved` origin of the read. Content stored from `tool_output` stays untrusted after the read, as it was on the write.
- A result with no `lineage` has no known basis. The Guardian MUST treat it as the least-trusted origin its policy knows, or deny it. It MUST NOT treat it as fresh.
- The reading session's provenance summary MUST include at least the `origins_seen` of every `lineage.provenance_summary` it ingests, so the writer's taint is visible to cumulative-context policy in the new session.

A Guardian that does not dispatch `memoryStore` or `memoryContextRetrieval` does not enforce these rules. The [FAQ's ASI06 row](../../topics/faq.md) describes what the schemas carry, not what a given Guardian decides.

## 8. SessionContext and Intent

State the Guardian Agent maintains across a session. The Guardian commits the chain head (`chain_hash`) on the wire in its response for every step where it writes a ContextEntry (§8.6), so the chain is externally observable rather than purely Guardian-internal. The Observed Agent MAY also send `session_id` and a `chain_hash` for cross-checking.
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ ACS exposes control points and audit surface; the deployment's policy library de
| ASI03 Identity & Privilege Abuse | Core hook envelopes carry replay protection and a signature using a per-session HKDF-derived key; `subagentStart.intent_derivation` lets policy deny delegation that would widen the parent's authority |
| ASI04 Agentic Supply Chain Vulnerabilities | AgBOM inventories components and `agbom/changed` is decision-eligible on mutation; `skillRegister` exposes the definition, `declared_capabilities`, digest, and `registration_provenance` for vetting before any action runs, and `skillLoad` correlates each activation to an approved `(skill_id, digest)` |
| ASI05 Unexpected Code Execution (RCE) | Process, code, and shell actions that escape the agent's reasoning context cross `toolCallRequest` before execution, so policy sees the exact command or code proposed |
| ASI06 Memory & Context Poisoning | `memoryStore` and `memoryContextRetrieval` gate memory writes and reads; with ACS-Provenance, lineage travels with stored content and `preCompact` / `postCompact` bind a summary to the entries it compacts |
| ASI06 Memory & Context Poisoning | `memoryStore` and `memoryContextRetrieval` gate memory writes and reads; with ACS-Provenance, a write carries its `lineage` and every later read echoes it, so content stored from an untrusted source cannot come back as trusted recall (Specification §7.3), and `preCompact` / `postCompact` bind a summary to the entries it compacts |
| ASI07 Insecure Inter-Agent Communication | In-process spawning crosses the decision-eligible `subagentStart`; `subagentStop` records termination when emitted. Core hook envelopes carry replay protection and a baseline signature; A2A wrapping (`protocols/A2A/*`) is reserved for v0.2 |
| ASI08 Cascading Failures | A Guardian `deny` at `subagentStart` stops a spawn cascade at the boundary; parent and child session references preserve the propagation path in the audit record |
| ASI09 Human-Agent Trust Exploitation | ASK routes decisions to a human approver as a wire-level primitive; `agentResponse` gates agent output before delivery and records the proposed response together with the Guardian's decision |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/memory-context-retrieval.acs-provenance.json",
"title": "steps/memoryContextRetrieval payload (ACS-Provenance profile)",
"description": "Strict variant of memory-context-retrieval.json for deployments running provenance_producer: deterministic / claiming ACS-Provenance. Every result MUST carry provenance. ACS-Core deployments validate against the base schema instead.",
"description": "Strict variant of memory-context-retrieval.json for deployments running provenance_producer: deterministic / claiming ACS-Provenance. Every result MUST carry provenance (origin: retrieved, derived_from non-empty) and lineage with the stored provenance and summary, so stored untrusted content cannot be read back as fresh or trusted. ACS-Core deployments validate against the base schema instead.",
"allOf": [{ "$ref": "./memory-context-retrieval.json" }],
"properties": {
"results": {
"type": "array",
"items": { "required": ["value", "provenance"] }
"items": {
"required": ["value", "provenance", "lineage"],
"properties": {
"provenance": {
"required": ["derived_from"],
"properties": {
"origin": { "const": "retrieved" },
"derived_from": { "minItems": 1 }
}
},
"lineage": { "required": ["lineage_id", "origin_session_id", "provenance", "provenance_summary"] }
}
}
}
}
}
58 changes: 56 additions & 2 deletions specification/v0.1.0/hooks/memory-context-retrieval.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@
"description": "Payload for the memoryContextRetrieval hook. Fires when the agent reads from persistent memory.",
"type": "object",
"required": ["results"],
"allOf": [
{
"if": {
"required": ["memory_store"],
"properties": {
"memory_store": {
"required": ["scope"],
"properties": { "scope": { "enum": ["user", "tenant", "global"] } }
}
}
},
"then": {
"properties": {
"results": { "items": { "required": ["value", "memory_id"] } }
}
}
}
],
"properties": {
"query": {
"type": "object",
Expand All @@ -25,13 +43,49 @@
"items": {
"type": "object",
"required": ["value"],
"if": { "required": ["lineage"] },
"then": {
"properties": {
"provenance": {
"properties": { "origin": { "const": "retrieved" } }
}
}
},
"properties": {
"memory_id": { "type": "string" },
"memory_id": {
"type": "string",
"description": "Identifier of the entry, as sent on the memoryStore write. REQUIRED on every result when memory_store.scope is user, tenant or global."
},
"value": {},
"stored_at": { "type": "string", "format": "date-time" },
"provenance": {
"$ref": "../provenance.json",
"description": "OPTIONAL in the base schema; REQUIRED on every result under provenance_producer: deterministic (ACS-Provenance). Validate against memory-context-retrieval.acs-provenance.json when the profile is claimed. See Specification section 7."
"description": "Provenance of this read: a new node with origin: retrieved and source_id naming the store. OPTIONAL in the base schema; REQUIRED on every result under provenance_producer: deterministic (ACS-Provenance). When the result carries lineage, origin MUST be retrieved and derived_from MUST include lineage.lineage_id. Validate against memory-context-retrieval.acs-provenance.json when the profile is claimed. See Specification section 7."
},
"lineage": {
"type": "object",
"description": "The lineage the entry had when it was stored, echoed from the memoryStore write so the trust basis crosses the session boundary. Set by deterministic framework code, never by the LLM. OPTIONAL in the base schema; REQUIRED on every result under ACS-Provenance. A result without it has no known basis; a Guardian MUST NOT treat it as fresh or trusted. See Specification section 7.3.",
"required": ["lineage_id", "origin_session_id"],
"properties": {
"lineage_id": {
"type": "string",
"minLength": 1,
"description": "The lineage_id the memoryStore write carried."
},
"origin_session_id": {
"type": "string",
"minLength": 1,
"description": "The session that wrote the entry. The provenance_ids in lineage.provenance resolve in that session."
},
"provenance": {
"$ref": "../provenance.json",
"description": "The Provenance the content carried when it was stored (the write's content.provenance). This, not the origin of the read, is what a Guardian classifies the entry by."
},
"provenance_summary": {
"$ref": "../provenance-summary.json",
"description": "The writing session's provenance summary at write time, echoed from the write. The reading session's own summary starts from at least these origins for this entry."
}
}
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions specification/v0.1.0/hooks/memory-store.acs-provenance.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/memory-store.acs-provenance.json",
"title": "steps/memoryStore payload (ACS-Provenance profile)",
"description": "Strict variant of memory-store.json for deployments running provenance_producer: deterministic / claiming ACS-Provenance. The written content MUST carry provenance. ACS-Core deployments validate against the base schema instead.",
"description": "Strict variant of memory-store.json for deployments running provenance_producer: deterministic / claiming ACS-Provenance. The written content MUST carry provenance, and the write MUST carry lineage with the writing session's provenance_summary. ACS-Core deployments validate against the base schema instead.",
"allOf": [{ "$ref": "./memory-store.json" }],
"required": ["lineage"],
"properties": {
"content": { "required": ["value", "provenance"] }
"content": { "required": ["value", "provenance"] },
"lineage": { "required": ["lineage_id", "origin_session_id", "provenance_summary"] }
}
}
37 changes: 36 additions & 1 deletion specification/v0.1.0/hooks/memory-store.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@
"description": "Payload for the memoryStore hook. Fires before the agent writes to persistent memory.",
"type": "object",
"required": ["content"],
"allOf": [
{
"if": {
"required": ["memory_store"],
"properties": {
"memory_store": {
"required": ["scope"],
"properties": { "scope": { "enum": ["user", "tenant", "global"] } }
}
}
},
"then": { "required": ["memory_id"] }
}
],
"properties": {
"memory_id": {
"type": "string",
"description": "Identifier for the memory entry being written or updated"
"description": "Identifier for the memory entry being written or updated. REQUIRED when memory_store.scope is user, tenant or global, so a later read in another session can be joined to this write. See Specification section 7.3."
},
"memory_store": {
"type": "object",
Expand All @@ -32,6 +46,27 @@
}
}
},
"lineage": {
"type": "object",
"description": "Cross-session lineage the stored entry carries, so a read in a later session can recover the facts the write was made on. Set by deterministic framework code, never by the LLM. OPTIONAL in the base schema; REQUIRED under provenance_producer: deterministic (ACS-Provenance). See Specification section 7.3.",
"required": ["lineage_id", "origin_session_id"],
"properties": {
"lineage_id": {
"type": "string",
"minLength": 1,
"description": "Identifier for this entry's lineage that survives the session, unlike provenance_id. Minted by the framework on each write and echoed by every memoryContextRetrieval result for the entry."
},
"origin_session_id": {
"type": "string",
"minLength": 1,
"description": "The session that produced the content. The provenance_ids in content.provenance resolve in that session, so the pair (origin_session_id, provenance_id) addresses a node across sessions."
},
"provenance_summary": {
"$ref": "../provenance-summary.json",
"description": "The writing session's cumulative provenance summary at write time. A reading session starts from at least these origins for the entry. REQUIRED under ACS-Provenance."
}
}
},
"metadata": {
"type": "object",
"additionalProperties": true
Expand Down
2 changes: 1 addition & 1 deletion specification/v0.1.0/provenance.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"derived_from": {
"type": "array",
"items": { "type": "string" },
"description": "Lineage edges. Array of provenance_ids this data was derived from."
"description": "Lineage edges. Array of provenance_ids this data was derived from. On a memoryContextRetrieval result, also the lineage_id of the stored entry the data came back from, because a provenance_id resolves only within its own session (Specification section 7.3)."
}
}
}
Loading
Loading