Metadata-Version: 2.5
Name: agent-airlock
Version: 0.10.4
Summary: A type-checker and contract layer for AI agent tool calls, deny-by-default, in-process, Pydantic-only. Strict argument validation, ghost-argument stripping, and self-healing retries for MCP servers and agent frameworks.
Project-URL: Homepage, https://sattyamjjain.github.io/agent-airlock/
Project-URL: Documentation, https://sattyamjjain.github.io/agent-airlock/
Project-URL: Repository, https://github.com/sattyamjjain/agent-airlock
Project-URL: Changelog, https://github.com/sattyamjjain/agent-airlock/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/sattyamjjain/agent-airlock/issues
Project-URL: Benchmarks, https://github.com/sattyamjjain/agent-airlock/blob/main/BENCHMARK.md
Project-URL: CVE catalog, https://sattyamjjain.github.io/agent-airlock/cves/
Author-email: Sattyam Jain <sattyamjjain@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agent-safety,agent-security,ai-security,e2b,guardrails,least-privilege,llm,llm-security,mcp,model-context-protocol,pydantic,sandbox,tool-poisoning,validation
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Pydantic
Classifier: Framework :: Pydantic :: 2
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: pydantic<3.0,>=2.0
Requires-Dist: tomli>=2.0; python_version < '3.11'
Provides-Extra: all
Requires-Dist: claude-agent-sdk<0.3.0,>=0.1.58; extra == 'all'
Requires-Dist: cloudpickle>=3.0; extra == 'all'
Requires-Dist: crewai<2.0,>=1.14.4; extra == 'all'
Requires-Dist: cryptography>=42.0; extra == 'all'
Requires-Dist: e2b-code-interpreter<3.0,>=1.0; extra == 'all'
Requires-Dist: e2b<3.0,>=1.0; extra == 'all'
Requires-Dist: fakeredis>=2.20; extra == 'all'
Requires-Dist: fastmcp<5.0,>=2.0; extra == 'all'
Requires-Dist: google-adk<3.0,>=2.0; extra == 'all'
Requires-Dist: google-cloud-modelarmor>=0.2; extra == 'all'
Requires-Dist: mcp>=1.0; extra == 'all'
Requires-Dist: pydantic-ai<3.0,>=1.88.0; extra == 'all'
Requires-Dist: redis<7.0,>=5.0; extra == 'all'
Requires-Dist: structlog>=24.0; extra == 'all'
Requires-Dist: textual>=0.40; extra == 'all'
Provides-Extra: attested
Requires-Dist: cryptography>=42.0; extra == 'attested'
Provides-Extra: bench
Requires-Dist: agentdojo>=0.1.35; extra == 'bench'
Provides-Extra: claude-agent
Requires-Dist: claude-agent-sdk<0.3.0,>=0.1.58; extra == 'claude-agent'
Provides-Extra: console
Requires-Dist: textual>=0.40; extra == 'console'
Provides-Extra: crewai
Requires-Dist: crewai<2.0,>=1.14.4; extra == 'crewai'
Provides-Extra: crypto
Requires-Dist: cryptography>=42.0; extra == 'crypto'
Provides-Extra: dev
Requires-Dist: bandit>=1.9.4; extra == 'dev'
Requires-Dist: cloudpickle>=3.0; extra == 'dev'
Requires-Dist: cyclonedx-bom>=4.0; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-benchmark>=4.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.2; extra == 'dev'
Requires-Dist: safety>=3.0; extra == 'dev'
Requires-Dist: structlog>=24.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Requires-Dist: pymdown-extensions>=10.0; extra == 'docs'
Provides-Extra: google-adk
Requires-Dist: google-adk<3.0,>=2.0; extra == 'google-adk'
Provides-Extra: logging
Requires-Dist: structlog>=24.0; extra == 'logging'
Provides-Extra: mcp
Requires-Dist: fastmcp<5.0,>=2.0; extra == 'mcp'
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: modal
Requires-Dist: cloudpickle>=3.0; extra == 'modal'
Requires-Dist: modal>=0.65; extra == 'modal'
Provides-Extra: model-armor
Requires-Dist: google-cloud-modelarmor>=0.2; extra == 'model-armor'
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai<3.0,>=1.88.0; extra == 'pydantic-ai'
Provides-Extra: redis
Requires-Dist: fakeredis>=2.20; extra == 'redis'
Requires-Dist: redis<7.0,>=5.0; extra == 'redis'
Provides-Extra: sandbox
Requires-Dist: cloudpickle>=3.0; extra == 'sandbox'
Requires-Dist: e2b-code-interpreter<3.0,>=1.0; extra == 'sandbox'
Requires-Dist: e2b<3.0,>=1.0; extra == 'sandbox'
Description-Content-Type: text/markdown

