AI agent security
An agent that calls a provider directly, and any media it sends
every questionOn this page
What an agent adds to an ordinary application’s attack surface
An ordinary service calls a fixed set of endpoints that a programmer chose at build time. An agent decides at runtime which tool to call and with what arguments, and it decides that on the basis of text — a system prompt, a conversation, a retrieved document, a database row, a ticket body. Some of that text was written by somebody who wanted the agent to do something else. That is the whole of the difference, and every control that follows is a consequence of it.
Three consequences matter enough to design around. The first is that authority and behaviour come apart: the set of things an agent may do is now much larger than the set of things it was built to do, because a persuaded model will attempt anything in its grant list. The second is that the boundary between data and instruction is not maintained anywhere in the stack — not by the model, not by the protocol, not by the transport — so anything the model reads is a potential instruction. The third is that some of what an agent does cannot be undone: a refund is issued, an email is sent, a record is deleted, and no amount of afterwards fixes it.
So the security question is not whether the model can be tricked. It can, an attacker gets unlimited attempts, and the research position is that this is unsolved. The question is what an agent that has been tricked can reach, how quickly you would know, and whether the record of what happened was produced by the component that was tricked.
That is why the sections below are ordered the way they are. The credential and the authority come first because they bound the blast radius regardless of what the model was persuaded of. Input handling comes third rather than first, because it is the layer that degrades as attackers iterate. And the evidence comes last because it is the layer people build last and need most.
The credential is the agent, so treat it that way
Holding an agent’s token is being that agent — there is no second factor and no session a human can be asked to re-establish. The handling that follows from that is unremarkable and worth checking anyway. Token Observe stores the SHA-256 of the token and a 16-character display prefix, returns the only copy of the plaintext in the create response, and never logs, echoes or audits the secret. Minting is admin rank because it hands out gateway authority for an agent; creating and editing agents is operator rank; reading is viewer rank. Revocation is one write and takes effect on the next authentication attempt.
Two details are worth copying if you are building this. Look the token up by digest and then re-compare in constant time, so a storage layer that ever answered a prefix match cannot be turned into a byte-at-a-time oracle. And return one identical message for unknown, revoked and expired keys, while logging them as three different events, because telling somebody their key merely expired confirms it was once valid.
The stronger arrangement, where your platform can support it, is not a long-lived token at all. Token Observe accepts a workload assertion from an OIDC issuer you configure, signed over a composite identity — the workload subject, the agent id, the human actor and the task id — checks the two bindings it owns rather than trusting the assertion wholesale, and exchanges it for a capability that is short-lived, audience-bound and single-use, with the assertion itself capped at fifteen minutes. Those capabilities are HMAC tokens for that one deployment rather than portable OAuth credentials, which is a deliberate narrowing: a token that is only meaningful to one gateway is a token that is worth less to whoever steals it.
Whatever the scheme, record the rejections. Every authentication failure at the door is folded into an hourly roll-up that discovery reads, carrying the reason and, where the credential resolved to a real key, the key and agent id — never the presented token and never a digest of it, because a digest of a live secret is an offline oracle against that secret. That roll-up is what turns somebody is holding a credential we do not recognise into a finding rather than a log line nobody greps for.
Input handling, and why it is third rather than first
Normalise before anything reads the text. The Unicode tag block encodes a complete invisible ASCII alphabet, so a scanner running over raw input is examining a different document from the one the model will read. Token Observe strips the tag block, zero-width characters, bidirectional overrides and isolates, the soft hyphen, invisible mathematical operators and private-use planes, looping to a fixpoint because stripping one layer can reveal another, and deliberately keeps the zero-width joiner so emoji sequences survive.
Then scan, and score each fragment under its own source. A directive inside a tool result is more suspicious than the same words typed by a person, because a tool result is data rather than a principal: Token Observe multiplies a tool-result finding by 1.25 and takes the highest score across fragments rather than the average, so one hostile paragraph inside a large legitimate payload is not diluted.
Say the limit in the same breath as the capability, because this is the layer that invites overclaiming. The injection scanner is nine weighted patterns, and the personal-data detector is regular expressions plus checksums — Luhn for a card, mod-97 for an IBAN, mod-11 for an NHS number — with a set of secret shapes such as JWTs, AWS access keys, prefixed vendor keys and PEM headers. A phrasing nobody wrote a pattern for scores zero. Free-text personal data is not detected at all, and neither are identifier formats outside the shipped UK and US set. Anything scanned inline on attacker-controlled text also needs bounds: Token Observe caps a scan at 65,536 characters, which means text beyond that is not scanned, and structured payloads are walked under a hard depth of 32 and a node limit of 5,000 with exceeding a bound refusing rather than forwarding an uninspected tail.
The surfaces to enumerate are wider than most threat models list. Tool names, descriptions and input schemas are instruction surface, because they are fed to the model to help it choose; an upstream server that rewrites a description steers an agent without a line of your code changing, which is why Token Observe hashes each descriptor at approval and quarantines it on drift. Any passthrough field a gateway forwards without reading is an injection channel shaped exactly like the fields it does not recognise. And media is a channel Token Observe does not read at all: image, audio, PDF and opaque file inputs are rejected before egress on every model dialect, and image, audio, blob and resource blocks are withheld from tool results, because there is no bounded decoding or optical character recognition behind the detectors. A picture of a prompt is not inspected, so it is refused.
Egress and effect: the two places where a mistake becomes permanent
Redaction runs inline, before the request leaves, and in both directions — so a rule fires on an identifier the model produced even though nobody sent one. Secret kinds are masked irreversibly whatever a policy’s mode says, because a reversible placeholder for a credential is a credential. On a stream the response-side plan has to be resolved before the first byte, since a stream has no later enforcement point and bytes already written cannot be recalled, and the hold-back buffer has to be deep enough that a value split across two chunks cannot escape half-masked.
Read that as a compensating control rather than as data-loss prevention, and say so to whoever is relying on it. It also cannot reach what the calling application does with the text it receives: masking on the way back does not stop an application rendering model output into an HTML page or passing it to a shell, and a governance product that claims to close that is describing a control it does not have.
For the small set of actions that move money, delete data or contact a customer, the control that actually holds is a human bound to the exact payload. An approval that authorises a refund rather than this refund of this amount on this order is a standing licence for every refund the agent proposes afterwards, and the agent proposing them is the component most likely to have been talked into it. Token Observe binds an approval to a hash of the canonical action plus its stable execution context — the subject and team, the effective role grants, the ordered delegation identities and their grants, the named human, the caller’s session and tags — and consumes it atomically, so a changed argument or a replay is refused. Request and trace ids and transport session ids are deliberately excluded, so a reconnect does not invalidate a reviewed action.
Where the action is irreversible and the tool call may be lost rather than merely refused, an approval is not enough on its own. The construct that closes it is a bounded contract around the call: a declared expectation of what committing looks like, verified from evidence afterwards, with a defined compensation path and a named human adjudication when verification cannot settle it. Token Observe’s effect contracts are deliberately not an expression language — they select JSON values, compare them with six bounded operators and copy them into pinned tool arguments, and cannot run code or interpolate a template — because a control that could execute arbitrary logic on attacker-influenced input would be a second injection surface inside the thing doing the defending.
Evidence, and the failure mode to design against
A record produced by the agent is a description written by the party under examination. The record that counts is produced by the enforcement point, and it has to open before the decision rather than after it: Token Observe mints the trace identifier at step 3 of an eleven-step request path, before sanitisation, before the scanners and before the policy verdict, so a request refused a millisecond later is recorded rather than missing, and the identifier comes back on a response header even on a refusal. The interesting rows in a governance review are the refusals — which agent proposed which tool it held no grant on, and when.
Administrative changes need a stronger record than requests do, because the person you are collecting evidence about may be the person with database access. Token Observe hash-chains its audit log so that any edit or deletion breaks verification at a named sequence number, and states plainly what that is worth: under the default unkeyed configuration the digests are plain SHA-256, so an operator who can write the database can rewrite an entry, recompute every downstream hash, and have verification report valid. That is tamper-evidence against alteration that does not recompute the chain, which is genuinely useful and is not tamper-proofing. Keying the digests moves the requirement to possession of a key held outside the database; an off-box Ed25519 anchor lets an outside party check without being handed the ability to forge. Both guarantees are forward-looking, which is the argument for switching them on while the history is short.
Halting deserves its own note because it is the control people assume is stronger than it is. A kill switch scoped to one agent, one team or the whole estate, checked before anything else in the pipeline, engaged by a named person with a stated reason, is admission control: it refuses new work rather than recalling a request already dispatched to a provider or an effect already committed downstream. State that in the runbook rather than discovering it during the incident.
The last thing to say is the boundary. A control in the request path secures the traffic that arrives at it. It evidences nothing about training-data provenance, model cards or bias testing, it never sees hidden model reasoning, and it says nothing at all about an agent using a personal API key against a vendor endpoint. That last one is a discovery problem, and it is the denominator for every coverage claim the rest of this makes.
How to put AI agent security into practice
- 01
Inventory the agent against a named human
One record with an owner, a team and a declared purpose, and make it the record the enforcement point resolves on every call rather than a list maintained beside the runtime. - 02
Issue a credential you can revoke in one write
Store the digest rather than the token, show the plaintext once, look it up by digest and re-compare in constant time, and return one identical message for unknown, revoked and expired keys. - 03
Write the grants as an allowlist of actions
Name the specific tools and models the declared purpose requires, deny everything else by default, and check that delegation between agents intersects rather than accumulates. - 04
Normalise, then scan, then assume the scan missed
Strip invisible characters to a fixpoint before anything reads the text, score each fragment under its own source, and record which heuristics fired so a near-miss is visible later. - 05
Set ceilings that refuse rather than report
Per request, hourly, daily and monthly spend plus per-minute request, tool-call and token limits, decided before egress, with an unpriced route failing closed rather than being estimated at zero. - 06
Gate the irreversible actions on a payload-bound approval
Bind the human decision to a hash of the exact action and its execution context, make it single-use and give it an expiry, and keep the gated set small enough that the queue is actually read. - 07
Switch on evidence integrity early
Key the audit digests from a secret manager your database administrators cannot read, start anchoring off the box, and record the chain head somewhere outside the database. Both guarantees only cover what comes after them. - 08
Measure what you are not covering
Stand up discovery alongside all of the above and read its coverage before its findings, because an estate where an unknown number of agents never route through the gateway is not a governed estate.
Where this argument meets an implementation
Agent permissions
Deny by default, explicit deny wins, and delegation intersects — so an agent cannot borrow authority it was never granted.
Policy engine
One deterministic verdict on every governed request: allow, block, redact, or park it for a human.
Audit chain
Every administrative act hash-chained; seal it under a key held off the box, and anchor it with a signature your auditor can check alone.
Agent registry
One record per agent, and it is the record the gateway enforces against.
Those pages are one product's implementation of what this guide argues for; describe what your agents actually do and you will get a straight answer about whether you need any of it, including when the answer is no.
Talk it throughIs securing an agent different from securing a microservice?
The transport-layer work is the same and should be reused: TLS, network segmentation, secret management, host hardening. What differs is that a microservice calls a fixed set of endpoints chosen by a programmer, while an agent chooses its calls at runtime from text that may have been written by an attacker. That makes the grant list rather than the code the real specification of what the process can do, makes tool results an instruction channel, and makes some calls irreversible in a way an idempotent API retry is not. Everything in this guide follows from those three, and nothing in it replaces the ordinary infrastructure work.
Where should a team start if it can only do one thing this quarter?
Cut the grants. It is unglamorous, it needs no new product, and it is the only layer that does not degrade as attackers get better at phrasing. Review each agent’s tool and model grants against its declared purpose, remove anything the purpose does not require, and check that an agent cannot obtain through a delegation chain what it was refused directly. An injected instruction telling a support agent to issue a refund is inert if that agent holds no grant on the refund tool, and the attempt becomes a recorded finding rather than a transaction.
How much should we rely on prompt-injection detection?
As a signal, not as a boundary. Token Observe scores injection with nine weighted patterns over sanitised text, weights tool-result findings 1.25 times higher, and caps a scan at 65,536 characters — which means a phrasing nobody wrote a pattern for scores zero, and a payload hiding its directive past the cap is not scanned at all. An attacker iterating against a fixed pattern set will find both. Stage injection rules in observation mode, scope them to tool-result findings so the people using your product are not caught by a control aimed at the data they submit, and spend the effort you save on the grant list and the approval gates.
What does the audit chain actually protect against?
Alteration that does not also recompute every downstream digest, located to an exact sequence number. Under the default unkeyed configuration that is the honest boundary: an operator with write access to the database can rewrite an entry, recompute the chain and have verification report valid, and Token Observe has a test that does exactly this on a default install so the limit is a tested fact rather than a caveat. Keying the digests under a key held outside the database moves the requirement from write access to key possession; an off-box signed anchor lets an auditor check without holding a key that could forge. None of the three is tamper-proof, and the claim worth making is narrow: any copy of an anchor you kept off the box beats any rewrite made after you took it.
Can a gateway secure a coding assistant on a developer’s laptop?
Partly, and the boundary should be stated before anyone assumes otherwise. Pinning the client through a managed-settings channel the developer cannot remove, so that exactly one tool server is allowed, means every tool call the editor makes lands on a governed endpoint and is decided by the same evaluator against the same rules. What that does not cover is the client’s built-in tools, such as shell commands and file edits, and its model traffic. Token Observe also governs subscription seats through each vendor’s own administrator hook, and publishes that surface as preview: it ships as an unsigned Node command rather than a signed native binary, freshness is bounded by the device’s own clock, and the local spool is unsigned, so on an unmanaged device the governed party can move the clock back.
Prefer to ask a person? Write to us →
Bring us the question this guide did not answer.
Write to hello@tenhaw.com with what your agents do, which providers they call and what would have to be true for you to put something in front of them. James Rooney replies. You will get a straight answer about whether Token Observe fits, including when it does not.
no form · no qualification step · no sales desk · the other three ways in