Zero-trust tool control for AI agents
Agents read documents, tickets and web pages, and then act on them. Anything they read can carry an instruction. Promtact sits between the agent and the tools it can reach and decides, per call, whether the call happens - allow, hold for a person, or refuse - then writes that decision to a record your own administrators cannot quietly change.
The agent never holds your credentials. API keys and tokens stay with the gateway and are attached only after a call has been allowed, so an agent that is talked into something has nothing to leak and nothing to reuse. A refused call never causes a secret to be read at all.
An agent that has read a poisoned document will do what the document says. The decision happens before the tool runs: the message is not sent, the record is not deleted, the payment is not made. A log that tells you afterwards is an incident report, not a control.
Every decision is hash-linked to the one before it, and the head of that chain is countersigned by a witness outside the machine. Someone with root on the server cannot remove a decision without the gap being visible. That is the difference between a log and evidence.
It runs where your agents run. There is no vendor endpoint, no telemetry, no account. A gateway that inspects tool calls sees documents, arguments and credentials - sending that to a supplier would build the exfiltration path it exists to prevent.
It speaks the Model Context Protocol, so it goes in front of the tool servers you already run and the agent keeps calling them the way it always did. Adoption is a configuration change, which matters more than it sounds: a control that requires rebuilding the thing it protects does not get deployed.
The EU AI Act requires high-risk systems to keep automatic records over their lifetime (Article 12) and to remain under meaningful human oversight (Article 14). DORA requires demonstrable ICT risk controls over third-party components, and an agent calling tools through an MCP server is one. Both of those describe a record of what was decided and a point at which a person decides.
That is what this produces: the record, and the decision point. Whether a given deployment satisfies a given obligation is a question for your counsel and your auditor, and this page does not answer it.
A tool call is evaluated against policy and either runs, waits for a person, or is refused. Detection that reports what an agent already did is a log, not a control.
It gates what passes through it. An agent with another route to the same data has a route this does not see.The answer is examined before the agent reads it: instructions smuggled in characters that render as nothing, image URLs carrying data outward, credential shapes. A poisoned response is withheld.
Prompt injection cannot be reliably detected, and this does not claim to. Detection is the smaller half.Content returned by a tool is untrusted by origin, whatever it contains. A session that read it has its next outward action held for a person - with no dependence on anything having been detected.
The mark expires, and only outward actions are affected. A control that holds everything is one somebody switches off.Every decision is hash-linked to the one before it, and the chain head is published to a witness outside the host, which refuses a chain that got shorter or was rewritten.
Without that witness the chain detects accidental corruption only: anyone able to write to the database can recompute every hash.$ promtactl audit trail --last 4
02:37:05 #270 allowed
tool list_documents
because tool "list_documents" matched the approved manifest
02:37:05 #271 allowed
tool read_document
02:37:05 #272 WITHHELD - a tool's answer was kept from the agent
tool read_document
because tool result contains characters that are invisible to a
reader but not to a model
02:37:05 #273 HELD - waiting for a person
tool send_message
because action reaches outward after this session read untrusted
content, so it needs a person
$ promtactl audit verify
Records 267
Hash-linked 267
Chain intact - every record still hashes to the one before it
Witness agrees at record 267
The witness refuses a chain that has been shortened or
rewritten, so an operator cannot quietly edit this record
even with full access to this server and its database.
This is output from a running deployment, not a mock-up. The same information is in the console, and the same commands are in the repository.
The demonstration above uses tool servers from this repository, which proves the mechanism and not the integration. This is the other half: the official Model Context Protocol reference server, unmodified, with the gateway in front of it and a policy approving exactly one of its twelve tools.
$ curl ... -d '{"method":"tools/list"}'
['echo', 'get-annotated-message', 'get-env', 'get-resource-links',
'get-resource-reference', 'get-structured-content', 'get-sum',
'get-tiny-image', 'gzip-file-as-resource', 'toggle-simulated-logging',
'toggle-subscriber-updates', 'trigger-long-running-operation']
$ curl ... -d '{"method":"tools/call","params":{"name":"echo", ...}}'
{"result":{"content":[{"type":"text","text":"Echo: through the gateway"}]}}
$ curl ... -d '{"method":"tools/call","params":{"name":"get-env"}}'
{"error":{"code":403,"message":"blocked by policy",
"reason":"tool \"get-env\" is not on the approved manifest"}}
The tool refused there is get-env, which returns
the process environment. On a real host that is where database passwords and
API keys live. The upstream offers it, the agent may ask for it, and the answer
is no - with the refusal in the audit record and the agent never learning what
it would have contained.
Upstream credentials stay with the gateway and are attached only after a call has been allowed. A refused call never causes a secret to be read at all, so a denied call cannot be used to find out which credentials exist. The audit record carries a fingerprint, never the value.
A tool that returns a credential in its own response is a different problem, and inspection is the smaller half of it.The witness signs what it saw with a P-256 key, and receipts are checked offline against the public half by whoever holds it. Run the witness yourself and the vendor is no longer part of the trust argument. The scheme is identical either way; no code changes.
A receipt proves a third party saw this chain head at this time. It does not prove the contents of any record.Nothing leaves the deployment. There is no vendor endpoint, no telemetry home, no account to create. A gateway that inspects tool calls sees document contents, arguments and credential material - shipping that to a vendor builds the exfiltration path it exists to prevent.
Which also means there is no vendor dashboard, and support cannot see what happened unless you show it.Policy, identities and isolation per tenant, so one deployment serves many customers without their decisions or records meeting. Provisioning is an API, and identities can come from SCIM.
Physical separation per tenant is a deployment choice rather than the default, and the default is logical.Evidence is not retroactive. The record of what an agent was allowed to do is written when the call happens, or it does not exist. Deploy this in six months and the six months before it have no record - not a thin one, none. When somebody asks what the agent did in March, the honest answer is that nobody knows, and that answer is the same whether or not anything went wrong.
The exposure grows with every tool you connect. An agent with read access is a question of confidentiality. An agent that can send mail, close tickets, call an API or move money is a question of consequences, and the second kind is being connected right now, in most organisations, by teams who are being asked to move quickly.
The obligations are already written. The EU AI Act requires automatic records over a system's lifetime and meaningful human oversight; DORA requires demonstrable controls over third-party ICT components. Neither waits for the agent programme to mature. What they ask for is a record and a decision point, and both have to exist before the auditor asks, not after.
None of that is an argument for buying this specific thing today. It is an argument for having the record start today, from whoever you choose. A control added later cannot describe what already happened.
The useful first conversation is thirty minutes and technical: what your agents can reach today, which of those calls would be held, and what the record would look like afterwards. Bring an engineer.
What comes out of it is a written answer to three questions - which of your current tool calls would be allowed, held or refused; what an auditor would see six months later; and what it would take to run it in front of your own stack. That is useful whether or not you buy anything, and it is yours either way.
Independent assurance, stated plainly: there has been no third-party penetration test and no SOC 2 or ISO 27001 audit. This is version 0.5 of a product built by a small team, and pretending otherwise would be the first thing your due diligence caught. What exists instead is reproducible - the scans, the commands and the findings are published so you can run them yourself rather than take a report on faith. An independent test is the natural next step and something we would scope together with a first partner.
Each claim in technical-claims.md carries the command that checks it and states its own limits. Where a control does not do something, that is written next to what it does.
The security scans are published as instructions rather than as results. verify-it-yourself.md gives four tools with pinned versions, the commands, and the numbers to expect - including why some findings are excluded rather than fixed. You get your own number, which is the only kind worth having.
security-findings.md lists what has been found and the commit that fixed it, including a defect a human read past during the same review that the tool then caught. A page reporting nothing would mean either perfect software or a search that stopped early.
The source is public. There is no exploit logic in it and nothing that attacks a target.