The execution gap — Actenon
Research noteJuly 2026Apache 2.0
Your agent can already do things nobody authorised.
We scanned 25 AI agent frameworks — 23,476 files — looking for one specific thing: a consequential action that a model can reach, with a parameter the model controls, and no authorisation check on the path. We found 30. We also found 12 false positives in our own scanner, and fixed them before publishing this.
~/your-agent
$ pipx run actenon-scan scan .
Your agent can take 7 consequential actions without an authorisation check.
DATA LOSS 3 purge_workspace, drop_table, delete_prefix<br>EXECUTION 2 run_migration, deploy<br>EGRESS 2 notify_customers, post_webhook
Most exposed: app/tools.py:47 purge_workspace()<br>Reachable by: @mcp.tool()<br>Guarded by: nothing<br>Model controls: path
7 findings in 340 files (0.4s)
25 repos, pinned by SHA
23,476 files analysed
30 findings, hand-triaged
0 in control repos
The gap
Validation is not authorisation.
Most agent tools check that a request is well-formed. Far fewer check that this caller is permitted to perform this exact action, right now — at the point where the side effect actually happens.
That distinction is invisible in a code review, because both look like a guard. It is the difference between a tool that refuses an unauthorised refund and one that processes it correctly.
The scanner looks for exactly one shape: a model-controlled parameter reaching a recognised consequential sink, with no check that dominates every path to it.
TODAY
model decision
validate args
NOTHING HERE<br>no authority decision
side effect
WITH A BOUNDARY
model decision
verify authority<br>actor · target · scope · expiry
ALLOW · or refusal
side effect
The scanner finds the left-hand shape. It does not claim the action is exploitable — only that nothing on the path establishes authority.
The study25 repositories, immutable SHAs
Where the findings actually were.
Every repository is pinned by commit hash so the result is reproducible. Five non-agent control repositories were included deliberately: any finding in those is a precision failure by definition. There were none.
RepositoryClassFindings
crewAIInc/crewAIframework12<br>TransformerOptimus/SuperAGIapplication6<br>FoundationAgents/MetaGPTapplication5<br>microsoft/semantic-kernelframework3<br>agno-agi/agnoframework2<br>modelcontextprotocol/serversMCP server1<br>modelcontextprotocol/python-sdkMCP server1<br>requests · flask · fastapi · click · richcontrol0
What the action wasCount
Network egress with model-controlled destination15<br>File write to a model-controlled path7<br>Shell execution3<br>Message send to a model-controlled recipient2<br>File deletion2<br>SQL execution with a model-controlled statement1
What we got wrongand found ourselves
The first run of this study was 81% precise.
Twelve of sixty-three findings were wrong. We publish that because a scanner claiming perfect precision has either not been measured or is not being honest, and because the failure classes are more useful to you than the successes.
51/63<br>→<br>30/30<br>initial measurement → after three fixes, every finding hand-triaged
A Kubernetes rule that matched a search client<br>The pattern client.*.create was loose enough to match Elasticsearch and vector-store clients. It fired at HIGH severity in two well-known repositories. Constrained to genuine Kubernetes surfaces.
A SQL rule that caught the safe case and missed the dangerous one<br>It matched the literal string execute("DROP TABLE …") but not execute(query) — where the model controls the statement. It was detecting hardcoded SQL and ignoring caller-controlled SQL. Now matches the sink, not the text.
A bulk-deletion call that was never in the vocabulary<br>s3.delete_objects — deleting many objects at once — simply was not a recognised sink. Added, with the exact source line kept as a regression test.
Each fix carries a permanent regression fixture, and CI now fails if any finding in the corpus is untriaged or triaged as a false positive. The 30/30 is enforced, not asserted.
Limits
What this cannot see.
Static analysis of guard soundness is undecidable in the general case. Rather than claim coverage we cannot demonstrate, here is what is verified against real code and what is not.
Agent architectureStatus
MCP @mcp.tool()COVERED<br>LangChain @tool / BaseToolCOVERED<br>OpenAI @function_toolPARTIAL<br>Custom agent loopsNOT COVERED<br>Action/observation dispatchersNOT COVERED
A finding is not a vulnerability<br>The scanner establishes that a model-controlled parameter reaches a consequential action with no dominating authority check in the analysed path. It does not establish that the agent is externally reachable, that no guard exists elsewhere in the system, or that the action is exploitable.
Custom agent loops were tried and rejected<br>Detecting agents that run whatever the model returns, with no framework decorator, produced 10 false positives out of 10 candidates. The signal decomposes into “this module talks to an LLM” and...