agentlure
Detonate MCP servers against fake credentials before you install them.
agentlure runs an untrusted MCP server inside a sandbox planted with canary credentials and a sinkholed network, calls each of its tools once, and reports what the server actually tried to read or send out. The verdict is deterministic: no account, no LLM judge, no static pattern matching.
Quickstart
pip install agentlure
# or, with uv:
uvx agentlure scan --command "python3 path/to/server.py"
agentlure scan --config ~/.config/mcp.json --server my-server
agentlure scan --command "npx -y some-mcp-server"
Exit codes: 0 = CLEAN, 1 = SUSPICIOUS, 2 = MALICIOUS
(see --fail-on below for CI gating).
Requires Linux, Python 3.9+, and a sandbox backend: Docker, Podman, or
Linux namespaces (unshare + strace). agentlure refuses to run a
target when no sandbox backend is available.
How a scan works
- Canaries are minted, fresh for every run: an AWS key pair, a
GitHub token, an npm token, an SSH key pair, and a
.envbundle (database URL, Stripe key, SendGrid key). Each value has a recognizable prefix and a random suffix, so a captured payload attributes to exactly one run. - A synthetic HOME is planted with those canaries at the usual
paths:
~/.aws/credentials,~/.ssh/id_rsa,~/.env,~/.config/gh/hosts.yml,~/.npmrc. The target'sHOMEpoints at the synthetic directory. Your real home is never the target's home. - The target starts inside the sandbox with no real network
egress. Its only reachable endpoint is a sinkhole proxy on
loopback, injected through the standard
HTTP_PROXY/HTTPS_PROXYenvironment variables. The proxy logs every request and searches the payload — URL, headers, body — for canary values in raw, base64, hex, and URL-encoded form, including encoded-inside-encoded payloads (for example a base64'd file riding in a query string). - agentlure drives the server over MCP (stdio, JSON-RPC):
initialize,tools/list, then onetools/callper tool with benign arguments synthesized from the tool's input schema (strings become"hello", integers1, enums their first value, and so on). - Behaviour is graded from strace and proxy evidence, and reports
are written: a terminal summary,
report.json,report.sarif(SARIF 2.1.0), and a standalonereport.html. Reports contain masked canary values only; the full proxy capture is kept innetwork.jsonlnext to the reports.
Evidence and verdicts
| Evidence | Meaning |
|---|---|
canary_egress |
A canary value was found in a payload captured by the sinkhole. |
canary_read |
The target opened a planted canary file. |
unexpected_spawn |
The target executed a network/shell tool (curl, wget, nc, ncat, socat, bash, sh) outside its launch chain. A shell that is itself the launched command, or a wrapper shell exec'd while the server was still starting, does not count. |
write_outside_workdir |
The target wrote, created, or deleted a file outside its sandbox workdir and outside the synthetic HOME. |
- MALICIOUS — any
canary_egress. - SUSPICIOUS — any of the other three classes, without egress.
- CLEAN — the detonation completed and produced no evidence.
- ERROR — the detonation itself failed (the target died, or the MCP handshake never completed), so the scan is inconclusive. ERROR exits 3 and never counts as a pass: a broken setup must not grade a server CLEAN.
Reading a canary is not proof of theft — a server may legitimately look at its own configuration. Sending one out is. That is why only egress grades MALICIOUS.
For CI, --fail-on suspicious|malicious turns the exit code into a
gate: the scan exits 0 when the verdict is below the chosen grade, and
with the grade code (1 or 2) at or above it. Without --fail-on, the
exit code is the grade code itself.
Sandbox backends
Backends are auto-detected in this order: Docker, Podman, namespace.
--backend forces one. A container backend only counts as available
when its sandbox image is present locally; auto-detection skips a
runtime whose image is missing and falls through to the namespace
backend, because a docker run against a missing image kills the
target before the scan starts.
-
Docker / Podman —
runwith a read-only root filesystem,--cap-drop ALL,no-new-privileges, memory/CPU/pids limits, and--network none; the sinkhole proxy runs inside the container on its loopback. The image (defaultagentlure-sandbox:latest, override with--sandbox-imageorAGENTLURE_SANDBOX_IMAGE) must contain Python 3, strace, agentlure itself, and the runtime your target needs. Build it from the repository checkout:docker build -f Dockerfile.sandbox -t agentlure-sandbox:latest .
These backends are implemented to the same interface as the namespace backend but are not covered by the project's own tests — the development environment has no container daemon. Prefer them for hostile targets once you have validated the image.
-
Namespace — user + mount + PID + network namespaces via
unshare. The target'sHOMEis the synthetic home; the credential-bearing paths of the real home (~/.aws,~/.ssh,~/.config/gh,~/.env) are additionally bind-masked with the canary versions when they exist, so absolute-path reads hit canaries too. The network namespace contains loopback only, with the sinkhole on it. The target is traced withstrace -f. This backend is whatagentlure selftestexercises.
Self-safety model
agentlure exists to run hostile code, so the scanner treats itself as part of the attack surface:
- The target never runs outside a sandbox backend; there is no
--no-sandboxflag and there will not be one. - Canary values are fake and minted per run. No real credential is ever planted, read, or passed into the sandbox. The canary manifest reaches the in-sandbox proxy through the environment and is scrubbed from the target's environment before launch.
- The network namespace has no route out; the sinkhole terminates every connection. CONNECT tunnels are logged and closed, never forwarded.
- Wall-clock timeouts, CPU rlimits, and process-group teardown bound every run.
Honest limits of the namespace backend: it redirects HOME, masks the
known credential paths, kills the network, and watches syscalls, but
it shares the host filesystem — a target that goes looking with
absolute paths can see (and, with your user's permissions, modify)
files outside the masked paths, and the PID namespace uses the host's
/proc when mounting a fresh one is not permitted. For code you
actively distrust, use a container backend or a throwaway VM.
Limitations (v0.1)
- Linux only. Stdio MCP servers only — no remote/HTTP servers yet.
- Each tool is called once, with schema-derived arguments. Servers that misbehave only on specific argument values, on a second call, or after a delay longer than the observation window will not be caught by this version.
- A target that exfiltrates through a channel that ignores proxy environment variables gets no route out of the network namespace, so the attempt fails silently instead of producing evidence.
- Verdicts are evidence, not proof of intent: a SUSPICIOUS grade means "look at this", and the HTML report shows exactly what was observed.
GitHub Action
- uses: 0xABCD01/agentlure@v0.1.0
with:
config: mcp.json
server: my-server
fail-on: malicious
Inputs: command or config (+ server), fail-on, version.
Output: verdict. The full report is uploaded as a workflow artifact.
npm wrapper
npx agentlure ... works through a thin Node shim (in npm/): it
delegates to uvx agentlure@0.1.0 when uv is present, else to an
installed Python package (python3 -m agentlure), else it prints
install instructions. The scanner itself is the Python package.
Selftest
The package ships six fixture servers (in agentlure/fixtures/) —
three malicious (SSH-key reader, .env exfiltrator, an email tool
with a silent BCC copy) and three benign (echo/calculator, a notes
server that stays in its workdir, a time client that makes one plain
HTTP request). From any install:
pip install agentlure
agentlure selftest # expects MALICIOUS x3, CLEAN x3
From a source checkout, the unit tests run with
python3 -m unittest discover.
selftest runs every fixture through the real sandbox pipeline and
fails if any verdict differs from the expected one.
Metadata
Release files for agentlure 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| agentlure-0.1.3.tar.gz | 42.1 kB | With provenance | Details for agentlure-0.1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| agentlure-0.1.3-py3-none-any.whl | Python 3 | none | any | Details for agentlure-0.1.3-py3-none-any.whl |
Total release size: 88.2 kB
Release files / agentlure-0.1.3.tar.gz
| Download URL | agentlure-0.1.3.tar.gz |
|---|---|
| Size | 42.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
930f8e24bb6b9700bda5ced43a888aa5b241610378cf18f2c888a1c098040f6c
|
|
BLAKE2b-256 checksum How to use checksums |
cb82eeb3e10c0dab956d4c0fcccdc2ffa501cb350edbd8046d60184fbebd76bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 6, 2026.
Transparency logRelease files / agentlure-0.1.3-py3-none-any.whl
| Download URL | agentlure-0.1.3-py3-none-any.whl |
|---|---|
| Size | 46.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ccd3fcba0481c69e3e2d9599817cc1b2bb59ad6b4203337d1501f19133081fc8
|
|
BLAKE2b-256 checksum How to use checksums |
7e20b46917179cba2836459f9e1c8776d3ffa1ba3fe5241b9a7a7549800c9b08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 6, 2026.
Transparency logSecurity for agentlure 0.1.3
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.
Attestation for agentlure-0.1.3.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 Nov 6, 2026.
Transparency logA 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.