<div align="center">

# Agent-Airlock

### A deny-by-default contract layer for AI agent tool calls

**Strict argument validation, ghost-argument stripping, and self-healing retries — one decorator, any agent or MCP server. Pydantic-only core.**

[![PyPI version](https://img.shields.io/pypi/v/agent-airlock?logo=pypi&logoColor=white&color=3775A9)](https://pypi.org/project/agent-airlock/)
[![Downloads](https://img.shields.io/pypi/dm/agent-airlock?logo=python&logoColor=white&color=success)](https://pypistats.org/packages/agent-airlock)
[![CI](https://img.shields.io/github/actions/workflow/status/sattyamjjain/agent-airlock/ci.yml?logo=github&label=CI&color=success)](https://github.com/sattyamjjain/agent-airlock/actions/workflows/ci.yml)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-3776AB?logo=python&logoColor=white)](https://www.python.org/downloads/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-green)](https://opensource.org/licenses/Apache-2.0)

<!-- TEST-BADGE-START -->
<!-- Auto-generated by scripts/update_test_badge.py. Do not edit by hand. -->
**Test suite:** 4,469 tests · **Coverage:** 87.39% · **v0.10.4**<br>
<sub>Coverage runs 4,457 of 4,469; the 12 excluded are 8 benchmark tests (not correctness tests) and 4 docker-marked (need a daemon — they run in CI's `docker-sandbox` job, against the image built from the Dockerfile at the repo root).</sub>
<!-- TEST-BADGE-END -->
[Quickstart](#quickstart) · [Benchmarks](#benchmarks) · [Frameworks](#framework-support) · [Docs](https://sattyamjjain.github.io/agent-airlock/) · [How numbers are reported](#how-numbers-are-reported-here)

</div>

---

## The problem

An LLM decides which tool to call and what to pass it. Nothing checks the arguments before
they reach your code.

```python
# Your tool expects an int. The model sends a string, and invents a parameter.
transfer_funds(amount="500", to="alice", force=True)
#                     ^^^^^              ^^^^^^^^^^
#                     wrong type         never in your signature
```

Type coercion turns `"500"` into `500` and moves on. The invented `force=True` lands in
`**kwargs` and silently changes behaviour. Neither is a model failure you can prompt away —
it is a missing contract at the call boundary.

## Quickstart

```bash
pip install "agent-airlock>=0.10.3"
```

```python
from agent_airlock import Airlock

@Airlock()
def transfer_funds(amount: int, to: str) -> str:
    return f"Sent ${amount} to {to}"
```

That is the whole integration. Now:

| The model sends | What happens |
|---|---|
| `amount="500"` | **Blocked** — Pydantic V2 strict mode, no coercion. Returns a structured error carrying `fix_hints` the model can retry against |
| `force=True` | **Stripped** — a parameter that is not in your signature never reaches your function |
| `amount=500, to="alice"` | **Executes** |

A blocked call returns an `AirlockResponse` rather than raising, so the model gets a
refusal it can act on instead of a traceback it cannot.

## What it does

The decorator runs a fixed sequence of gates before your function and sanitises after it.
Everything except validation is off by default.

| Layer | What it covers |
|---|---|
| **Validate** | Pydantic V2 strict mode · ghost-argument stripping · self-healing `fix_hints` |
| **Authorize** | RBAC · token-bucket rate limits · time windows · per-model-tier cost budgets · capability gating |
| **Isolate** | Filesystem path validation · network egress control · sandboxed execution (E2B, Modal, Docker, local) |
| **Sanitize** | PII and secret masking, including opt-in Indic PII · output truncation · JSON-Lines audit log |
| **MCP guards** | Per-CVE guards for stdio injection, OAuth, DNS rebinding, SSRF, eval-RCE, WebSocket origin and task lifecycle — tracking MCP spec revision `2026-07-28` |

Full reference on the [documentation site](https://sattyamjjain.github.io/agent-airlock/).

## Benchmarks

Every row is one command you can run. Dates are when the number was last actually produced,
not when the row was last edited — [`check_benchmark_freshness.py`](scripts/check_benchmark_freshness.py)
fails the release if any row drifts past 30 days.

| Benchmark | Result | Reproduce |
|---|---|---|
| **Cross-tool block-rate** · 210 tool calls | **100% blocked · 0% false-positive · p50 ~2µs** · _6 of 10 OWASP ASI slots measured; ASI07–ASI10 are n=0_ | `python -m benchmarks.blockrate` · _re-run 2026-09-06_ · [results](benchmarks/blockrate/RESULTS.md) |
| **Least-privilege** · ToolPrivBench, 100 scenarios | **100% over-privileged blocked · 100% low-privileged allowed** · OPUR 100% → 0% | `python -m benchmarks.toolprivbench` · _re-run 2026-09-08_ · [results](benchmarks/toolprivbench/RESULTS.md) |
| **Adaptive attacker** · AgentDojo, all 4 suites | **86.0% of injection→target tool-calls blocked** (524/609, deterministic bound). Model-in-the-loop ASR 45% → 10%, but that is **one model family on a 60-pair subset** | `python -m benchmarks.agentdojo.run` · _re-run 2026-09-08_ · [results](benchmarks/agentdojo/RESULTS.md) |
| **vs. native MCP gateway** · 12 malformed payloads | **airlock 12/12 blocked · Docker MCP Gateway 0/12** · 0% false-positive on both | `python -m benchmarks.vs_gateway` · _re-measured live 2026-09-12_ · [results](benchmarks/vs_gateway/RESULTS.md) |
| **Prompt injection across agent harnesses** | **A null result, published as one.** `claude-code` and `codex` each acted on the planted script **0/36** — but they ignored the benign twin just as completely, so this is indifference to the channel, not detection | `python -m benchmarks.harness_injection` · _last verified 2026-08-26_ · [results](benchmarks/harness_injection/RESULTS.md) |
| **MCP spec conformance** · `@modelcontextprotocol/conformance` | Run against the wire-path validators, outcome published in full. **Not a full MCP server/client conformance pass** — airlock is a request validator, not a server, so it does not claim one | [`benchmarks/mcp_conformance/RESULTS.md`](benchmarks/mcp_conformance/RESULTS.md) · _re-run 2026-09-08_ |

Summary and method: [`BENCHMARK.md`](BENCHMARK.md).

## Where this sits

Airlock is **in-process**. It is not a proxy, a gateway or a sidecar — it runs inside the
process that executes the tool, which is the only place the real Python arguments exist.

That makes it complementary to the layers around it, not a replacement for them. A gateway
authenticates identity and transport and sandboxes the server; it does not validate the
tool call's argument contract. Measured live against a Docker MCP Gateway on 12 malformed
payloads, the gateway forwarded **12/12** that airlock blocks.

## Framework support

Both paths use the same `@Airlock()` decorator. **Adapter-shipped** means a dedicated module
under `src/agent_airlock/integrations/` handles framework-specific glue — signature
preservation, tool-registry rewrites, request-shape adapters. **Example-only** means the
decorator works out of the box with no adapter.

**Adapter-shipped (12):** LangChain (`integrations/langchain.py`),
LangGraph (`integrations/langgraph_toolnode_compat.py`),
OpenAI Agents SDK (`integrations/openai_guardrails.py`),
Anthropic Messages API (`integrations/anthropic.py`),
Anthropic Claude Agent SDK (`integrations/anthropic_claude_agent_sdk.py`, v0.6.1+),
smolagents (`integrations/smolagents_wrapper.py`),
Gemini 3 Agent Mode (`integrations/gemini3_tool_shape_adapter.py`),
GPT-5.5 (`integrations/gpt5_5_tool_shape_adapter.py`),
PydanticAI (`integrations/pydantic_ai.py`, v0.7.1+),
CrewAI (`integrations/crewai.py`, v0.7.2+),
Google ADK (`integrations/google_adk.py`, v0.9.0+),
FastMCP (`agent_airlock/mcp/`).

**Example-only (2):** AutoGen, LlamaIndex — decorator-compatible without an adapter.

<details>
<summary><b>Every framework: adapter, doc and runnable example</b></summary>

### Complete Examples

| Framework | Path | Surface |
|-----------|------|---------|
| LangChain | [adapter](./src/agent_airlock/integrations/langchain.py) · [example](./examples/langchain_integration.py) | @tool, AgentExecutor |
| LangGraph | [adapter](./src/agent_airlock/integrations/langgraph_toolnode_compat.py) · [example](./examples/langgraph_integration.py) | StateGraph, ToolNode |
| OpenAI Agents | [adapter](./src/agent_airlock/integrations/openai_guardrails.py) · [example](./examples/openai_agents_sdk_integration.py) | Handoffs, manager pattern |
| Anthropic API | [adapter](./src/agent_airlock/integrations/anthropic.py) · [example](./examples/anthropic_integration.py) | Direct Messages API |
| Claude Agent SDK | [adapter](./src/agent_airlock/integrations/anthropic_claude_agent_sdk.py) · [doc](./docs/integrations/anthropic-claude-agent-sdk.md) | `wrap_agent(agent, policy=...)` |
| smolagents | [adapter](./src/agent_airlock/integrations/smolagents_wrapper.py) · [example](./examples/smolagents_integration.py) | CodeAgent, E2B |
| Gemini 3 | [adapter](./src/agent_airlock/integrations/gemini3_tool_shape_adapter.py) | `function_call` carrier + `thought_signature` redaction |
| GPT-5.5 | [adapter](./src/agent_airlock/integrations/gpt5_5_tool_shape_adapter.py) | `gpt_5_5_agent_defaults` preset |
| FastMCP | [adapter](./src/agent_airlock/mcp/__init__.py) · [example](./examples/fastmcp_integration.py) | `@secure_tool` decorator |
| PydanticAI | [adapter](./src/agent_airlock/integrations/pydantic_ai.py) · [doc](./docs/integrations/pydantic-ai.md) · [example](./examples/pydanticai_integration.py) | `wrap_agent(agent, policy=...)` + output_validate hook |
| CrewAI | [adapter](./src/agent_airlock/integrations/crewai.py) · [doc](./docs/integrations/crewai.md) · [example](./examples/crewai_integration.py) | `wrap_crew(crew, policy=...)` + task-level tool overrides |
| Google ADK | [adapter](./src/agent_airlock/integrations/google_adk.py) · [doc](./docs/integrations/google-adk.md) · [example](./examples/google_adk_integration.py) | `wrap_agent(agent, policy)` + `tool_context` relaxation |
| LlamaIndex | [example only](./examples/llamaindex_integration.py) | ReActAgent |
| AutoGen | [example only](./examples/autogen_integration.py) | ConversableAgent |

</details>

Decorator-ordering rules per framework: [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md).

## Security coverage

Mapped against the **OWASP Agentic Top-10 (2026)**. `Partial` means airlock covers the
runtime leg and something upstream is out of scope. Every label is justified per row in
[`docs/owasp-agentic-2026-coverage.md`](docs/owasp-agentic-2026-coverage.md), which is
generated from a YAML source and byte-diffed in CI.

| Risk | Primary controls | Coverage |
|---|---|---|
| **ASI01 Agent Goal Hijack** | Sequence guard, action-contradiction gate, tool-output trust guard | Partial |
| **ASI02 Tool Misuse and Exploitation** | Strict validation, ghost-arg stripping, `SafePath` / `SafeURL`, capability gating | Full |
| **ASI03 Identity and Privilege Abuse** | RBAC, signed agent identity, capability-union boundary, privilege right-sizing | Partial |
| **ASI04 Agentic Supply Chain Vulnerabilities** | Description and manifest pinning, schema `$ref` guard, tool-definition pin, attested admission, and 43 CVE/advisory regression tests in `tests/cves/` (the 36 CVE-numbered ones are published in [the generated catalog](docs/cves/index.md)) | Partial |
| **ASI05 Unexpected Code Execution (RCE)** | Eval-RCE guards, sandboxed execution, stdio command-injection guards | Full |
| **ASI06 Memory & Context Poisoning** | Auto-memory provenance, cross-tenant isolation, conversation tracking | Partial |
| **ASI07 Insecure Inter-Agent Communication** | A2A guard, MCP proxy guard, transport validation | Partial |
| **ASI08 Cascading Failures** | Circuit breaker, retry policy, amplification budget | Full |
| **ASI09 Human-Agent Trust Exploitation** | Human-oversight decorator, elicitation guard, honeypot deception | Partial |
| **ASI10 Rogue Agents** | Audit telemetry, anomaly detector, kill switch — **no quarantine primitive** | Monitor-only |

### MCP-specific mapping

Against the **MCP Top-10**. Evidence is compressed here; each row's full justification is in
[`docs/owasp-agentic-2026-coverage.md`](docs/owasp-agentic-2026-coverage.md).

| Risk | Primary controls | Coverage |
|---|---|---|
| **MCP01 Token Mismanagement & Secret Exposure** | `MCPProxyGuard` rejects passthrough headers and enforces audience; sanitizer masks secrets in tool output | Partial |
| **MCP02 Privilege Escalation via Scope Creep** | `require_agent_id` preset, capability gating, `CredentialScope`, runtime capability-union deny at grant time | Full |
| **MCP03 Tool Poisoning** | Ghost-arg rejection, `SafePath` / `SafeURL`, `mcp_description_manifest_guard`, `install_*` deny | Full |
| **MCP04 Software Supply Chain Attacks & Dependency Tampering** | `stdio_guard_ox_defaults()` (Ox 2026-04-16 advisory), `download_plugin_*` / `fetch_plugin_*` deny | Full |
| **MCP05 Command Injection & Execution** | `stdio_guard` shell-metachar and deny-pattern rules, `exec_*` / `run_*` / `system_*` deny, sandbox for `DANGEROUS` | Full |
| **MCP06 Intent Flow Subversion** | `ToolOutputTrustGuard` envelopes injected-instruction output; `@requires_human_oversight` gates high-value actions | Partial |
| **MCP07 Insufficient Authentication & Authorization** | OAuth 2.1 + PKCE S256 helpers, `iss` mix-up validation, header/body routing integrity, unsigned-`_meta` trust boundary, step-up scope guard, Tasks lifecycle and admission guards. Transport-level auth remains server-side | Partial |
| **MCP08 Lack of Audit and Telemetry** | JSON-Lines audit log, OpenTelemetry export, spans and metrics | Full |
| **MCP09 Shadow MCP Servers** | Attested tool-server admission, LAN unauthenticated-MCP guard | Partial |
| **MCP10 Context Injection & Over-Sharing** | PII and secret sanitizer, workspace-scoped config, `ToolOutputTrustGuard` untrusted-data envelope | Full |

Use it directly via the presets named above, or the per-CVE catalogue at
[`docs/cves/index.md`](docs/cves/index.md).

## CLI

### One CLI: `airlock <command>` (unified dispatcher, full command set since v0.8.56)

```bash
airlock scan-tools ./tools.json     # static contract check on tool declarations
airlock doctor                      # environment and config diagnosis
airlock explain --unused-scopes     # diff granted privilege against used
airlock attest receipt emit ...     # signed evidence that a run was gated
airlock policy compile "..."        # English to typed policy (needs an LLM backend)
airlock kill-switch trigger         # HMAC-signed fleet freeze
```

Per-command documentation: [CLI reference](https://sattyamjjain.github.io/agent-airlock/).

## Performance

| Metric | Value | | Detail |
|---|---|---|---|
| **Validation overhead** | ~2µs p50 per decision | | deterministic, in-process |
| **Framework integrations** | 14 | | [see above](#framework-support) |
| **Core dependencies** | 0 | | beyond the Pydantic foundation; everything else is an opt-in extra |

## How numbers are reported here

This project holds itself to a rule worth stating plainly: **a claim in this README is
gated by a test, or it is not made.**

- Counts — tests, CVEs, adapters — are cross-checked against the tree by
  [`tests/test_numeric_claim_parity.py`](tests/test_numeric_claim_parity.py) and siblings.
  A number that drifts fails the build.
- Benchmark dates are enforced. A row older than 30 days blocks a release.
- **Null results are published.** The prompt-injection row above found nothing and says so,
  in the same table as the wins.
- Competitor numbers are never fabricated. Where an incumbent is cited it is from their
  published scope and marked *not re-run*; where a comparison **was** run live, it says so.
- Where no head-to-head exists, that is recorded rather than implied — see
  [**Prior art**](./PRIOR_ART.md), including arXiv:2608.18351, where the honest claim is
  complementarity and not superiority.

Known gaps and their status: [`ROADMAP.md`](ROADMAP.md).

## Documentation

| | |
|---|---|
| [Documentation site](https://sattyamjjain.github.io/agent-airlock/) | Guides, API reference, CLI, integrations |
| [`BENCHMARK.md`](BENCHMARK.md) | Benchmark summary and method |
| [`docs/cves/index.md`](docs/cves/index.md) | Generated CVE catalogue |
| [`ROADMAP.md`](ROADMAP.md) | What is not done, and why |
| [`PRIOR_ART.md`](./PRIOR_ART.md) | External research this rests on |
| [`CHANGELOG.md`](CHANGELOG.md) | Release history |
| [`AGENTS.md`](AGENTS.md) | Contributor contract |

## Contributing

Read [`AGENTS.md`](AGENTS.md) first — it is the load-bearing contributor contract — then
[`CONTRIBUTING.md`](CONTRIBUTING.md).

```bash
pip install -e ".[dev]"
make test lint
```

Every `feat:` needs a regression test. Every CVE fixture needs a primary-source URL in the
commit message. Claims need gates.

## Security

Report vulnerabilities via [`SECURITY.md`](SECURITY.md). Please do not open a public issue
for an unpatched vulnerability.

## License

[Apache-2.0](LICENSE). Relicensed from MIT on 2026-09-06 to add an explicit patent grant.
Citation metadata: [`CITATION.cff`](CITATION.cff).

---

<div align="center">

If this is useful, a ⭐ helps other people find it.

[Report a bug](https://github.com/sattyamjjain/agent-airlock/issues) ·
[Request a feature](https://github.com/sattyamjjain/agent-airlock/issues) ·
[Discussions](https://github.com/sattyamjjain/agent-airlock/discussions)

</div>
