Skip to content

PredicateSystems/langgraph-poisoned-escalation-demo

Repository files navigation

Poisoned Escalation Demo

Demonstrates Chain Delegation in Predicate Authority preventing a prompt injection attack from escalating privileges across a multi-agent LangGraph system.

Demo

The Scenario

A malicious resume contains a hidden prompt injection payload (1pt white text). When processed:

  1. Intake Agent (low-privilege, local LLM) parses the resume
  2. The injection hijacks the agent to pass a malicious instruction
  3. HR Admin Agent (high-privilege, cloud LLM) receives the instruction
  4. The instruction attempts to: query executive salaries → exfiltrate to evil-exfil.com

Why Chain Delegation Blocks This

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

Running the Demo

Option 1: Docker Mode (Self-contained)

No setup required - starts sidecar automatically in a container:

cd predicate-secure/examples/langgraph-poisoned-escalation
./run-demo.sh --docker

Option 2: Local Mode (Real-time Web UI)

Run 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.yaml

Terminal 2 - Run the demo:

cd predicate-secure/examples/langgraph-poisoned-escalation
./run-demo.sh --local

Open http://localhost:8787 to see the sidecar Web UI showing allow/deny decisions in real-time.

Sidecar Web UI - Policy Evaluation

Sidecar Web UI

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

Recording a GIF with VHS

Prerequisites

# Install VHS
brew install vhs

# Install Python dependencies (for local mode)
pip install langgraph httpx

Option 1: Record in Local Mode (~30s GIF)

Terminal 1 - Start the sidecar:

cd rust-predicate-authorityd
cargo run -- --policy-file ../predicate-secure/examples/langgraph-poisoned-escalation/policy.yaml

Terminal 2 - Record:

cd predicate-secure/examples/langgraph-poisoned-escalation
vhs demo.tape

Outputs: poisoned-escalation-demo.gif

Option 2: Record in Docker Mode (~38s GIF)

No sidecar setup needed - everything runs in containers:

cd predicate-secure/examples/langgraph-poisoned-escalation
vhs demo-docker.tape

Outputs: poisoned-escalation-demo-docker.gif

Recording Features

  • Hides username/hostname (clean $ prompt)
  • Title cards before the demo
  • Automatic timing for each phase

Files

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

Policy Highlights

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/*"]

Key Insight

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.

Environment Variables

Variable Default Description
SLOW_MODE 1 Timing multiplier (2 = 2x slower for GIF)
PREDICATE_SIDECAR_URL http://localhost:8787 Sidecar URL for local mode

Related

About

LangGraph demo with Poisoned Escalation for agent delegation

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors