STATIC SCANNER FOR AI AGENTS

Even the best AI agents leak secrets.
Through skills, tools, and configs nobody read.

Lucin catches it.
The necessary scan before every push, every deploy.

30 OWASP-mapped detectors · 8 frameworks · 76% of known agent vulnerabilities caught
$ pip install lucin && lucin scan .
See what it prints →
MIT · no signup · nothing leaves your machine
AG-TRIFECTA · CRITICAL
read_email query_customers __llm__ post_webhook
SCANNING TOOL BODIES…
|
01 THE SHAPE EVERY AGENT HAS
114 / 156
OPEN-SOURCE AGENT REPOS AFFECTED

had a live path from untrusted input to an external sink.

Not toy examples — smolagents, CAMEL, LlamaIndex, mem0, txtai, autogen, agno, promptflow and others, across the 8 frameworks most agents are built on. 94% of them had at least one HIGH or MEDIUM.

WHY IT KEEPS HAPPENING

An exfiltration doesn't need an exploit. It needs three capabilities that are each completely fine alone:

READS
An input it trusts
read_email · fetch_url · read_pdf
TOUCHES
Something worth taking
query_customers · read_secrets
SENDS
A way out
post_webhook · git_push · upload_s3

Every agent worth shipping has all three — that's what makes it useful. The risk is whether the model can walk from one to the next without anyone having decided it should. These aren't careless repos. They're repos where somebody reviewed every tool and nobody reviewed the combinations.

114 of 156 is how common the wiring is in the world — not a claim that all of it is exploitable in production, which depends on your deployment.

Lucin reads your tool definitions, agent graph and MCP config, and only counts the edges your code actually wired.

SEPARATE QUESTION — HOW MUCH LUCIN FINDS · DENOMINATOR: PATHS, NOT REPOS
76%
of paths found (recall)
98%
of flagged paths are real
30
detectors, OWASP-mapped
0.2s
median scan
02 IT ALREADY HAPPENED
17,600
ACTIONS · 2.5 DAYS · NOBODY WATCHING

Hugging Face, July 2026.

Not a sophisticated exploit. One bad edge, traversed roughly 17,600 times over two and a half days of logs, because nothing was watching the flow.

One edge, in a tool definition, in a file somebody had already reviewed.

Read our teardown — the incident as an information-flow graph →
03 WHAT LUCIN PRINTS

One finding. Severity, the exact path, the line, and the fix.

Every finding answers four questions in one read: how bad, which tool-to-tool path proves it, which single tool to restrict to close every path through it, and where in the code. Same finding, three formats.

PICK A FORMAT

lucin scan ./support-agent/
$ lucin scan ./support-agent/
Target: ./support-agent/ (2 agents, 14 tools, 1 MCP server)
Scan completed in 842ms
── SECURITY SCORE ─────────────────────────────
████████████░░░░░░░░ 62/100 — Needs attention
── RISK SUMMARY ───────────────────────────────
CRITICAL ███ 1
HIGH ██████ 2
MEDIUM ██████ 3
── CRITICAL · AG-TRIFECTA ─────────────────────
Untrusted input reaches an external sink
Agent: support_agent Tool: post_webhook
Proof:
control: read_email __llm__ post_webhook
data: query_customers __llm__ post_webhook
Min-cut fix: restrict 1 tool — closes all 16 paths through it
post_webhook (allow-list hosts, or require approval)
OWASP: LLM06 Excessive Agency
Location: agents/support.py:88

Every number on this site regenerates from a committed command.

04 PROVE IT ON REAL TOOLS

64 combinations have the shape. One is wired.

SHAPED — ARITHMETIC

Sixty-four triples have the right shape: one tool reads untrusted text, one touches private data, one can send. That says nothing about your code.

WIRED — YOUR REPO

A triple is wired when all three tools are registered to the same agent, reachable in a single run, with nothing between them that requires a human. We read that off your tool definitions, agent graph and MCP config.

SUPPORT-AGENT · 14 TOOLS CLICK A TOOL TO RESTRICT IT
64 have the shape
1 wired in your code
One path is drawn because one path exists.
YOUR 14 TOOLS

The gap between those two numbers is the entire product. Sixty-four is a list nobody triages. One is a decision — and it arrives with the name of the tool that closes it.

Other scanners don't model tool composition at all. That isn't a knock — it's a different job.

05 THE HABIT

Triage once. After that, only new findings can fail the build.

A scanner that starts from zero on every run teaches you to ignore it. Lucin writes today's findings to a baseline file you commit. From then on CI fails on paths that weren't there yesterday — not on the backlog you inherited.

CLICK A STEP

LOCAL · ONE TIME, BEFORE CI EXISTS
$ lucin scan . --write-baseline .lucin-baseline.json
── BASELINE WRITTEN ───────────────────────────
CRITICAL ███ 1
HIGH ██████ 2
MEDIUM ██████ 3
wrote .lucin-baseline.json — commit it. Nothing failed, nothing blocked.

Accepting a finding is a decision with a name and a reason on it — and it re-opens automatically if the code underneath it moves.

A baseline is a decision you can revisit. It is not a delete key.

06 FIELD NOTES

Writing on agent security.

All posts →
DESIGN PARTNERS · GUARD

Lucin finds the path before you merge. Guard closes it at runtime.

Lucin is free, MIT and shipping today — it reads your code. Guard is what we're building next: the same information-flow model enforced live, so a path that opens after deploy is blocked instead of logged. It isn't shipped yet.

Static analysis sees the tools you declared. Runtime sees the ones the model actually reached. We're taking a small number of design partners to build Guard against real agents in production. Tell us what you're running; we reply within a day.

Or just start local — nothing here depends on us:
pip install lucin · MIT · nothing leaves your machine