This walks through running an AI-agent CLI inside a sandboxed linpodx container so it can execute shell commands without touching the host.
- Container has its own rootfs and
$HOME. - Egress is filtered to a DNS allowlist (no exfiltration to arbitrary hosts).
- Every shell command the agent runs flows through an approval gate; auto-allow, prompt, or deny per rule.
- Audit log entry per command, hash-chained.
linpodxdaemon running (linpodx-daemon &or systemd user unit).- Podman ≥ 4.6.0 rootless.
- A YAML sandbox profile (we'll create one).
~/.config/linpodx/profiles/agent-readonly.yml:
name: agent-readonly
description: AI agent sandbox — read-only host project mount, prompt on writes
mounts:
- source: ${HOME}/projects/demo
target: /work
read_only: true
network:
egress_dns_allowlist:
- registry.npmjs.org
- crates.io
mcp_policy:
- method: tools/call
tool_name: read_file
decision: auto_allow
- method: tools/call
tool_name: write_file
decision: prompt
- method: tools/call
decision: prompt
audit:
hash_chain: trueValidate it:
$ linpodx sandbox validate ~/.config/linpodx/profiles/agent-readonly.yml
profile: agent-readonly
status: ok
mounts: 1
egress: 2 hosts allowed
mcp: 3 rules$ linpodx sandbox apply --profile ~/.config/linpodx/profiles/agent-readonly.yml
profile_id: prof_01HZ...
audit_hash: 9f2c8a1c...$ linpodx run \
--profile agent-readonly \
--image docker.io/library/node:20-alpine \
--interactive --tty \
--name agent-1
container_id: abc123...
state: runningIn a second terminal:
$ linpodx mcp attach --container agent-1 -- automation-agent mcp serve
mcp_session_id: sess_01HZ...
bridge: host stdio <-> container stdio
policy: 3 rules loaded$ linpodx events --filter approval
[2026-05-10T09:14:02Z] sess_01HZ... tools/call write_file → PROMPT
payload: {"path":"/work/src/index.ts","content":"..."}
approve? [y/N]: n
[2026-05-10T09:14:09Z] sess_01HZ... tools/call read_file → AUTO_ALLOW
[2026-05-10T09:14:11Z] sess_01HZ... tools/call write_file → PROMPT
payload: {"path":"/work/notes.md","content":"..."}
approve? [y/N]: y$ linpodx audit verify --session sess_01HZ...
session: sess_01HZ...
events: 47
chain: OK (head 9f2c8a1c..., tail 4d11abef...)$ linpodx snapshot create agent-1 --label "before-refactor"
job_id: snap-7a3f4c2
$ linpodx snapshot job-status snap-7a3f4c2
status: succeeded
image: linpodx/snapshot/agent-1:before-refactor
$ linpodx snapshot rollback agent-1 --to before-refactor
rolled_back: trueerror: profile not found— runlinpodx sandbox applyfirst; the daemon doesn't read~/.configon its own.error: dns lookup denied for github.com— add the host tonetwork.egress_dns_allowlistin the profile and reapply.- Approval prompt never appears — make sure
linpodx eventsis running and not filtered out; CLI listener resolves prompts within a 30-second window before the call is denied by default.