The confused deputy,
from EL3 to AI agents
For the past year most of my work has been in Trusted Firmware-A, the reference implementation of the secure-world firmware that Arm processors boot from. Specifically in EL3, the highest privilege level on the chip, where the Secure Monitor runs.
The interface I spend my time on is the SMC call. When an operating system needs something only firmware can do, it issues a Secure Monitor Call: a request to change a power state, move memory between security domains, or talk to a secure peripheral. Execution traps into EL3, a handler runs, and control returns. The arguments come in registers, and they were written by the caller.
That last part is the whole problem.
A very old bug
In 1988 Norm Hardy described a compiler that could write to a system billing file. Users could ask it to write output anywhere they had permission to write. Someone asked it to write output to the billing file. The compiler had the authority, the user supplied the target, and the compiler did not think to ask whether the user should have been able to reach it.
Hardy called it the confused deputy. A privileged program, tricked into misusing its authority on behalf of something less privileged.
At EL3 it looks like this. A handler receives a pointer and a length from the normal world. It uses them. EL3 can write to secure memory, so if the handler never checks that the range actually belongs to the normal world, an attacker in the OS supplies an address inside secure RAM and the firmware writes there on their behalf. The attacker never gained privilege. The firmware spent its privilege for them.
I ended up calling this privilege-blind forwarding, because the defining feature is that the forwarding step is blind to where the address came from.
Making the safe path the default
The fix for any individual instance is easy: check the range. The problem is that "check the range" gets reimplemented per handler, per platform, per vendor, and the failure modes are subtle. Base and size are validated separately, so base + size wraps around and passes. Or the argument is read from normal-world memory more than once, so what you validated isn't what you used.
So rather than fix instances, I wrote a validation framework for TF-A and upstreamed it. It copies arguments out of the caller's registers exactly once, validates base and size together as a tuple with overflow checking, and delegates the actual range decision to a platform hook that knows the memory map. Five patches, merged, and the pattern is documented in the runtime service writer's guide for people implementing new handlers.
It did not make the bug class disappear. Over the same period I found it in shipping vendor firmware repeatedly, across multiple silicon vendors. Five CVEs this year, with more findings still going through disclosure. A framework only helps handlers that use it.
Then I saw it somewhere else
Last month I read a post from Arcjet about runtime security for AI agents. It put the central problem this way: an agent may possess legitimate delegated authority and still be manipulated into misusing it.
That is the confused deputy. Not an analogy. The same structure, moved up the stack.
An agent holds real credentials: OAuth tokens, API keys, database access, permission to spend. Its instructions arrive from a channel that isn't fully trusted: user input, retrieved documents, a web page, another agent's output. When that channel carries something adversarial, the agent uses its authority for a purpose its principal never intended. The attacker never obtained the credentials. The agent spent them on their behalf.
Swap the nouns and it's an SMC handler.
The industry is reaching for the same tool
The response has taken exactly the shape firmware took. Validate at the boundary.
Keycard issues short-lived, task-scoped tokens instead of long-lived API keys, so an agent that goes wrong can only go wrong within a narrow scope. Arcjet enforces at the point of action, checking for prompt injection and restricting which tools may be called. Model providers ship approval gates and classifiers that escalate risky actions to a human. Every one of these is a boundary check. It is the same instinct that produced my framework, and it is right.
It is also, on its own, insufficient. Firmware is where you find out why.
What a year at EL3 teaches you about boundary checks
Consider a handler that does everything right. It copies the arguments once. It validates base and size as a tuple. It confirms the range lies in non-secure memory. Every check passes, correctly.
Then, between the moment of the check and the moment of use, another CPU core changes the memory mapping underneath it.
Nothing about the check was wrong. The check was a claim about the state of the system at one instant, and the system did not hold still. This is a time-of-check-to-time-of-use bug, and it is not a mistake in the sense that a missing bounds check is a mistake. It is a structural property of validating at a boundary: you are asserting something about a world that continues to move after you stop looking.
Agents have the same property, with worse odds. A permission granted at the start of a task is a claim about intent at that instant. The task then runs for minutes or hours, pulling in content nobody reviewed, chaining tool calls nobody enumerated in advance. The scope was correct when it was issued. That is not the same as it being correct when it was used.
You can shrink the window. You cannot close it.
Everything proves a precondition
Look at what the agent stack can currently establish.
Identity proves which agent is running. Authorization proves it was permitted to act. Platform attestation (the RATS architecture and Entity Attestation Tokens, the same primitives I work with at the firmware layer) proves the machine booted into a known-good state.
Every one of those is a statement made before the work happens. They are preconditions. Not one of them leaves a record that anyone outside the operator can independently verify.
Probabilistic systems holding real credentials will eventually do something wrong. When they do, the only account of what the agent did is a log written by the party with the most interest in its contents. In a dispute, that log is exactly the evidence a regulator or auditor has reason not to trust.
Prevention has a ceiling. Evidence doesn't.
Every preventive control buys safety by stopping the agent doing something. That's a real cost, paid in utility, and it's why safety and capability feel like a trade-off: each new guardrail makes the system a little less useful, and you can never add enough of them to reach certainty.
Proof doesn't work that way. Recording what happened has a cost: hashing the stream, signing it, the overhead of running inside an enclave. But it's a cost in cycles, not in autonomy. Nothing is disallowed. The agent runs at full capability, and the record is simply unforgeable afterwards. A system whose actions can be proven can be permitted to do more, not less, because the consequence of being wrong is bounded by accountability rather than by restriction.
This is the argument for making execution verifiable at the hardware layer rather than trusting it at the application layer, for the same reason we root firmware measurements in silicon instead of asking the firmware to vouch for itself.
It's what I've spent this year building, in a spec called APEX and a runtime called VAR. But the through-line matters more than the product: I didn't move from firmware to AI. I followed one bug up the stack, and found the industry at the top of it reaching for the tool I already knew the limits of.