Skip to main content

agent-boundary-scan

Static analysis that detects confused-deputy vulnerabilities in multi-agent and MCP-based AI systems — unvalidated paths where attacker-influenced data can reach a privileged action (shell exec, file write, credential access, network egress, agent delegation) with no trust boundary in between. It reads an agent/tool graph and reports where the unvalidated paths are, the way a SAST tool flags SQL injection before the query ever runs.

Supports LangGraph, the OpenAI Agents SDK, CrewAI, and MCP (both server manifests and FastMCP @mcp.tool server source, the latter giving HIGH-confidence analysis of the real tool bodies). Outputs a console table, JSON, or spec-compliant SARIF 2.1.0 for GitHub code scanning, and returns a CI-friendly exit code.

Status: v1.1 (research/portfolio project). See DESIGN.md for the full rationale behind every decision below.

The problem

Single-agent LLM systems have one trust boundary: the line between the user's prompt and the model. Multi-agent systems (LangGraph, CrewAI, AutoGen, Google ADK) and MCP tool integrations multiply that boundary across a graph of agents and tools, and most frameworks wire that graph together without re-checking authorization at each hop. Concretely: data enters from a source that may be attacker-influenced — a scraped web page, a document pulled into RAG, a third-party API response, a poisoned tool result — and can flow, unvalidated, into a node with real-world write power.

This is a structural recurrence of the confused deputy problem (Norm Hardy, 1988), catalogued by MITRE as CWE-441 (Unintended Proxy or Intermediary). What's new is the medium: the "deputy" is now an LLM interpreting natural language, and the trust boundary is a graph edge instead of a function call — which makes the vulnerability harder to see by inspection and easier to construct by accident. The attack and runtime-defense research is mature; the static side (inspect the graph before deployment) is the gap this fills.

How it works

