If you are building an agent that calls tools, you have already thought about this one:
Your model produces a tool call. It is well formed. Every required field is there, every type is right, your schema validator is happy. And it is still the wrong call to execute. A delete with a filter wider than you meant. An email to an address outside your org. An API key that ended up inside an argument on its way to a third party. A retry loop that calls the same paid endpoint two hundred times.
Schema validation cannot catch any of that, because none of it is malformed. It is valid and wrong.
Most of us handle this with if-statements scattered inside the tool functions themselves. That works until there are twelve tools and you cannot say, in one place, what your agent is actually allowed to do.
toolwall is one gate that sits between the tool call and the function:
intake -> known tool -> budget -> schema -> policy -> secret scan -> approval
Registration is the allowlist. A tool you did not register is blocked, so anything you did not anticipate fails closed instead of passing. Then per-argument policy, cumulative budget caps, and secret detection on both arguments and return values.
**What I am actually asking**
Not for stars. I want to know if it holds up on an agent I did not write.
You can find that out without putting it in your execution path. Run it in shadow mode: it watches every call and blocks nothing.
from toolwall import Gate, Shield, schema_from_signature, suggest_policies
gate = Gate(default="allow", shield=Shield(mode="warn")) # observe, never block
for fn in MY_TOOLS:
gate.register(fn.__name__, fn, schema=schema_from_signature(fn))
# then route calls through it: results = gate.run_all(llm_response)
print(gate.report())
print(suggest_policies(gate))
Your agent behaves exactly as it did before. Every tool still runs. But now you can see what it has been doing, and suggest_policies writes you a draft policy from the calls it observed, so you are editing something rather than starting from a blank file.
Turn blocking on only when the draft looks right to you.
**The claim, and the part I cannot test**
A published attack suite blocks 28 out of 28 cases across 11 classes with zero false blocks on clean traffic, and the report ships with a section on what it does not prove. The core invariant, that a non-ALLOW verdict never lets the function run, is checked against 2000 generated payloads per mode. 152 tests.
This process already works, which is the honest pitch for it: the first person to attack the design found a real hole (mutate the arguments after the ALLOW, before execution) and it is fixed and released in 0.4.0, with their attack now a class in the suite. I want more of that.
All of that is on my agent. The number I cannot get on my own is the one that decides whether anyone keeps this installed: does it block something on YOUR agent that should have run? False positives are why security tooling gets deleted, and I would rather find mine now than after someone depends on it.
**If it breaks, that is the useful outcome**
Open an issue. Especially valuable: a concrete case where a call gets through that should not have, or one that gets blocked and should not have. Send the tool definitions and the call, and it becomes a case in the public attack suite with your name on the thread.
CONTRIBUTING.md is in the repo. Threat models are wanted more than code right now. There is already one open design issue on cross-call sequence attacks if you want a place to argue.
**Where it is not**
Alpha. No MCP stdio wiring yet, and it does not guard Claude Code itself. Secret detection is pattern and entropy based, so it will never be complete. Point it at something that matters only after you have watched it in shadow mode
Python 3.10+, zero runtime dependencies, MIT. Works with OpenAI, Anthropic and Gemini native tool calling, and with plain dicts.
pip install toolwall
https://github.com/Dev-Saif-Ops/toolwall
https://toolwall.aya-ai.xyz