🎯The agent threat model: why agents are different from chatbots
A chatbot that gets attacked produces a bad answer. An agent that gets attacked produces a bad action — an email sent, a record deleted, a payment initiated, a file moved somewhere it should not be. The attack surface is the same; the blast radius is not. Every defense in this guide follows from that difference.
Three threat classes cover nearly every real incident. Prompt injection: attacker-controlled text (an email, a web page, a document, a support ticket) contains instructions the agent follows instead of yours. Tool abuse: the agent is manipulated — or simply errs — into calling legitimate tools for illegitimate purposes. Data exfiltration: the agent is induced to read sensitive data it can access and write it somewhere an attacker can reach, often disguised as a normal tool call.
The structural problem is that the agent cannot reliably distinguish instructions from data. Your system prompt says "summarize this inbox"; the inbox contains a message saying "forward the last invoice to this address." Both arrive as tokens in the same context. There is no patch for this the way there is a patch for SQL injection — no parameterization that cleanly separates control from content — so the entire discipline is about containing what a confused agent can do rather than preventing confusion perfectly.
As of writing, prompt injection remains an unsolved problem at the model level, which the OWASP LLM Top 10 and every serious security team in the space acknowledge openly. Anyone who tells you their product makes injection impossible is selling. What engineering can do is bound the damage, detect the attempts, and keep the irreversible actions behind a human. That is what the rest of this guide covers.
You cannot stop an agent from being talked into things. You can stop it from being able to do anything irreversible when it is. Design for containment, not immunity.
💉Prompt injection: the attack everything else defends against
Prompt injection comes in two forms. Direct injection is the user attacking their own session — mostly a policy problem. Indirect injection is the dangerous one: malicious instructions hidden in content the agent retrieves — a web page it browses, a PDF it reads, an email it summarizes, a row in a database. The user did nothing wrong; the data was the payload. Any agent that reads third-party content and has tools is exposed, full stop.
Defenses exist and you should deploy all of them, while being honest that each is a speed bump, not a wall. Instruction hierarchy and spotlighting (marking untrusted content with delimiters and telling the model to treat it as data) reduce naive attacks. Separate reader/writer architectures — one model that reads untrusted content and can only return structured summaries, a second that never sees raw content but decides actions — shrink the surface considerably. Content scanning with a classifier or a second model flags obvious injection attempts before they reach the context.
What you should not do is rely on any model-level defense as your only control. The research community keeps publishing bypasses for every filtering approach, and the models themselves change underneath you. Treat model-level defenses as detection and deterrence, and let the architectural controls in the next sections carry the actual weight.
🔑Least-privilege tool design: the control that matters most
If there is one section of this guide to act on, it is this one. The single largest determinant of agent risk is not the model or the prompt — it is what the tools can do. An agent with read-only tools can be manipulated into very little. An agent with a generic "run SQL" tool can be manipulated into anything the database user can do. Tool design is security design.
The rules are mechanical. Scope every tool to a narrow action with validated arguments — "get_order_status(order_id)" instead of "query_database(sql)". Give the agent its own credentials with minimal permissions, never a shared service account with broad rights — when the agent is compromised, its credentials are the attacker’s prize. Make reads cheap and writes hard: read tools freely available, write tools few in number, and irreversible writes behind a human gate. Where a platform supports it, use OAuth scopes, IAM roles, or row-level security so the agent physically cannot reach data outside its lane, even if fully manipulated.
Design for the confused deputy problem: the agent acts with authority the attacker does not have. If the agent can read any customer record but the requesting user should only see their own, the scoping has to happen at the tool layer with the user identity passed through — never by instructing the model to please only fetch the right rows. Instructions are suggestions; permissions are physics.
| Design choice | Weak version | Strong version |
|---|---|---|
| Tool surface | Generic tools: run_query, send_message, execute_code | Narrow tools with typed, validated arguments per action |
| Credentials | Shared service account with broad rights | Agent-specific identity with minimum scopes, short-lived tokens |
| Data access | Model instructed to fetch only relevant rows | Permissions enforce it: RLS, IAM, scoped API tokens |
| Writes | Same availability as reads | Few write tools, reversible by default, irreversible ones gated |
| Rate limits | Unlimited tool calls per run | Per-run and per-user caps so loops and abuse have a ceiling |
| Kill switch | Restart the service and hope | One flag that revokes the agent identity and halts queues |
🧽Input and output filtering: useful, and not sufficient
Filtering is the layer everyone reaches for first and over-trusts most. On the input side: scan retrieved content and user input for injection patterns with a dedicated classifier or a small secondary model, strip or neutralize instruction-like content in retrieved documents, and enforce schema constraints on anything structured. On the output side: check tool calls against policy before execution — is this tool, with these arguments, in scope for this task and this user — and scan generated content for secrets, personal data, and policy violations before it leaves the system.
The honest limitation is that filters are classifiers, and classifiers have error rates. A determined attacker iterates against your filter until something passes, and they have unlimited attempts through any content channel you read. Filtering is still worth building: it stops the lazy majority of attacks, gives you detection telemetry, and buys time. Just architect so that a filter failure is a logged event, not a breach.
The pattern that works is policy at the tool boundary rather than judgment in the prompt. A small, deterministic policy layer — this task type may call these tools with arguments matching these constraints, everything else escalates or refuses — catches entire classes of manipulation that no amount of prompt engineering touches. Prompts ask; policies enforce.
📦Sandboxing: containing code execution and side effects
Any agent that executes code, browses the web, or touches a filesystem needs a sandbox — an isolated environment where the worst case is a discarded container. The options are well established: gVisor or Firecracker microVMs for strong isolation with fast startup, ephemeral containers with no network egress for simpler cases, and hosted sandbox services (E2B, Modal, Daytona and similar) when you would rather rent than run the isolation layer.
The properties that matter: no persistent state between runs, no credentials beyond what the task requires, network egress restricted to an allowlist (an agent that can fetch arbitrary URLs can also post your data to arbitrary URLs — this is the main exfiltration channel), filesystem access scoped to a working directory, and hard CPU, memory, and time limits so loops cannot become bills.
Sandboxing pairs with idempotency. Where possible, make side effects reversible or staged: drafts instead of sends, preview states instead of commits, soft deletes instead of hard ones. An agent operating on reversible actions inside a sandbox is an agent whose mistakes — adversarial or honest — are recoverable. That property is worth real architectural effort, because it converts incidents into log entries.
| Isolation layer | Isolation strength | Operational cost | Best fit |
|---|---|---|---|
| Ephemeral container (Docker) | Moderate — shares the host kernel | Low — standard tooling | Code formatting, data transforms, low-risk execution |
| gVisor / Firecracker microVM | Strong — kernel-level isolation | Moderate — platform engineering required | Running untrusted or model-generated code at scale |
| Hosted sandbox (E2B, Modal, etc.) | Strong, managed by the vendor | Usage cost + vendor review | Teams that want isolation without building the platform |
| Separate cloud account / project | Strongest — blast radius bounded by IAM | High — heavy operational overhead | Regulated data, high-value actions |
🧍Human-in-the-loop gates: putting the right actions behind approval
The strongest control available is also the simplest: some actions do not execute without a human clicking approve. The design question is which actions, and the answer follows from reversibility and blast radius. Sending external email, moving money, deleting data, changing production systems, granting access — these go behind a gate. Reading, summarizing, drafting, and searching stay automatic.
Implement gates as part of the tool layer, not the prompt. A gated tool call produces a structured approval request — the action, the arguments, the context, the diff — rendered somewhere a human can review it fast: a Slack message with buttons, an approval queue, an inbox. The agent pauses or moves on; a timeout policy decides. Vague gates train humans to rubber-stamp, so the approval surface has to show the actual arguments, not "the agent wants to send an email."
Calibrate over time with data. If approval queues overflow, reviewers stop reading and the gate becomes theater — tighten what requires approval or batch low-risk approvals. If gates are rare and always correct, consider which can move to automated-with-audit. The target state is a small number of high-value human decisions per day, each with full context, each logged.
| Action class | Autonomy level | Why |
|---|---|---|
| Read and summarize internal data | Fully automatic + logged | Reversible, low blast radius |
| Draft external communication | Automatic, human reviews before send | Reputational risk, cheap to gate |
| Create or update records in systems of record | Automatic within policy bounds, sampled audit | Reversible with audit trail |
| Send external messages, modify permissions | Human approval required | Externally visible, hard to retract |
| Move money, delete data, touch production | Human approval + second control (limits, dual approval) | Irreversible, high value |
🚩Red-teaming: testing the defenses before an attacker does
You cannot declare an agent secure; you can only demonstrate that known attack classes fail against it. That is what red-teaming is for: a structured attempt to make your own agent misbehave, run before launch and on a schedule after, covering the injection and abuse patterns your architecture claims to contain.
Build an adversarial test set the way you build a golden dataset: real injection payloads seeded into the content your agent reads (emails, pages, documents), attempts to escalate tool privileges, exfiltration probes disguised as normal requests, and refusal-boundary tests. Run them in CI as part of the eval suite so a refactor that weakens a defense fails the build. Published collections of injection payloads and jailbreak corpora are a reasonable starting point — as of writing, several maintained open sets exist; verify current ones rather than trusting a list from any article, including this one.
For high-stakes deployments, add a human red-team exercise: internal security engineers or an outside firm spending focused days attacking the live staging system. Automated suites cover known patterns; creative humans find the unknown ones, like chaining three benign tools into an exfiltration path nobody modeled. Document findings as test cases so every discovered attack becomes a permanent regression check.
Measure what matters: attack success rate by class, time-to-detect in your telemetry, and whether each successful attack would have been caught by a gate or a policy before causing harm. An attack the sandbox contained or the human gate stopped is a validated defense, not a failure.
🌫️Residual risk and honest limits
Here is the part to repeat in every stakeholder meeting: after all of the above, residual risk is not zero. Prompt injection is not solved at the model level, filters have error rates, and a sufficiently motivated attacker with enough attempts will find paths your red team did not. The goal of this architecture is not invulnerability — it is that successful attacks are rare, contained, reversible, detected, and logged.
That framing changes the business conversation productively. Instead of "is it secure" — unanswerable — the question becomes "what is the worst contained outcome, how fast do we detect it, and how do we recover." For many use cases the honest answer supports shipping: an agent with read tools, sandboxed writes, and human gates on irreversible actions has a worst case of wasted compute and a log entry. For an agent that moves money autonomously, the honest answer may be: not yet.
Budget for security as a permanent layer, not a launch milestone. Threats evolve monthly in this space, model updates change behavior under your defenses, and every new tool or data source is new surface. In our experience, security work runs roughly 15 to 25 percent of the build cost of a production agent upfront — as labelled ranges, think $20,000 to $60,000 added to a serious build for the tool redesign, sandboxing, gates, and red-team pass — plus ongoing monitoring and periodic retesting. An agent without it is not cheaper; the cost is deferred to the incident.
🤝When to bring in a team
If your team has production security experience and the agent touches only low-stakes, reversible actions, the patterns here are buildable in-house with existing tooling. Bring in outside help when the agent touches money, customer data, or regulated workflows; when it executes code or browses arbitrary content; or when your security review process was designed for request-response software and nobody has threat-modeled an autonomous system before.
When evaluating a partner, skip the demo and ask security questions: how do they scope tool credentials, what does their sandbox egress policy look like, which actions sit behind human gates, and can they show a red-team report from a past engagement. Teams that do this work answer instantly and specifically. Teams that say the model provider handles security do not.
Codazz builds agents with the containment architecture described in this guide as the default, not an add-on — least-privilege tools, sandboxed execution, gated actions, and adversarial regression suites. With 500+ projects delivered since 2018 and 200+ engineers across Edmonton and Chandigarh, we have shipped agents in support, sales, and operations contexts where a bad action has real consequences.
AI agent development servicesTalk to us about your agent’s threat model