flowchart LR
    subgraph Extraction
        A1[LangGraph parser]
        A2[OpenAI Agents / CrewAI parsers]
        A3[FastMCP / MCP manifest parser]
    end
    B[Capability + source<br/>classification]
    C[networkx graph]
    subgraph Analysis
        D[Trust-boundary<br/>detection]
        E[Taint path<br/>enumeration]
        F[Rule engine]
        G[Severity + confidence<br/>scoring]
    end
    subgraph Reporting
        H1[Console]
        H2[JSON]
        H3[SARIF]
    end
    A1 --> B
    A2 --> B
    A3 --> B
    B --> C
    C --> D
    C --> E
    D --> E
    E --> F
    F --> G
    G --> H1 & H2 & H3
  1. Extraction parses LangGraph source, OpenAI Agents SDK source, FastMCP server source (@mcp.tool), and MCP manifests into a normalized IR of nodes and edges. A .py file is parsed against each framework (each ignores files that aren't its own). For LangGraph it AST-walks graph construction (StateGraph, add_node, add_edge, add_conditional_edges), the four tool-wiring idioms (ToolNode, create_react_agent, create_agent, in-body llm.bind_tools), and modern multi-agent control flow (Command(goto=...) and Send(...) handoffs, compile(interrupt_before/after=[...]) pause points). Each node's calls — including those in local helper functions it calls (interprocedural) — are recorded as evidence. Anything too dynamic to resolve is reported as unresolved rather than silently dropped.
  2. Classification assigns each node capabilities and a source category using a signal hierarchy: AST-detected calls (HIGH) > MCP annotation hints / known dangerous tool classes like CodeInterpreterTool/ShellTool (MEDIUM) > name/description keywords (LOW). Confidence is reported alongside every finding.
  3. Graph construction assembles a networkx digraph. A flat MCP manifest (no edges) gets a synthesized client hub wired to every tool, modeling that one session can call them all.
  4. Analysis detects trust boundaries, enumerates source→sink paths that don't cross one, and applies five rules (four graph-reachability + intra-tool value taint).
  5. Reporting renders findings as console / JSON / SARIF and sets the exit code.

What it detects

Rule What it flags CWE (informal)
confused-deputy An unmitigated path from an untrusted source to a privileged sink CWE-441
lethal-trifecta One subgraph with untrusted input + private data + external egress (Willison) CWE-668
cyclic-delegation An agent-to-agent delegation cycle that can bypass per-hop capability limits CWE-441
guardrail-only A path whose only control is a prompt-based/sanitizer guard, not a structural boundary CWE-863
tool-injection A tool whose own input flows unsanitized to a dangerous sink (intra-tool value taint) CWE-78 / 918 / 22

Rules 1–4 are cross-agent reachability (can untrusted data reach a sink across the graph); rule 5 is intra-tool value taint (is a single tool's own input passed to a dangerous sink — a classic injection bug). See DESIGN.md §5a for why the inter-node analysis is reachability rather than value-tracking, grounded in the AgentFlow / VIPER-MCP literature.

Capabilities: CODE_EXEC, FILE_WRITE, FILE_READ, NETWORK_EGRESS, CREDENTIAL_ACCESS, AGENT_DELEGATION, EXTERNAL_ACTION (financial/physical/publishing actions), READ_ONLY_QUERY. Sources: UNTRUSTED_EXTERNAL, USER_INPUT, TRUSTED_INTERNAL.

What counts as a trust boundary (a path crossing one is mitigated): a human-in-the-loop interrupt(); a deterministic validator with a rejection path (pydantic/jsonschema validation, or a regex/allowlist check followed by raise); an OpenAI Agents SDK guardrail (tool_input_guardrails / output_guardrails, trusted as declared, not independently verified); or a node you explicitly trust in the config. A bare sanitizer (re.sub, .strip) or an LLM "is this safe?" check is not a boundary.

Installation

Requires Python 3.11+.

pip install agent-boundary-scan

Or from source:

git clone https://github.com/AureliusOctavion/agent-boundary-scan.git
cd agent-boundary-scan
pip install -e .

Usage

agent-boundary-scan scan <path>          # a .py file, a .json manifest, or a directory
agent-boundary-scan scan src/ -f sarif -o results.sarif
agent-boundary-scan scan graph.py --fail-on CRITICAL
Option Meaning
-f, --format console (default), json, or sarif
-o, --output Write the report to a file instead of stdout
--fail-on Severity gate for the exit code: CRITICAL | HIGH | MEDIUM | LOW (default HIGH)
--config Path to an .agent-boundary-scan.yml (default: found next to the target)

Exit codes: 0 clean, 1 a finding at or above the --fail-on severity was found, 2 usage/scan error. That makes scan usable directly as a CI gate.

Console output

1 finding(s) across 1 file(s): 1 critical

1. [CRITICAL] confused-deputy  (confidence: high, CWE-441)  confused_deputy_langgraph.py:31
   Untrusted data (from: fetch_page) can reach privileged sink 'run_command' (CODE_EXEC) with no
   trust boundary in between. Example path: fetch_page -> run_command.
   path: fetch_page -> run_command

SARIF (for GitHub code scanning)

Severity maps to the SARIF level plus a numeric security-severity (so GitHub ranks findings); confidence maps to properties.precision (the CodeQL convention).

{
  "ruleId": "confused-deputy",
  "level": "error",
  "message": { "text": "Untrusted data (from: fetch_page) can reach privileged sink 'run_command' (CODE_EXEC) ..." },
  "properties": { "security-severity": "9.0", "precision": "high", "cwe": "CWE-441" },
  "locations": [ { "physicalLocation": {
    "artifactLocation": { "uri": "confused_deputy_langgraph.py" },
    "region": { "startLine": 31 } } } ]
}

Configuration (.agent-boundary-scan.yml)

# Nodes to trust as boundaries even if the static heuristics don't recognize them.
# Every path cleared this way is labeled "boundary trusted via user override" in output.
trusted_boundaries:
  - my_module.custom_validator
  - approval_node

# Severity at or above which `scan` exits non-zero (overridable with --fail-on).
fail_on_severity: HIGH

Keeping overrides in a checked-in file (rather than an in-code decorator) means they work for MCP-manifest-only scans too, and every trust decision is visible in one PR-reviewable place.

GitHub Action

A reusable action ships in this repo. It installs the scanner, runs it, and gates the build; set upload-sarif: true (with security-events: write) to feed findings into GitHub code scanning:

permissions:
  security-events: write   # only needed for upload-sarif
jobs:
  agent-boundary-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: AureliusOctavion/agent-boundary-scan@v1
        with:
          path: .
          fail-on: HIGH
          upload-sarif: true

Inputs: path (default .), fail-on (default HIGH), output (SARIF path), config, upload-sarif (default false), python-version (default 3.11).

pre-commit hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/AureliusOctavion/agent-boundary-scan
    rev: v1.1.1
    hooks:
      - id: agent-boundary-scan

Scans the whole tree on commit (the analysis is graph-based). Override the target or gate with args: ["scan", "src/", "--fail-on", "CRITICAL"].

Limitations (read these)

This is a triage/assist tool, not a proof of safety. A clean scan means "no obvious structural red flags, plus here is where I couldn't see" — never "safe". Key false-negative/false-positive risks (full discussion in DESIGN.md §7):

  • Heuristic classification (LOW-confidence, keyword-based) both under- and over-classifies; confidence is always reported so you can weigh it.
  • Interprocedural analysis is module-local and bare-name only — a node is scanned together with local helpers it calls, but danger reached only through imported code or method calls (obj.foo()) is still under-reported.
  • MCP-only scans have no function bodies to inspect, so confidence is capped at MEDIUM/LOW, and the co-reachability model assumes all of a manifest's tools are exposed to one agent (can over-flag).
  • MCP annotation hints are server-supplied claims, treated as untrusted per the MCP spec.
  • Cyclic-delegation deliberately excludes agent↔tool ReAct loops (can miss a cycle through an agent that also wraps tools).

Prior art

  • pajaMAS — Trail of Bits (Triedman et al., 2025): MAS-hijacking control-flow attacks.
  • InjecAgent (ACL Findings 2024) and AgentDojo (NeurIPS 2024): indirect-prompt-injection benchmarks for tool-using agents.
  • Prompt Flow Integrity (Kim, Choi, Lee, 2025) and MiniScope (2025): runtime enforcement / least-privilege reconstruction — complementary to this tool's static, pre-deploy analysis.
  • OWASP Top 10 for Agentic Applications (2026) and Simon Willison's lethal trifecta (2025).
  • "Capability Gates Are Not Authorization" (arXiv 2606.28679, 2026): frameworks gate which tools a model sees but don't re-authorize the specific arguments of a specific call — the gap confused-deputy paths exploit.
  • AgentFlow (arXiv 2607.01640, 2026): the first static analysis framework for agent programs — independently validates this tool's approach (LLM as a taint conduit; reachability over an over-approximating dependency graph).
  • VIPER-MCP (arXiv 2605.21392, 2026): intra-tool taint of MCP servers (106 zero-day CVEs) — the basis for the tool-injection rule.

Fixtures under fixtures/ recreate the patterns (not verbatim code) of these scenarios; the test suite asserts every vulnerable fixture is flagged and every safe fixture is not.

Future work

  • Deeper interprocedural resolution — follow method calls and imported helpers (needs light type/import resolution), beyond today's module-local bare-name expansion.
  • Compiled subgraphs as nodes — inline a builder.compile() used as an add_node action (Command/Send handoffs and interrupt_before/after pause points are already handled).
  • MCP resources & prompts (@mcp.resource, @mcp.prompt) — resources as untrusted sources.
  • Full argument-level dataflow — track the untrusted value into the sink's dangerous argument (today's taint is graph-reachability with self-node and per-sink precision refinements).
  • AutoGen / ag2, Google ADK, Pydantic AI extraction, and cross-framework tracing.
  • CrewAI Task/context chaining (v1 models agents, tools, and delegation, not task data-flow).
  • SARIF codeFlows — render the full source→sink chain as thread-flow locations.
  • Correlating an MCP manifest with the server implementation source when available (upgrades MCP-only confidence from MEDIUM to HIGH).

Development

pip install -e ".[dev]"
pytest -q          # 166 tests
ruff check .       # lint

CI (GitHub Actions) runs lint + tests on Python 3.11 and 3.12.

License

MIT

Metadata

Release files for agent-boundary-scan 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-boundary-scan 1.1.1
File Size Uploaded Provenance
agent_boundary_scan-1.1.1.tar.gz 106.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for agent-boundary-scan 1.1.1
File Interpreter ABI Platform
agent_boundary_scan-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 165.2 kB

Release files / agent_boundary_scan-1.1.1.tar.gz

Download URL agent_boundary_scan-1.1.1.tar.gz
Size 106.1 kB
Tags Source
SHA-256 checksum
How to use checksums
4ebf0e10c47cce5f3c827d24ddd6ba8ce2c4b2ee038d8926830fd0ced9505ee5
BLAKE2b-256 checksum
How to use checksums
3fe14cef91368f3c94dd2eba5bab814323cca7da1f453b6c384bf4f4eb0dd4de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / agent_boundary_scan-1.1.1-py3-none-any.whl

Download URL agent_boundary_scan-1.1.1-py3-none-any.whl
Size 59.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2421b3b67c692aea82bb09e2da2238e0180e6080b37066d2cf0819f069a4424f
BLAKE2b-256 checksum
How to use checksums
1c438d5fdcec293668d8c00c1cf5e843f418592b8f486c3372a0aa24c005a53a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Security for agent-boundary-scan 1.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for agent_boundary_scan-1.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page