OpenAI, Netflix, and Dave Are Rethinking How Coding Agents Get Secured From the Inside Out
By Declan Osei · September 19, 2026
Category: attack-surface-threat-modeling
Key takeaways
The problem Teams deploying coding agents rely on prompt instructions and tool allowlists as security boundaries, but those controls do not reach the execution layer where tool calls actually run.
Core insight An LLM is a generator of requests, not a decision-maker that respects constraints, so security invariants must be enforced in code at the execution layer rather than written into prompts.
Practical outcome Readers can audit their agent architecture for prompt-only controls, add a pre-execution logging layer, and begin replacing prompt-based restrictions with capability tokens validated at the point of tool execution.
"We control what the agent can do by restricting its tool access and writing careful prompts." We have heard this in architecture reviews, in security audits, and - more than once - from teams who had already deployed agents into production. It feels right. It maps cleanly to how we think about access control in traditional systems: define what's allowed, enforce the boundary at the gate. The problem is that in agentic systems, the gate isn't where you think it is.
The [un]prompted conference surfaced a version of this reckoning across multiple organizations simultaneously. Paul McMillan at OpenAI, Scott Behrens at Netflix, and Srajan Gupta at Dave were each working through variations of the same structural problem: coding agents, LLM-driven vulnerability discovery, and MCP server interactions all expose a gap between what a prompt says the agent should do and what the agent's tool calls actually execute. The gap is not theoretical. It's a production failure mode.
The Misconception: Prompt Engineering and Tool Allowlists Are Sufficient Security Boundaries
The belief has a reasonable origin. Early LLM security guidance treated the model as a black box you could constrain through careful instruction. Write a system prompt that says "do not access production data." Restrict the tool schema to read-only operations. Document which tools the agent is allowed to call. This instinct - treat the model as the decision point - was correct in a narrow sense and wrong in an architectural one.
Vendor documentation has reinforced the pattern. Tool definitions in the OpenAI Assistants API, in Anthropic's tool use, in most MCP server implementations, are described primarily in terms of schema and description. The framing is: tell the model what the tool does and what inputs it accepts, and the model will use it appropriately. That framing treats the LLM as a security boundary. It isn't.
The specific assumption that breaks is this: tool-use restrictions assume the LLM will interpret and respect those restrictions as written, consistently, across all inputs and contexts. It won't - not because the model is malicious, but because it's a generator. It generates plausible next tokens given its context. If its context contains instructions that make a restricted tool call seem appropriate, it will generate that call. The prompt is one input to that generator. It is not a lock.
Consider a developer building a coding agent with access to a file-read tool, a code-execution tool, and an API call tool. They write a prompt that says "only read files within the project directory" and define the file-read tool with a description that says the same. A user inputs a task that, after several reasoning steps, causes the agent to construct a path traversal. The model didn't "decide" to violate the restriction - it generated a file path that happened to escape the intended scope, because the path made sense given the accumulated context. The restriction lived in the prompt. The execution layer didn't know it existed.
Paul McMillan's work on "Code Is Free" security invariants at OpenAI points at exactly this. The invariants are properties the system must maintain regardless of what the model generates - not guidelines for the model to follow, but constraints enforced at the layer below the model. That's the architectural shift the misconception misses.
Where This Breaks in Production: Tool-Use Interception and Context Poisoning
Walk through a concrete sequence. An agent is given three tools: file read, code execution, and an external API call. The system prompt says "do not access production data." The agent is deployed to help developers debug test environments.
An attacker - or a sufficiently unusual input - causes the agent to reason its way toward reading a configuration file outside the test directory. The file-read tool doesn't know it's not supposed to do this. It has a schema, it validates the input type, and it executes. The restriction was in the prompt. The execution layer doesn't consult the prompt.
That's the first-order failure. The second-order failure is what happens next. The file-read result is added to the agent's context. Now the context contains real configuration data - database credentials, environment variables, API keys. The agent's next reasoning step happens with that data present. It might not do anything obviously malicious. It might just use those credentials in a subsequent API call because they're there and they look like the right credentials for the task. Each step is locally plausible. The chain is a privilege escalation.
Scott Behrens's LLM vulnerability discovery work at Netflix tracks a similar pattern in code-generation contexts. When an LLM is generating code that interacts with production systems, the generated code can contain vulnerabilities that the model didn't "intend" - path traversals, injection points, over-permissive file operations - because the model is optimizing for plausible code, not for secure code. The prompt says "write secure code." The execution layer runs whatever was generated.
Srajan Gupta's work on MCP server security at Dave adds another layer. MCP servers expose tool capabilities through a context interface, and the context itself can be poisoned. If an attacker can influence what the MCP server returns as context - injecting instructions or data that redirect the agent's tool calls - the prompt-based controls are irrelevant. The model is reading from a context it trusts. The execution layer doesn't validate that the context is clean.
The failure mode is consistent: tool-call interception and context poisoning operate below the prompt layer. Prompt-based controls don't reach them.
The Corrected Mental Model: Security Invariants Must Live in Code, Not Prompts
Stop thinking of the LLM as a decision-maker that respects constraints. Think of it as a generator of requests that must be validated before execution. This reframe changes where you put your security controls.
The corrected belief: security boundaries in agentic systems cannot be enforced by prompt engineering or allowlists alone. They must be enforced at the execution layer, where tool calls actually run, independently of what the model was instructed to do.
McMillan's framing of security invariants is precise here. An invariant is a property the system maintains regardless of the path taken to reach any given state. "This agent cannot write to the production database" is not an invariant if it's written in a prompt - it's a suggestion. It becomes an invariant when the database tool's execution layer rejects write operations regardless of how the model invoked it, regardless of what credentials were in context, regardless of what the system prompt said.
The architectural implication is a capability-based security model. Each tool is defined not just by its schema and description, but by its execution context: what capabilities does a caller need to invoke this tool? What inputs are valid not just syntactically but semantically, given the caller's context? What outputs should be scrubbed before they're returned to the agent's context?
This is meaningfully different from RBAC. A role gives an agent access to a set of tools. A capability gives an agent access to a specific operation within a specific scope, and that scope is enforced at the point of execution. The difference matters when the agent's context has been partially poisoned or when a chain of plausible-looking tool calls is leading somewhere the prompt never intended.
Practical Defenses: From Theory to Implementation
Tool-call validation at the execution layer
A tool-call validator that only checks whether the tool exists and whether the input matches the schema is not a security control. It's a type checker. A security-relevant validator checks: is this input within the declared scope for this caller? Does the input, given the caller's capability token, make sense? Is the combination of tool name, input, and caller context consistent with expected behavior?
The validator needs to operate before execution, not after. Log the validation result. Reject and surface anomalies rather than silently dropping them - silent failures are invisible to your incident detection.
Capability tokens and sandboxing
Move from "the agent has access to these tools" to "the agent has a token that grants it specific capabilities within a defined scope." A file-read capability token should encode not just "read files" but "read files within this directory, for this session, with these exclusions." The token travels with the tool call. The execution layer validates the token, not the prompt.
Sandbox code-execution tools aggressively. A coding agent that can execute arbitrary code is a high-privilege process attached to an LLM. Treat it accordingly: network isolation, filesystem restrictions, execution time limits, and syscall filtering. The OWASP Top 10 for LLM Applications lists excessive agency and insecure plugin design as two of the most common failure modes in deployed systems - both map directly to insufficient execution-layer sandboxing.
Tool-call logging and anomaly detection
Log: the tool name, the full input, the caller identity, the capability token used, the timestamp, the output (or a hash of it for sensitive outputs), and any validation errors. That's the minimum. What matters for detection: a tool being called with inputs outside its declared scope, a tool being called in a sequence that doesn't match expected workflow patterns, a tool being called with inputs that reference system paths or credentials that shouldn't be in the agent's context, and a tool being called at a rate inconsistent with the task.
Detection requires a baseline. Run your agent through representative tasks and characterize normal tool-call sequences before you're looking for anomalies in production.
STRIDE adapted for agentic systems
Generic STRIDE applies, but the categories need mapping. Spoofing: can an attacker forge a tool-call request, or inject content into the MCP context that makes the agent believe it came from a trusted source? Tampering: can the tool's input or output be modified in transit between the LLM and the execution layer? Repudiation: do your logs capture enough to reconstruct what the agent did and why, including the context state at the time of each tool call? Information disclosure: does tool output get returned to the agent's context without scrubbing - and does that context get logged or transmitted in ways that expose sensitive data? Denial of service: can an attacker cause the agent to enter a tool-call loop, exhausting rate limits or compute budgets? Elevation of privilege: can a sequence of individually-permitted tool calls lead to a capability the agent was never explicitly granted?
The Anthropic tool use documentation covers schema-level constraints but is explicit that execution-layer validation is the implementer's responsibility. That's not a criticism - it's an accurate description of the boundary. Your threat model needs to cover what's on your side of that boundary.
What You Should Be Doing Right Now: Ordered by Effort and Impact
First, and with minimal architectural change: add a logging layer before tool execution. Not after. Before. Log every tool call with the fields named above. This gives you immediate visibility and a baseline for anomaly detection. You will almost certainly find tool calls you didn't expect.
Second, audit your current architecture against the corrected model. Walk through your agent system and ask: where are my security boundaries? Are they in prompts, in schemas, or in execution-layer enforcement? For each high-risk tool - code execution, database access, file system operations, external API calls - ask whether a malformed or adversarially constructed input could cause the tool to operate outside its intended scope. Document the exposure. Don't estimate it.
Third, move from prompt-based to capability-based access control for your highest-risk tool. Pick one - probably code execution or database access - and implement a capability token for it. Define the token's scope explicitly. Build the execution-layer validator that checks the token before running the tool. Treat this as a proof of concept for the pattern before rolling it out system-wide.
Fourth, red-team your own tool-use chains. The questions to answer: can you make the agent escalate from a read-only database query to a write operation through a sequence of individually-permitted calls? Can you inject content into the agent's context - through a tool output, a retrieved document, or a user input - that causes it to make a tool call it was explicitly told not to make? Can you construct a path that causes the agent's capability token to be used in a scope it wasn't intended for? Run these tests before someone else does. The Microsoft Security blog's work on AI-assisted code generation security covers several red-team patterns relevant to coding agents specifically.
We don't yet have a complete picture of how trust degrades across multi-agent handoffs, and anyone who claims they do is probably oversimplifying. What we do know is that the execution layer is where the real boundary lives. Build your controls there, log what crosses that boundary, and treat the prompt as input to a system that doesn't inherently respect it.
FAQ: Applying This to Your Stack
What if I am using a managed agent platform like Anthropic's tool use or OpenAI Assistants?
Managed platforms handle some execution-layer validation for you - schema enforcement, type checking, basic input validation. They do not eliminate your need for security controls; they shift the boundary of your responsibility rather than eliminating it.
What you still need to do: implement your own tool-call logging and validation layer on top of the platform API. Before passing a tool call to the Assistants API or Anthropic's tool-use endpoint, validate that the call's inputs are within the expected scope for your application. After receiving the output, scrub it before returning it to the agent's context if it contains sensitive data.
The specific risk with managed platforms is opacity. You cannot see how they validate tool calls internally, and you cannot customize their validation logic. If a vulnerability is discovered in the platform's execution layer, your response time depends on the vendor's patch cycle, not yours. Your own logging layer is the only
Frequently Asked Questions
Why isn't a system prompt enough to restrict what a coding agent can do?
A system prompt is an input to a generator, not a lock. The LLM produces plausible next tokens given its context - if the accumulated context makes a restricted tool call seem appropriate, the model will generate that call. It does not 'decide' to violate the restriction; it simply produces output that happens to escape the intended scope. The execution layer that actually runs the tool call never consults the prompt, so the restriction has no enforcement mechanism below the model itself.
What is a security invariant in the context of agentic systems, and how is it different from a prompt instruction?
A security invariant is a property the system maintains regardless of what the model generates or what path led to a given state. For example, 'this agent cannot write to the production database' is only an invariant when the database tool's execution layer rejects write operations unconditionally - independent of the model's instructions, the credentials in context, or the system prompt. Written as a prompt instruction, it is a suggestion the model may not follow. The architectural difference is that invariants live in code at the execution layer, not in text the model reads.
How does context poisoning turn a single tool-call mistake into a privilege escalation?
When a tool returns output that gets added to the agent's context - such as a configuration file containing database credentials or API keys - the agent's next reasoning step happens with that sensitive data present. Each subsequent tool call is locally plausible given the context, but the chain can lead somewhere the prompt never intended. Srajan Gupta's work on MCP server security at Dave illustrates this: if an attacker can influence what the MCP server returns as context, injecting instructions or data that redirect the agent's tool calls, prompt-based controls are bypassed entirely because the model trusts what it reads from context.
What is the difference between capability-based access control and RBAC for agent tool access?
A role gives an agent access to a set of tools. A capability gives an agent access to a specific operation within a specific scope, and that scope is enforced at the point of execution. For example, a file-read capability token encodes not just 'read files' but 'read files within this directory, for this session, with these exclusions.' The token travels with the tool call and the execution layer validates it directly. This distinction matters when the agent's context has been partially poisoned or when a chain of individually permitted tool calls is leading somewhere the prompt never intended.
If I am using a managed platform like OpenAI Assistants or Anthropic's tool use, do I still need my own execution-layer controls?
Yes. Managed platforms handle schema enforcement, type checking, and basic input validation, but they shift the boundary of your responsibility rather than eliminating it. You still need to implement your own tool-call logging and validation layer on top of the platform API, validate that call inputs are within the expected scope for your application before passing them to the platform, and scrub sensitive data from tool outputs before returning them to the agent's context. An additional risk with managed platforms is opacity - you cannot customize their internal validation logic, and your response time to any platform-level vulnerability depends on the vendor's patch cycle.