Aran
A transparent security proxy for Model Context Protocol (MCP) stdio servers. Wraps any existing MCP server — no server-specific integration — and gates traffic in both directions before it reaches your agent or your machine:
- Blocks dangerous outbound tool calls — destructive commands (
rm -rf,chmod 777, fork bombs, ...), before they ever reach the real server. - Redacts prompt-injection payloads in inbound tool results — before they reach your agent's context window.
- Audits every gated call to a local JSONL log, so you can see what was blocked and tune false positives.
Why
Autonomous coding agents (Claude Code, Cursor, Windsurf, ...) use MCP servers to read files, run shell commands, and hit the network. If an agent ingests untrusted content — a webpage, a repo file, a database row — containing a hidden prompt injection, it can be manipulated into running destructive commands or exfiltrating secrets, using your own valid credentials. Static-analysis tools and traditional network firewalls are both blind to this: neither operates at the live protocol layer where the agent and its tools actually talk to each other.
Aran sits inline at that layer instead, as a stdio proxy your IDE launches transparently.
New to Aran? docs/guide/ is a full, beginner-friendly walkthrough — one concept per page, from "what is MCP" through installing, wiring it into your IDE, and a hands-on session that proves every gate case works, with real command output at each step. The rest of this README is the fast, condensed version of the same information.
Install
pip install aran
Not on PyPI yet — that command doesn't work today. Install from source instead:
git clone https://github.com/aranaisec-cyber/aran.git
cd aran
pip install -e .
This applies to the one-click badges and .mcp.json below too: they configure
your IDE correctly, but the server will show as errored until you've run the
above at least once. Once published to PyPI, this step goes away — uvx will
fetch Aran automatically, the same way it already does for the fetch server
these examples wrap.
Usage
Wrap the command you'd normally use to launch an MCP server:
aran -- npx -y @modelcontextprotocol/server-filesystem /path/to/project
Wiring into an MCP-speaking IDE
In your IDE's MCP server config, replace the server's command/args with
aran, moving the original command after a -- separator.
Claude Code / Cursor-style JSON config:
{
"mcpServers": {
"filesystem": {
"command": "aran",
"args": ["--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
}
}
}
That's the whole integration. Aran spawns the real server as a child process and relays every message between it and your IDE, gating tool calls and tool results as they pass through — nothing else in your IDE config changes, and the downstream server needs no modification.
Every gated message (allowed or blocked) is logged to ~/.aran/audit.jsonl.
One-click / auto-config
Both badges above wrap a real, production MCP server —
mcp-server-fetch,
the official reference server for fetching web pages — not a toy demo.
It's a deliberate choice: fetching an untrusted URL and having whatever
text is on that page land in your agent's context is exactly the attack
this project defends against, so wrapping it is the most honest possible
demonstration of what Aran does. It needs uv
installed (uvx specifically) and needs no per-user path or account setup
— it works identically for every developer who clicks it.
- Cursor: the "Add to Cursor" badge above installs
aran-fetchdirectly — one click, no manual config. - Claude Code: this repo ships a working
.mcp.jsonat its root wrapping the same server. Claude Code auto-detects project-level.mcp.jsonfiles — clone this repo, open it in Claude Code, and you'll get a one-time approval prompt foraran-fetch. There's no click-to-install deep link for Claude Code (unlike Cursor, it doesn't have one), but.mcp.jsonis the equivalent "ships with the repo, auto-detected" mechanism.
Once it's running, ask your agent to fetch a URL and watch
~/.aran/audit.jsonl — every fetched page's content passes through the
input gate exactly like the manual tests in TESTING.md, just
against the live internet instead of a canned fixture. A page containing
ignore previous instructions (or anything else in
src/aran/default-rules.yaml) gets redacted
before your agent ever sees it.
To wrap a different server instead — your own, or another server
entirely — the pattern is identical: swap the args in .mcp.json, or
generate your own Cursor deep link by base64-encoding
{"command":"python","args":["-m","aran.cli","--",<your command>,<your args...>]}
and using it in
cursor://anysphere.cursor-deeplink/mcp/install?name=<name>&config=<that base64>
— or run claude mcp add <name> --scope project -- python -m aran.cli --
<your command> <your args...>, which writes the .mcp.json entry for you.
Heads up: mcp-server-fetch's own documentation notes it can reach
local/internal network addresses, which is a real consideration for any
fetch-capable tool regardless of Aran — worth knowing if you point it at
anything beyond public URLs.
How it works
- Outbound (
tools/callrequests): the tool name and arguments are checked against a list of destructive-command signatures. A match means the call is never forwarded to the real server — Aran synthesizes a JSON-RPC error response instead. - Inbound (tool results and other responses): every string is scanned
for prompt-injection signatures. A match in agent-visible content gets
replaced with a redaction notice before being relayed; a small, explicit
set of protocol-machinery fields (
protocolVersion,serverInfo, resourceuris, etc.) is exempt from rewriting so a match there doesn't break session negotiation — see the design spec for the exact field list and reasoning. - Aran treats the downstream server it wraps as untrusted — the gating
guarantees are designed to hold even against a malformed or actively
hostile server, not just a well-behaved one. See
tests/fixtures/hostile_server.pyandtests/test_proxy_hostile.pyfor the adversarial test suite this is verified against. - A blocked outbound call gets a JSON-RPC error with
code: -32001and adatafield giving the violation, the matched signature, and a best-efforttarget_node(which argument the match was found in) — useful context for an agent or developer debugging why a call was blocked. A bare-32000with nodatameans Aran itself couldn't safely inspect the message (fail-closed), not a signature match. - Optional: scan a public GitHub repo before it's cloned/downloaded
(
ARAN_SCAN_GITHUB_REPOS=1). If an outbound call references agithub.comrepo (an HTTPS clone URL or an SSH remote, in any tool's arguments — not tied to a specific tool name), Aran fetches that repo's tarball and scans every file against destructive-command, prompt-injection, hardcoded-secret, and supply-chain (curl | bash-style install hooks) signatures before the call that would clone it is forwarded. A match blocks the call withcode: -32002. This is the one feature in Aran that makes outbound network requests, so it's off by default — see Configuration below.
Configuration
Environment variables, read once at startup:
ARAN_MODE=audit— dry-run mode. Gate decisions still run and are still logged (aswould_blockinstead ofblocked), but nothing is actually blocked or redacted — every call and response is forwarded unmodified. Use this to tune signatures against real traffic before turning enforcement on.ARAN_PROFILE=1(ortrue/yes/on) — prints a timing line to stderr for every gated message: how long the outbound check took, and how many JSON leaf nodes the inbound scan visited and in how long.ARAN_SCAN_GITHUB_REPOS=1(ortrue/yes/on) — enables the GitHub repo scan described above. Prints a one-time startup notice, since this is the only toggle that makes Aran reach the network. If the fetch itself fails (offline, rate-limited, repo not found, ...), the call is forwarded anyway — a failed scan is logged as"error", not treated as a match; see docs/guide/08-modes-and-configuration.md for the full reasoning on why this one check fails open instead of closed.
ARAN_MODE=audit aran -- npx -y @modelcontextprotocol/server-filesystem /path
ARAN_SCAN_GITHUB_REPOS=1 aran -- npx -y @modelcontextprotocol/server-filesystem /path
See TESTING.md
for worked examples of ARAN_MODE/ARAN_PROFILE.
Rule config
Signatures live in src/aran/default-rules.yaml, which ships inside
the installed package, under four keys: input_gate_signatures and
output_gate_signatures (the two live gates, described above) plus
secret_signatures and supply_chain_signatures (used only by the
optional GitHub repo scan). The latter two are optional in a hand-edited
rules file — an older file that predates them loads exactly as before,
falling back to a small built-in default for whichever it's missing, with
no warning. Refresh the prompt-injection and destructive-command
signatures from a live labeled dataset with:
python scripts/sync_threat_intel.py
On false positives: the shipped signature set is generated from a public labeled dataset and, like any pattern-based detector, can flag benign content. If something gets redacted/blocked that shouldn't be, please open an issue with the matched signature (visible in the audit log) and the offending text.
Security
This is a security tool — please report vulnerabilities responsibly. See SECURITY.md for scope and reporting instructions.
Development
git clone https://github.com/aranaisec-cyber/aran.git
cd aran
pip install -e ".[dev]"
pytest -v
See CONTRIBUTING.md for project layout and testing
philosophy, and TESTING.md for a step-by-step manual
verification procedure — including seeing the output/input gates block a
destructive command and redact an injection payload live, not just watching
pytest pass.
Status
Early (v0.1, pre-1.0). The core proxy — invocation, bidirectional gating, YAML config, audit log — is implemented and tested, including against an adversarial downstream-server test suite. Not yet covered: a multi-server gateway, enterprise/compliance features, and a hosted control plane — see the design spec for what's explicitly in and out of scope for this stage.
License
Release files for aran 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aran-0.1.0.tar.gz | 83.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aran-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 131.2 kB
Release files / aran-0.1.0.tar.gz
| Download URL | aran-0.1.0.tar.gz |
|---|---|
| Size | 83.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e550b47433d77ec9272bc3a3d30c6fe1b9002531f26b6f93f7cbc2a2d00907fa
|
|
BLAKE2b-256 checksum How to use checksums |
a23ac141287fd7929e6d93e144b16e129672dd7202a2fd791d8edaf6a625f07a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / aran-0.1.0-py3-none-any.whl
| Download URL | aran-0.1.0-py3-none-any.whl |
|---|---|
| Size | 47.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6930943fcdcf010d4796dbb1601720e4c9321a1b1f5557cd7e47570995e868cb
|
|
BLAKE2b-256 checksum How to use checksums |
4183ad1428a40d2db7e79989792cf4cddf3cd3fcc5804e67690f08979784e075
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|