An agent can propose a well-formed tool call that the application should never execute. The question this experiment asks is narrow: if the unwanted call has already been proposed, can an authorization boundary refuse it while preserving legitimate operations?
Try the interactive lab. It runs entirely in your browser with synthetic invoices. No account, model API, or real messages are involved.
The setup
The caller, Alex, belongs to tenant North. Invoice INV-A belongs to North; INV-B belongs to South. Each fixture supplies a predefined tool call, trusted caller context, and—where appropriate—an approval record. Document text is displayed as untrusted context and is never parsed into permissions.
The baseline validates known tools and basic input structure but omits four authorization controls:
- Active identity: reject a caller whose access has been revoked.
- Tool scope: allow invoice reads and reminders, but deny bulk export.
- Tenant ownership: resolve ownership from the stored record, not a tenant label in model arguments.
- Exact approval: require approval to match the actor, tool, invoice, recipient, and message body.
The browser lets you disable these controls independently and inspect the decision, check trace, and simulated effects.
Seven cases
- Read an owned invoice: allow.
- Read another tenant’s invoice, despite a forged tenant argument: deny.
- Invoke a bulk-export tool suggested by an invoice note: deny.
- Turn a read-only request into an unapproved send: deny.
- Send the exact approved reminder: allow.
- Change the recipient after approval: deny.
- Execute a previously planned read after access is revoked: deny.
What the results establish
For engine version 1.0.0, the unchecked baseline matches 2 of 7 expected decisions: it permits both legitimate operations and all five adverse actions. With all four controls enabled, 7 of 7 decisions match: five adverse actions blocked, two legitimate operations allowed. Denied calls have no simulated effects.
These are fixed-fixture results, not an LLM accuracy score or prompt-injection attack-success rate. The experiment does not sample a model. Its cases were chosen to exercise the controls, so passing them establishes those particular behaviors—not coverage of unknown attacks.
Try disabling tenant ownership: the cross-tenant read becomes allowed. Restore it and disable exact approval: unapproved or modified sends become possible. The suite updates immediately so the regression is visible alongside legitimate operations.
A useful composition failure
The export tool is intentionally overbroad. Tenant checks on individual reads do not protect this separate bulk endpoint. In the lab, tool scope blocks it; in production, an export endpoint also needs record-level authorization.
This is the architectural point: a protected read path cannot compensate for an unprotected alternative route to the same data.
Reproduce the experiment
The dependency-free engine and fixtures power both the browser and the 19 executable tests. Download the engine from the lab and save this beside it as reproduce.mjs:
import { runSuite, allControls, noControls } from "./agent-security-engine.mjs"; for (const controls of [noControls, allControls]) { const report = runSuite(controls); console.log(report.correct, report.total); } // Expected: 2 7, then 7 7 Run node reproduce.mjs. You can also export the browser’s current results as JSON, or run npm run test:lab in the repository.
What remains outside the experiment
This is educational code, not production authorization middleware. Trusted identity, current permissions, ownership, and approval must come from a server-side enforcement point. Browser toggles are not a security boundary.
The experiment does not test token validation, approval expiry or replay, concurrent revocation, network egress, idempotent writes, or malicious final answers. Those require additional designs and tests.
For broader guidance, see the OWASP prompt-injection prevention cheat sheet and MCP security best practices.
Open the lab · Original article and full limitations
Which extension would be most useful to inspect next: one-time approvals, revocation races, or a retry ledger?
Disclosure: this write-up and lab were developed with AI assistance. The published results are reproducible deterministic fixture outcomes.