An offline, format-neutral verifier for agent delegation chains.
It checks whether authority only ever narrows when one agent delegates to another — across token formats and framework handoffs — without running any of the agents. Same input, same findings: no model calls, no network.
This page is a static demo. All numbers are copied from the audited reports (reports/phase1.md, phase2.md, gate2.md). Findings shown below were produced by the verifier and are reproduced here verbatim.
A planted case counts as detected only when a finding matches on both the check class and the edge.
| verifier | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | clean FP | extras |
|---|---|---|---|---|---|---|---|---|---|---|
| SubGrant | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 4/4 | 0 | 0 |
| B0 (naive per-edge) | 1.000 | 0.167 | 1.000 | 1.000 | 0.000 | 1.000 | 0.000 | 0/4 | 0 | 0 |
Recall over the public dev bench (120 cases: D1×8 D2×6 D3×6 D4×6 D5×8 D6×8 D7×6 D8×4 clean×68). SubGrant: GATE 1 PASS.
A second, independently authored 120-case holdout — with SubGrant's predictions pre-registered in git before the holdout was written, by a session that never ran the verifier. Scored once: recall D1–D7 = 1.000 each, D8 4/4, clean FP 0/68, extra findings 0 → GATE 2 PASS. The holdout itself is not published.
| comparison | result |
|---|---|
| B0 naive per-edge checker | 0.000 on D5 and D7; 0.167 on D2; matches SubGrant on D1/D3/D4/D6 |
| B2 UCAN-1.0-spec checker | D1 1.000; D4 0.500 (misses the start-time violation); D7 0.000 |
| attenu-guard interop vectors | agrees on 4 of 5; the one divergence is reject_bad_signature (a presence-only verifier cannot see a corrupted signature) |
| Framework | Default posture | Checks child ⊆ parent? | Handoff evidence | SubGrant findings |
|---|---|---|---|---|
| LangGraph / deepagents | inherit-all | No | none | D7 e3; D8 e1,e2,e3 |
| OpenAI Agents SDK | scoped | No | none | D8 e1,e2,e3 |
| CrewAI | scoped (delegation off by default) | No | none | D8 e1,e2,e3 |
| AutoGen (AgentChat) | scoped | No | none | D8 e1,e2,e3 |
| Google ADK | scoped | No | none | D8 e1,e2,e3 |
| Claude Agent SDK | inherit | No | none | D7 e3; D8 e1,e2,e3 |
| Pydantic AI | scoped (inherit_tools=False) | No | none | D8 e1,e2,e3 |
| A2A (reference) | scoped | No | none (card signed for identity) | D8 e1,e2,e3 |
No framework checks child ⊆ parent by default, and none records a delegation-authority binding on the handoff (D8 on every edge). The two inherit-by-default frameworks also propagate a protected action to the third agent (D7 on e3). Findings describe the scenario as modeled, not an overall security judgement of the framework.
max_depth, tightened by the root's depth_budget).redelegation: false yet a child edge exists (reported on the child).evidence.kind == "none" (presence only; no signature verification).Three real dev cases. The graph on the left is the verifier's input; the verdict on the right is its exact output.
Parent e1 grants a 100-call budget, then fans out to four children, each granted 30 calls. Every child individually stays within its parent — a per-edge checker sees nothing — but 4 × 30 = 120 > 100: the children recombine to exceed the parent.
{
"schema": "dgraph/1",
"nodes": [
{"id": "n0", "kind": "service", "trust_tier": "T0"},
{"id": "n1", "kind": "agent", "trust_tier": "T1"},
{"id": "n2", "kind": "agent", "trust_tier": "T1"},
{"id": "n3", "kind": "agent", "trust_tier": "T1"},
{"id": "n4", "kind": "agent", "trust_tier": "T1"},
{"id": "n5", "kind": "agent", "trust_tier": "T1"}
],
"edges": [
{"id": "e1", "parent_edge": null, "issuer": "n0", "holder": "n1",
"authority": {"actions": ["index.update","report.generate"],
"resources": ["files/reports/*"]},
"caveats": {"caps": [{"name": "api_calls", "limit": 100}]},
"evidence": {"kind": "attestation", "ref": "att:e1"}},
{"id": "e2", "parent_edge": "e1", "issuer": "n1", "holder": "n2",
"caveats": {"caps": [{"name": "api_calls", "limit": 30}]}, ...},
{"id": "e3", "parent_edge": "e1", "issuer": "n1", "holder": "n3",
"caveats": {"caps": [{"name": "api_calls", "limit": 30}]}, ...},
{"id": "e4", "parent_edge": "e1", "issuer": "n1", "holder": "n4",
"caveats": {"caps": [{"name": "api_calls", "limit": 30}]}, ...},
{"id": "e5", "parent_edge": "e1", "issuer": "n1", "holder": "n5",
"caveats": {"caps": [{"name": "api_calls", "limit": 30}]}, ...}
]
}
[
{"class": "D5", "edge": "e1"}
]
exit 1 — one finding, on the parent that over-subscribed its budget.
The root (n0, a human, T0) grants iam.grant — a protected action — to n1. n1 (not the root) then re-grants it to n2, an untrusted T2 agent. A protected action reaches an untrusted holder via a non-root issuer.
{
"schema": "dgraph/1",
"nodes": [
{"id": "n0", "kind": "human", "trust_tier": "T0"},
{"id": "n1", "kind": "agent", "trust_tier": "T1"},
{"id": "n2", "kind": "agent", "trust_tier": "T2"}
],
"edges": [
{"id": "e1", "parent_edge": null, "issuer": "n0", "holder": "n1",
"authority": {"actions": ["data.read","iam.grant","report.generate"],
"resources": ["db/project-x/*"]},
"evidence": {"kind": "attestation", "ref": "att:e1"}},
{"id": "e2", "parent_edge": "e1", "issuer": "n1", "holder": "n2",
"authority": {"actions": ["data.read","iam.grant","report.generate"],
"resources": ["db/project-x/*"]},
"evidence": {"kind": "attestation", "ref": "att:e2"}}
]
}
[
{"class": "D7", "edge": "e2"}
]
exit 1 — the re-grant to the T2 holder is flagged; authority never widens, but it reaches too far.
A faithful restatement: e2 holds exactly what e1 holds — same tools, actions, resources, window, caps, and purposes. Authority is restated, not widened, so there is nothing to report.
{
"schema": "dgraph/1",
"nodes": [
{"id": "n0", "kind": "agent", "trust_tier": "T0"},
{"id": "n1", "kind": "agent", "trust_tier": "T1"},
{"id": "n2", "kind": "agent", "trust_tier": "T1"}
],
"edges": [
{"id": "e1", "parent_edge": null, "issuer": "n0", "holder": "n1",
"authority": {"tools": ["tool.search","tool.summarize"],
"actions": ["data.read","index.update"],
"resources": ["logs/app/*"], "data_classes": ["internal"]},
"caveats": {"caps": [{"name": "rows_scanned", "limit": 100}],
"purposes": ["read"], "redelegation": true},
"evidence": {"kind": "attestation", "ref": "att:e1"}},
{"id": "e2", "parent_edge": "e1", "issuer": "n1", "holder": "n2",
"authority": {"tools": ["tool.search","tool.summarize"],
"actions": ["data.read","index.update"],
"resources": ["logs/app/*"], "data_classes": ["internal"]},
"caveats": {"caps": [{"name": "rows_scanned", "limit": 100}],
"purposes": ["read"], "redelegation": true},
"evidence": {"kind": "attestation", "ref": "att:e2"}}
]
}
[]
exit 0 — clean.