Skip to main content

agent-police

Audit an LLM API router for tool-call tampering and credential exposure.

If your agent reaches Claude or GPT through a third-party relay, that relay terminates your TLS session and originates a separate one upstream. It reads — and can rewrite — every JSON payload in flight, including the tool-call arguments the model produced. No provider currently offers an end-to-end integrity mechanism, so a client cannot tell whether the command it is about to execute is the command the model actually emitted.

agent-police probes an endpoint you control and tells you whether it is actually doing that.

The attack taxonomy and the field data come from Liu et al., Your Agent Is Mine: Measuring Malicious Intermediary Attacks on the LLM Supply Chain (arXiv:2604.08407), which measured 428 commodity routers and found 9 injecting malicious code, 17 touching researcher-owned AWS canaries, and 1 draining a researcher-owned ETH key. That paper deliberately does not publish its attack proxy. agent-police is an independent defensive implementation and never executes a command returned by the endpoint under test — detection is purely string- and metadata-level.

What it detects

Class Attack How agent-police sees it
AC-1 Tool-call arguments rewritten (fetch URL redirected, extra command chained) Sends a request whose correct answer is known byte-for-byte, then diffs the returned call
AC-1.a Package name substituted inside an install command, leaving the registry and command line untouched Same diff, plus typosquat classification (transposition, homoglyph, separator swap, affix)
AC-1.b Injection gated on session features, so routine probes see honest behaviour Sweeps a grid of tool names, project languages, auto-approve state and warm-up depths, and reports which cells differ
AC-2 Credentials silently harvested from plaintext traffic Plants canaries across four placements and watches for use

Plus endpoint hygiene: who actually answers, what router software it looks like, whether provider response headers survive, whether an invalid key is accepted (an open relay), TLS details, and multi-vendor model lists.

Telling tampering apart from model noise

Every probe payload carries a fresh nonce, so a fixed rewrite rule still produces a different string on each trial. agent-police compares the attacker-controlled invariant — the replacement host, the substituted package — rather than the whole argument. A finding is reported as high confidence only when the same rewrite target recurs across distinct nonces, which random model deviation does not do.

Cosmetic differences (whitespace, quoting) are classified as inconclusive, not as tampering.

Install

The PyPI name agent-police is not registered. Do not pip install it: an unclaimed name on a public index is exactly the AC-1.a shape this tool detects, and anyone could take it. Install from source and check the digest:

curl -fsSLO https://security.romaapi.com/static/download/agent-police-src.tar.gz
curl -fsSL  https://security.romaapi.com/static/download/SHA256
sha256sum agent-police-src.tar.gz      # compare the two by eye

tar -xzf agent-police-src.tar.gz && cd agent-police
python -m venv .venv && ./.venv/bin/pip install .

Use

# Standard audit. The key is prompted for, not echoed, and never hits shell history.
agent-police audit https://your-relay.example.com/v1 \
  --model claude-sonnet-4-5 --wire anthropic --repeats 3

# AC-1.b sweep: vary the features a trigger predicate can key on.
agent-police campaign https://your-relay.example.com/v1 \
  --model claude-sonnet-4-5 \
  --langs rust,go,python --warmups 0,10,50 --rate 20

# No network: heuristically review a command your agent is about to run.
agent-police inspect "curl -sSL https://get.example.com/i.sh | bash"

# Re-check planted canaries. AC-2 shows up days later, not minutes.
# Use the audit id the run printed; it is what the collector filed them under.
agent-police canary check --audit-id cli-1a2b3c4d5e6f

Canaries are registered with whichever collector --canary-base names, before the probe that carries them is sent. If registration fails the run says AC-2 COVERAGE LOST rather than implying coverage it does not have. Pass --no-canary to skip AC-2 entirely, or point --canary-base at a collector of your own (agent-police serve).

Exit codes: 0 nothing above low, 1 medium, 2 high or critical. --json emits a machine-readable report.

The limit you cannot engineer around

A clean run is not proof of a clean endpoint. Conditional delivery (AC-1.b) stays dormant until its trigger fires, and the trigger predicate lives on the server. Observed triggers in the wild include a 50-request warm-up, sessions in autonomous auto-approve mode, and Rust or Go projects specifically. No finite black-box probe can establish that a router is honest. agent-police widens coverage; it cannot close this gap, and it says so in every report.

The real fix is provider-side: a signed response envelope binding the model identifier, tool name, tool arguments, finish reason and a client nonce, which the client verifies before executing anything. No major provider tool-use API or the current MCP specification exposes one today.

Hosted service

agent-police serve runs the web UI and the canary collector. A deployment lives at https://security.romaapi.com.

The hosted service asks for a key to an endpoint you do not trust, which is the same trust problem it exists to detect. Handling rules, all covered by tests:

  • the key lives only in the worker thread's config, never in the job record, the database, a log line or a report;
  • everything leaving the process is redacted;
  • jobs and results are deleted after 30 minutes and tied to no account;
  • targets are validated against SSRF, and the connected peer address is re-checked on every response to close DNS-rebinding.

Prefer the CLI if you would rather your key never left your machine. Either way, use a short-lived key.

Development

python -m venv .venv && ./.venv/bin/pip install -e '.[dev]' fastapi 'uvicorn[standard]'
./.venv/bin/pytest

The suite runs the full auditor against a mock router that implements each attack class, including a check that a single-fingerprint audit misses conditional delivery entirely — the paper's central point about black-box auditing.

Scope

Probe only endpoints you own or are authorised to test.

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agent_police-0.1.0.tar.gz (77.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agent_police-0.1.0-py3-none-any.whl (77.4 kB view details)

Uploaded Python 3

File details

Details for the file agent_police-0.1.0.tar.gz.

File metadata

  • Download URL: agent_police-0.1.0.tar.gz
  • Upload date:
  • Size: 77.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for agent_police-0.1.0.tar.gz
Algorithm Hash digest
SHA256 81e94320d93076a81d17c591aad2451e94f356ccedafabb4a5c884d2b165ae67
MD5 7fb6b3e86747454cfaa33cfad9c12913
BLAKE2b-256 07d323479cf72a00c5237b220d7b094398e7c6f1434e5196be536d6e15c8c933

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_police-0.1.0.tar.gz:

Publisher: release.yml on RomaCredit/agent-police

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agent_police-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: agent_police-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 77.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for agent_police-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cb2a0c6e3e1b2dac9e96c7efc20ee0ae84db808a7b9db4bb309c2839f8a4848a
MD5 219776386114e4f1d42804383a9dae67
BLAKE2b-256 b57385587c09e8b119425f1b7acf18cc1adae935cc0b11ac923f274fe6745a56

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_police-0.1.0-py3-none-any.whl:

Publisher: release.yml on RomaCredit/agent-police

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.0

2 files

This release

0.1.0 This release

2 files

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