Agentic AI Atlasby a5c.ai
OverviewWikiGraphFor AgentsEdgesSearchWorkspace
/
GitHubDocsDiscord
iiRecord
Agentic AI Atlas · Proof-Based Policy Enforcement
page:docs-proof-based-policy-enforcement-overviewa5c.ai
Search record views/
Record · tabs

Available views

II.Record viewspp. 1 - 1
overviewarticlejsongraph
II.
Page JSON

page:docs-proof-based-policy-enforcement-overview

Structured · live

Proof-Based Policy Enforcement json

Inspect the normalized record payload exactly as the atlas UI reads it.

File · wiki/docs/proof-based-policy-enforcement-overview.mdCluster · wiki
Record JSON
{
  "id": "page:docs-proof-based-policy-enforcement-overview",
  "_kind": "Page",
  "_file": "wiki/docs/proof-based-policy-enforcement-overview.md",
  "_cluster": "wiki",
  "attributes": {
    "nodeKind": "Page",
    "sourcePath": "docs/proof-based-policy-enforcement-overview.md",
    "sourceKind": "repo-docs",
    "title": "Proof-Based Policy Enforcement",
    "displayName": "Proof-Based Policy Enforcement",
    "slug": "docs/proof-based-policy-enforcement-overview",
    "articlePath": "wiki/docs/proof-based-policy-enforcement-overview.md",
    "article": "\n# Proof-Based Policy Enforcement\n\n> A trust-chain authorization system that lets a tool command run **only** when it is\n> accompanied by cryptographic *proof* that the required conditions were met — a signed human\n> approval, an attestation of which model decided to call the tool, and a policy-signed\n> authorization binding all of it to the exact command about to execute.\n\n---\n\n## 1. Overview\n\nProof-Based Policy Enforcement makes agent tool execution **provable and non-spoofable**.\nInstead of trusting that \"the agent said a human approved\" or \"the agent said Opus decided\nthis,\" every sensitive action must carry an unforgeable chain of signed evidence that a\nconfigurable policy engine verifies before the tool layer will run the command.\n\nThe canonical example:\n\n> Allow the `aws` CLI with a specific credential scope **only if**\n> (a) a specific human breakpoint was approved — signed and not spoofable — **and**\n> (b) the model that decided to call the tool was Opus (proven, not asserted).\n> The tool layer itself refuses to execute unless it can verify a signed authorization,\n> and the policy component that issues that authorization is configurable so different\n> actions can require entirely different trust chains.\n\nIt ships as a new leaf crypto package plus a policy engine and enforcement gates wired\nacross the agent runtime:\n\n| Package / surface | Role |\n|---|---|\n| `@a5c-ai/trust-core` | Ed25519 signing, the universal `SignedEnvelope<T>` proof format, model-decision & permission payloads, trust-chain verification (a **support-systems leaf** — everything can depend on it) |\n| `@a5c-ai/policy-adapter` | The configurable policy component: trust-chain evaluator, `CommandAuthorization` issuer, config-manifest verification, canonicalized argv matcher, credential-identity resolution |\n| `babysitter-sdk` breakpoints | Enforced signed breakpoint approvals (the human-approval evidence producer) |\n| `transport-adapter` | Proxy-side model-decision attestation at the wire seam (the authoritative \"which model decided\" evidence producer) |\n| `genty-core` / `genty-platform` / `adapters-core` | The three enforcement gates + the session/dispatcher seams that verify authorizations before a tool runs or a credential is injected |\n\n---\n\n## 2. What it solves\n\nAgent systems increasingly take real, irreversible actions — running cloud CLIs, deleting\nresources, moving money, touching production. The safeguards that gate those actions\n(\"a human approved this\", \"only our most capable model may do this\", \"these credentials are\nonly for this command\") have historically been **claims the agent process itself makes**.\nA compromised, buggy, or adversarial agent can fabricate every one of them:\n\n- **Spoofed approval** — the agent writes `{approved: true}` and skips the human.\n- **Model downgrade** — a cheap model does the work while the logs claim Opus.\n- **Credential over-reach** — production credentials are handed to whatever command asks.\n- **Config tampering** — the agent rewrites the policy or the trusted-key list.\n- **Replay / aliasing / TOCTOU** — reusing an old approval, disguising a covered command\n  (`sh -c \"aws …\"`, `aws --region x s3 rm …`), or mutating arguments after approval.\n\nThis feature closes those gaps by making each safeguard a **cryptographic proof** that is\nverified at the exact moment of execution, sourced from an **integrity-protected,\nout-of-agent trust anchor** the agent cannot rewrite.\n\n**Threat model.** The in-scope adversary is a *workspace-writable / compromised agent\nprocess*: it can execute tools, write and commit to the working tree, and craft arbitrary\npayloads. It **cannot** forge signatures from keys it does not hold, and it cannot change the\ndeployment-pinned root of trust. Everything the system protects is protected against exactly\nthis adversary.\n\n### Two directions of proof\n\nProofs flow in two complementary directions, and this feature spans both:\n\n1. **Precondition proofs — \"you may run X because these proofs exist.\"**\n   Evidence (a signed human approval, a model-decision attestation) is gathered *before* a\n   command, a policy verifies it, and only then is the command authorized to execute. This is\n   the direction that gates dangerous actions. **(Shipped.)**\n\n2. **Outcome proofs — \"the tool call *is* the proof that X happened.\"**\n   The tool invocation *and its result* are themselves signed by a trusted executor, producing\n   an unforgeable record such as *\"`npm test` was run via Bash and returned exit code 0.\"* That\n   proof then becomes evidence a *later* policy step can require, or a **gate a babysitter\n   process step advances on** — so the orchestrating agent cannot fabricate \"tests passed\" to\n   move forward. See [§5](#5-the-complementary-direction-outcome-proofs). **(Design + a signing\n   primitive exist; producer/consumer integration is the next increment.)**\n\n---\n\n## 3. Why proofs (and not just checks)\n\nA \"check\" (an `if` statement in the agent, a flag in a run file, a hook that reads\n`task.json`) is only as trustworthy as the process running it — and that is the very process\nwe do not trust. A **proof** is different: its validity does not depend on who presents it or\nwhere it is stored, only on the signing key. That property is what makes the guarantees hold\neven when the agent is fully compromised:\n\n- The **human approval** is signed by a human's key held outside the agent, so the agent\n  cannot manufacture one.\n- The **model-decision attestation** is signed by the *transport proxy* — a component that\n  sits on the wire between the harness and the model provider, holds its own key, and sees the\n  real completion. The agent cannot forge \"Opus decided this\" because it does not hold the\n  proxy's key.\n- The **policy configuration and trusted-key list** are covered by a signed **config manifest**\n  with a monotonically increasing epoch, pinned off-workspace. The agent can edit the files on\n  disk, but the gate rejects any manifest whose signature or epoch does not match the pin — so\n  tampering and rollback both fail closed.\n\nDesign principle throughout: **fail closed, no fallbacks.** Any verification error, missing\nconfig, or unresolved authorization results in a **denial**, never a pass-through.\n\n---\n\n## 4. How it works\n\n### 4.1 The unified proof format\n\nEverything is a `SignedEnvelope<T>` (Ed25519 over a canonical JSON serialization), so evidence,\nattestations, and authorizations all share one verification path. Each envelope binds its\nown `payloadType` and the exact fields it signed, so a signature for one kind of payload can\nnever be transferred to another.\n\n### 4.2 Evidence — the inputs to a trust chain\n\n| Evidence | Payload | Produced by | Proves |\n|---|---|---|---|\n| **Human approval** | `PermissionEvidence` | A signed breakpoint answer (enforced in the SDK) | A specific human approved a specific breakpoint |\n| **Model decision** | `ModelDecisionPayload` (with per-tool-call `toolCallId` + `argsHash`) | Transport proxy at the wire seam (authoritative); genty in-process signer (correlation-grade) | *Which* model decided to call *this exact* tool with *these exact* args |\n| **Delegation** | delegation-chain links | Agent identities | A → B → C delegation actually happened (linkage verified, not just \"each is trusted\") |\n\nThe model-decision attestation binds the **specific tool call** — its `toolCallId` and a hash\nof its arguments — so an attestation produced for one tool call cannot be replayed to\nauthorize a different one in the same turn.\n\n### 4.3 The policy component\n\nA **policy document** declares, per action (matched on tool name + a *canonicalized argv* of\nthe command + credential scope), one or more acceptable **trust chains**. Chains compose\nflexibly with no code changes:\n\n- **AND across steps** — every required evidence step must be satisfied.\n- **OR across chains** — any one of several alternative chains satisfies the action.\n- **Quorum** — N *distinct* trusted identities (counted by resolved trust-root identity, not a\n  self-asserted label, so one human with two keys cannot satisfy a 2-of-N).\n- **Per-step conditions** — model allowlist (`modelIdMatches`), tags, expiry,\n  `requireProxyAttestation`, etc.\n\nThe evaluator verifies every evidence envelope against the **manifest-verified trust roots**\n(resolving each key from the trusted store, binding `sha256(publicKey) == fingerprint`, and\nrejecting cross-kind confusion), checks the chain linkage and conditions, and — on success —\n**issues a short-lived signed `CommandAuthorization`**. That authorization binds the policy id,\ntool name, command hash, args hash, credential scope, the evidence fingerprints, the config\nepoch, and an expiry. If nothing is satisfied, no authorization is issued and the action is\ndenied.\n\n### 4.4 The tool-layer gates (enforcement)\n\nVerification happens at the moment of execution, against what is *actually* about to run\n(hashes recomputed at the gate to close TOCTOU):\n\n- **Session gate (`policyToolGate`)** — verifies the `CommandAuthorization` before\n  `definition.execute` in every tool-executing session. *Load-bearing.*\n- **MCP dispatcher gate (`mcpPolicyGate`)** — same, before an MCP `dispatcher.dispatch`.\n  *Load-bearing.*\n- **GATE 3 — credential-injection backstop** — before a spawn injects scoped credentials\n  (docker `-e`/`-v`, ssh, k8s env/serviceaccount), it verifies an authorization covers that\n  credential's scope; with none, the credential is simply **not injected**.\n- **GATE 1 — dispatcher hook** — a `ToolDispatcher` policy verifier, built and active-by-default\n  when a dispatcher is constructed (see boundaries below).\n\nBoth load-bearing seams **auto-activate** when the deployment pins `POLICY_CONFIG_ROOT_FP`\n(the config root fingerprint) and `POLICY_CONFIG_MIN_EPOCH` (the anti-rollback floor). When\nthe anchor is not pinned, enforcement is inert (back-compat pass-through) — this is the single\nintentional pass-through.\n\n### 4.5 End-to-end flow (the aws-cli scenario)\n\n```\n1. Agent decides to run `aws s3 rm …` with prod credentials.\n2. A human answers the required breakpoint → SDK signs it (PermissionEvidence).      [proof #1]\n3. The transport proxy signs the model turn, binding this toolCall + argsHash,\n   attesting modelId = Opus.                                                         [proof #2]\n4. Policy component: matches the action, verifies both proofs against the\n   manifest-signed trust roots, checks modelIdMatches(opus) + the human step,\n   and issues a short-lived signed CommandAuthorization bound to the exact\n   command/args/credential-scope/epoch.                                             [authorization]\n5. Tool-layer gate: recomputes the command/args hashes, verifies the authorization\n   signature + expiry + epoch + binding → ALLOWS, and the scoped credential is\n   injected. Any mismatch → DENY, credential withheld.\n```\n\nEvery negative variant fails closed: unsigned approval, a Sonnet attestation, an expired or\nreplayed authorization, tampered args, an untrusted key, a rolled-back config, or a covered\ncommand disguised behind a wrapper/global-option/interpreter.\n\n---\n\n## 5. The complementary direction: outcome proofs\n\n> Status: the low-level signing primitive (`signToolResult` / `ToolResultPayload` in\n> `@a5c-ai/trust-core`) exists; the producer, the policy/process consumers, and the\n> docs below describe the **next increment**, not yet wired end-to-end.\n\nEverything in §4 answers *\"may this command run?\"*. The equally important question is\n*\"can I prove this command ran, and what it returned?\"* — and use that proof to validate a\nresult or to advance a process. This is what makes **execution itself** trustworthy rather\nthan just self-reported.\n\n### 5.1 The problem it closes\n\nIn a multi-step workflow, steps depend on prior steps having *actually happened*:\n\n- \"Only deploy if the tests actually passed.\"\n- \"Only mark this phase complete if the build, lint, and integration suite all ran and\n  returned success.\"\n- \"Only proceed to the migration if a dry-run was executed and produced no diff.\"\n\nToday, in an agent-driven process, the *agent orchestrating the process* is the one reporting\nthose outcomes — the same untrusted process we are guarding against. A compromised or\nover-eager agent can claim \"`npm test` passed\" and advance, having never run it (or having\nrun it and ignored a failure). The result gate is a self-report, not a proof.\n\n### 5.2 The mechanism: a trusted executor signs the invocation + result\n\nWhen a tool actually executes, the component that runs it — the **trusted tool executor**,\nholding a key the *deciding/orchestrating* agent does not — signs a `ToolResultAttestation`\nbinding the exact invocation to its exact outcome:\n\n```\nToolResultAttestation {\n  payloadType:   'tool-result-attestation',\n  toolName, toolCallId, commandHash, argsHash,   // WHAT ran (bound, not asserted)\n  exitCode, stdoutHash, stderrHash,               // WHAT it returned\n  startedAt, finishedAt, durationMs,\n  runId, sessionId,\n  modelDecisionFingerprint?,                      // links to WHO decided to run it (§4.2)\n  executorKind: 'tool-executor'                   // signed by a trusted executor root\n}\n```\n\nBecause it is signed by the executor (not the orchestrator), and it binds the exact command\nand args by hash, the orchestrating agent cannot forge \"tests passed\", cannot swap in a\ndifferent command's result, and cannot replay an old run's proof for a new command.\n\n### 5.3 Consuming an outcome proof — two use cases\n\n**A. As evidence in a later policy chain.** A policy step of kind `tool-result` requires a\nvalid `ToolResultAttestation` matching a command pattern *and* a result predicate, signed by a\ntrusted `tool-executor` root — expressed in the same policy-document schema as §4.3:\n\n```yaml\n# Authorize `aws deploy` only if the test suite provably passed.\naction: { tool: Bash, command: \"^aws deploy\\\\b\", credentialScope: aws:prod:deploy }\nchains:\n  - steps:\n      - kind: human-approval        # a person approved the deploy         (precondition)\n      - kind: model-decision\n        modelIdMatches: \"^claude-opus-\"\n      - kind: tool-result           # AND the tests provably passed        (outcome proof)\n        commandMatches: \"^npm test\\\\b\"\n        require: { exitCode: 0 }\n        signedBy: tool-executor\n```\n\n**B. As a babysitter process gate.** A process step produces an attestation when it runs, and a\ndownstream step refuses to advance without a matching one — making process progression itself\nnon-spoofable:\n\n```js\n// The step that runs the tests emits a signed proof as part of its effect result.\nconst test = await ctx.task({ kind: 'shell', command: 'npm test', attestResult: true });\n\n// A later step / breakpoint gate requires the signed proof — not the agent's word.\nawait ctx.requireProof({\n  command: /^npm test\\b/,\n  result:  { exitCode: 0 },\n  signedBy: 'tool-executor',\n});               // no valid executor-signed attestation in the journal → the step blocks\n```\n\nThe attestation travels in the effect result and the run journal, so the requirement is\nchecked against a signed artifact, not a narrative summary — the orchestrator cannot advance\nthe process by *claiming* success.\n\n### 5.4 What this shares with §4 — and what is new\n\nIt reuses the whole existing spine: the `SignedEnvelope` format, the manifest-verified\ntrust-roots, the evaluator, and the fail-closed discipline. What the increment adds is:\n\n- a `ToolResultAttestation` payload + a **`tool-executor` trust-root kind**;\n- **production** of it at the tool-execution seam (the shell/Bash effect and the adapters tool\n  layer), signed by the executor identity — so it is unforgeable by the orchestrator;\n- a **`tool-result` policy step type** with result predicates (`exitCode`, output-hash/pattern\n  match, freshness/expiry, command binding);\n- a **babysitter-process gate** (`attestResult` on a task + a `requireProof` intrinsic /\n  proof-gated breakpoint auto-approval) that consumes the attestation from the effect/journal.\n\nThe same threat model applies: the outcome proof is only as strong as the executor key being\nheld outside the orchestrating agent, so *where the executor runs and holds its key* is the\ncentral design decision for a deployment (e.g. a sidecar/harness-layer executor vs. an\nin-process one, mirroring the authoritative-proxy vs. correlation-grade split in §4.2).\n\n---\n\n## 6. Configuration & operations\n\nConfiguration lives under a `.policy/` directory, all of it covered by one signed manifest:\n\n- `.policy/policies/*.yaml` — the policy documents (worked examples shipped under\n  `packages/adapters/policy/examples/`: an aws-cli human+opus chain and a 2-human quorum chain).\n- `.policy/trust-roots.json` — the trusted public keys, each tagged by kind (human / engine /\n  proxy / config) and fingerprint.\n- `.policy/credential-scope-source.json` — the deployment's declaration of which credentials\n  map to which scope (signed, so the agent cannot re-scope its own credentials).\n- The **config manifest** — signs all of the above together with a monotonic `configEpoch`.\n\nThe root of trust is pinned **off-workspace** via `POLICY_CONFIG_ROOT_FP` +\n`POLICY_CONFIG_MIN_EPOCH` (deploy-image config, not agent-writable). Rotating keys or changing\npolicy means re-signing the manifest and bumping the epoch (which also atomically publishes\nrevocations). Full schema reference, key-provisioning/rotation runbook, and the deny-reason\ntroubleshooting table live in [`docs/policy-enforcement.md`](./policy-enforcement.md); the\nauthoritative spec is [`docs/design/proof-based-policy-enforcement.md`](./design/proof-based-policy-enforcement.md).\n\n**What is code vs. deployment config.** The verification, gating, issuance, and auto-activation\nare *code*. Which credentials are scoped, what the policies say, and where the pins live are\n*deployment configuration* — carried in the signed manifest, never taken from agent-writable\ninput.\n\n---\n\n## 7. Guarantees and honest boundaries\n\n**Guarantees (verified against the workspace-writable adversary):**\nno unsigned/spoofed approval is accepted (enforcement anchors to the signed manifest, not the\nrun-directory `task.json`); no covered action executes and no scoped credential is injected\nwithout a valid `CommandAuthorization`; replay-within-turn, model downgrade, config\nrollback, trust-anchor tampering, cross-kind/cross-payload-type confusion, argv/command alias\nevasion, and TOCTOU are all denied with tests; every error path fails closed.\n\n**Boundaries (documented, not overclaimed):**\n\n- **Live evidence-collection wiring** — the production plumbing to populate the authorization\n  store and GATE-3 resolver *from run-produced attestations at runtime* is an orchestrator-side\n  integration step; until wired for a given deployment, covered actions simply deny (correct\n  fail-closed). The store, registry, and auto-activation code all exist.\n- **GATE 1 latent-until-consumed** — the `ToolDispatcher` policy verifier is built and\n  active-by-default, but nothing in the current runtime constructs that dispatcher; the\n  load-bearing enforcement is the session + MCP-dispatcher seams (which *are* wired).\n- **Substrate-delivered credentials** (IMDS/IRSA/workload-identity, pre-existing mounts,\n  image-baked secrets) are a bounded non-goal — GATE 3 mediates injected credentials, not\n  credentials the substrate hands the process; mitigate those with substrate controls.\n- **Passthrough proxy mode** produces no attestation, so any action requiring proxy\n  attestation denies on that path (documented non-goal, fail-closed).\n- **Outcome proofs ([§5](#5-the-complementary-direction-outcome-proofs))** — the precondition\n  direction is shipped and enforced; the outcome-proof direction (trusted-executor result\n  attestation, the `tool-result` policy step, and the babysitter-process `requireProof` gate)\n  has a signing primitive in `trust-core` but is **not yet produced or consumed** — it is the\n  next planned increment, not a current guarantee.\n\n---\n\n## 8. Status\n\n**Shipped: the precondition direction (§4).** Delivered across five milestones (unified trust core → configurable policy engine → evidence\nproducers → tool-layer enforcement gates → end-to-end scenario), each independently\nsecurity-reviewed, with a whole-system acceptance audit at **96/100** and every acceptance\ncriterion met with code + passing tests. The adversarial reviews caught and forced fixes for\nreal authorization bypasses at each milestone (delegation-linkage forgery, quorum-by-one-human,\nargv global-option evasion, and initially-unwired gates) before it was accepted.\n\n**Next: the outcome direction (§5).** The trusted-executor `ToolResultAttestation`, the\n`tool-result` policy step, and the babysitter-process `requireProof` gate are designed above\nand rest on a signing primitive already present in `trust-core`, but are not yet produced or\nconsumed end-to-end. This is the planned next increment.\n",
    "documents": []
  },
  "outgoingEdges": [],
  "incomingEdges": [
    {
      "from": "page:docs",
      "to": "page:docs-proof-based-policy-enforcement-overview",
      "kind": "contains_page"
    }
  ]
}

Shortcuts

Back to overview
Open graph tab