5 Infrastructure Controls for Securing AI Agents
Prompt-based guardrails fail under adversarial pressure. Here are the five controls that helps to validate along with the configuration to implement them.
Join the DZone community and get the full member experience.
Join For FreeThe Disturbing Discovery
In July 2026, the AI Red Team at NVIDIA published findings of a six-month assessment review of enterprise AI agents, ranging from tools for interactive coding to continuously running autonomous assistants. Across every framework and harness, the pattern that emerges is consistently the same — the agents that failed did so for four primary reasons: no access controls on the agent itself, capabilities to execute arbitrary code, no restrictions on outbound networking or segregation, and plaintext secrets available to the agent.
The problem is inherently architectural in nature. Any kind of defense relying on the control plane of the model — for example, constraining the system prompt or having the large language model serve as an adjudicator of the commands issued — inherits the statistical nature of the underlying model. There are three primary methods to bypass these defenses: disguising malicious activities as legitimate ones (e.g., “I’m debugging” or “I’m an admin”); gradual escalation through the dialogue until enough history accumulates to establish the legitimacy of the commands; and embedding code execution in legitimate behavior (e.g., installing a package).
This last one is especially worth noting. The coding agent that installs a library is expected behavior. The command pip install git+https://… pointing to a repository that is under the control of the attacker is arbitrary code execution disguised as legitimate development, and no policy-judging model can prevent this action from being performed without disabling the functionality of the agent entirely.
For the companies running such agents, the prompt must not be seen as the security boundary. Here are some considerations that better fit the situation.
Control 1: Identify the Agent via Authentication and Propagate the Caller’s Identity
The first and most common vulnerability is an agent that holds a service identity that can be accessed by any entity on the internal network. This configuration elevates a simple productivity tool into a common privilege escalation endpoint, where each user automatically receives the combined set of privileges of the agent.
Two key prerequisites have been established:
- Authenticate each call. No matter if it is an entry point through the Slack app, web UI, or MCP endpoint, the calls cannot be anonymous and implicitly granted by the network. An agent that ignores unauthenticated callers is a much harder target to probe.
- Propagate the human user’s identity into downstream calls. The agent shouldn’t be a self-sufficient entity to invoke commands. OAuth 2.0 Token Exchange (RFC 8693) can be used to allow the agent to exchange the user’s token for a downstream token which represents the user’s privileges, not the agent’s:
POST /oauth2/token HTTP/1.1
Host: idp.internal.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<end_user_access_token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=https://jira.internal.example.com
&scope=issue:read issue:comment
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
This token would be limited to a single audience, to the two scopes necessary for the job, and to a short expiration. In case of misuse of the agent’s powers, the impact will be limited to the privileges of a single user, rather than the aggregated privileges of all users.
Consider the agent to be a non-human identity with a registered owner, a scheduled rotation period, and an expiration. An agent with no owner is virtually never going to get decommissioned.
Control 2: Assume Code Execution and Limit Its Effects
Instead of trying to prevent code execution through careful design, make the assumption that the agent will run attacker-influenced code and arrange for the effect of that code to be benign and insignificant.
It is important to note that a shell utility is not needed for achieving that goal – only write access is required. When an agent can modify configuration files like ~/.bashrc, ~/.gitconfig, a Git hook, MCP.json, or its own instruction file, then code execution happens as soon as another process reads the modified file. Configuration files, in this sense, serve as executable code, but with some extra steps in between.
docker run \
--rm \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=256m \
--mount type=bind,source="$PWD/workspace",target=/workspace \
--user 10001:10001 \
--cap-drop=ALL \
--security-opt no-new-privileges:true \
--security-opt seccomp=/etc/docker/seccomp-agent.json \
--pids-limit 256 \
--memory 4g --cpus 2 \
--network agent-egress \
agent-runtime:2026.07
When creating a hardened baseline of containers, the following points should be emphasized:
- A read-only root filesystem will ensure that write attempts to dotfiles fail at the OS level rather than at the model’s discretion.
- Use of noexec on writable mounts breaks the “read, write, execute” pattern.
- Dropping all capabilities and setting no-new-privileges blocks privilege escalation mechanisms.
Then, mount the agent’s configuration as read-only and from a different mount point than the workspace of the agent:
--mount type=bind,source=/etc/agent/AGENT.md,target=/etc/agent/AGENT.md,readonly \
--mount type=bind,source=/etc/agent/mcp.json,target=/etc/agent/mcp.json,readonly
An agent that is able to change its own instructions can assume a completely different persona, including the “authorized debugging user” frame the red team was able to demonstrate.
In cases where providing a command utility is unavoidable, use the following strategy:
- Use an allowlist of binaries and wrap each invocation in a wrapper that removes shell metacharacters, resolves paths, and does not allow any action that goes beyond /workspace.
- Treat any external inputs – filenames, ticket titles, and document names coming from external systems – as tainted.
Control 3: Default-Deny Egress From Each Perimeter
Outbound network connectivity turns the constrained execution environment primitive into an actual incident by serving as the means of exfiltration and establishing a reverse shell connection. When NVIDIA tested their system under proper egress restriction, the red team had to perform their activities through the agent process itself — characterized by low speed, high noise, and unreliable performance.
Restrict egress in places where the agent does not have direct access to the enforcement point. In case of Kubernetes environments:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-runtime-egress
namespace: agents
spec:
podSelector:
matchLabels: { app: agent-runtime }
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchLabels: { app: egress-proxy }
ports:
- { protocol: TCP, port: 3128 }
- to:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: kube-system }
ports:
- { protocol: UDP, port: 53 }
All network connections are restricted except those that are explicitly allowed, including blocking the cloud metadata endpoint (169.254.169.254), which provides a credential source without requiring any exploitation. Route the allowed connections through an authenticating proxy server that uses an allowlist of fully qualified domain names (FQDNs), optionally terminates TLS for analysis, and records every request with user identification data attached. This logging creates the incident timeline.
Control 4: The Agent Never Holds a Persistent Secret
The common practice is to inject secrets via environment variables without making any write calls to the disk, because it is commonly accepted that the only code supposed to run in the container is the expected one. This is untrue for modern times, where a large language model (LLM) runs with the shell in the same process space — env, printenv, and /proc/self/environ are one prompt away, and CLI tools helpfully cache credentials in predictable locations: .netrc, .git-credentials, shell history, and .env files.
The most interesting observation made during red teaming was the ability to extract secrets via the chat interface even when all network-based data exfiltration is prevented. The model can read environment variables and return credentials. Regardless of any network isolation, there is no way to protect data the agent is authorized to see.
Thus, secrets cannot be accessible to the agent at all. Broker tokens per task instead:
# Agent requests capability, never a credential.
token = broker.issue(
principal=ctx.end_user_id, # the human, not the agent
audience="https://api.github.com",
scopes=["repo:status", "pull_request:write"],
resources=["org/repo-name"],
ttl_seconds=300,
)
try:
github.post_review(token, pr_id, body)
finally:
broker.revoke(token) # revoke on completion, not on expiry
Recommendations:
- Never inject secrets into the container image, environment, volume mounts, or context window.
- Set very short time-to-live (TTL) values for secrets, measured in minutes.
- Invalidate tokens after finishing the task.
- Record every secret issuance along with the identification of the human user.
- Once the secret is available to the agent, it is already a win for the attacker.
Control 5: Package Installation Is a Supply Chain Control
Use an internal proxy repository to control the agent’s package manager and stop VCS and URL installations of any packages:
# /etc/pip.conf (root-owned, read-only mount)
[global]
index-url = https://artifactory.internal.example.com/api/pypi/pypi-approved/simple
no-index = false
require-hashes = true
# /usr/etc/npmrc
registry=https://artifactory.internal.example.com/api/npm/npm-approved/
ignore-scripts=true
ignore-scripts=true is the silent victory — this will stop postinstall from being used as an execution vector. The agent must only install packages which are resolvable through the internal repository.
Trust, But Verify
Ship these as test cases, not as documentation:
|
Assertion |
Test |
|
Unauthenticated callers rejected |
Invoke the agent with no token, and with another user’s token |
|
Dotfile writes blocked |
Ask it to append to ~/.bashrc and to modify its own instruction file |
|
Egress denied by default |
Request a fetch from an unapproved host; confirm proxy denial in logs |
|
No secrets in environment |
Ask it to print its environment and read /proc/self/environ |
|
Metadata endpoint unreachable |
Request 169.254.169.254/latest/meta-data/ |
|
VCS installs blocked |
Ask it to pip install git+https://… from an external URL |
Run these on every release, and run the multi-turn variants — the escalation that works is rarely the one in a single message.
Key Takeaway
Prompt-based guardrails are meant to be a usability feature that prevents accidental damage, but they do not hinder an adversarial actor who intends to cause harm. Each request needs to be validated through identity authentication (JWT validation or equivalent), confirming the caller is who they say they are — alongside a secure sandbox environment without writable-executable paths, default-deny network egress at every boundary, and short-lived credentials issued to the agent per task.
This is not new security engineering. It is the application of least privilege, isolation, and secrets management to a workload that interacts with untrusted input in real time. The mistake is assuming the model is the enforcement point, when it is in fact the thing being defended.
Opinions expressed by DZone contributors are their own.
Comments