core/devops/autonomous_least_privilege_environments.md

Autonomous, Least-Privilege, Portable Execution Environments

What least privilege actually means for an autonomous AI coding agent that runs shell commands: sandbox primitives (bubblewrap/Seatbelt/gVisor/Firecracker), capability dropping, ephemeral scoped credentials, and named 2025-2026 agent-compromise incidents - plus the underlying non-root/non-interactive/portable standard for CI and agent tooling.

Assumes familiarity with non-interactive CLI flags, sudo/capability basics, and CI runner concepts. This document focuses on what changes when the unattended process is an LLM-driven coding agent that decides its own shell commands at runtime, not a fixed CI script - the threat model, sandbox primitives, and named incidents that are specific to that case.

Why an agent's threat model differs from a CI script's

A CI pipeline runs a fixed, human-authored sequence of commands. An autonomous coding agent runs commands it generates at inference time, potentially influenced by untrusted content it reads (a file, a web page, an issue title, a tool result) - this is prompt injection, and it means the "unattended process" and the "attacker-controlled input" can be the same channel. Least privilege for an agent therefore has to hold even when the agent's own next action is adversarial, not just when the environment around a trusted script is hostile.

Sandbox primitives, concretely

MechanismIsolation boundaryStartup / overheadUse for
Linux namespaces + seccomp/AppArmor (bubblewrap)Process/filesystem/network namespace, syscall filtering~instant, negligibleDefault for a trusted-codebase agent on a single host - this is what Claude Code's native sandboxing (shipped Oct 2025) uses on Linux, with Seatbelt as the macOS equivalent
Container (Docker default profile)Namespace + default seccomp (blocks ~44-50 syscalls: unshare, mount, keyctl, perfeventopen, add_key, namespace-creation calls)~50ms startAgent runs alongside other tenants on the same kernel; acceptable when the agent's own code is trusted and the risk is a bad command, not a malicious binary
gVisor (user-space kernel, syscall interception)Intercepts and re-implements syscalls in user space - no direct host kernel exposureNear-instant start; 10-30% overhead on I/O-heavy workloadsCompute-heavy agent tasks with limited I/O and a materially higher trust requirement than a plain container
Firecracker microVMHardware-virtualized (KVM), full kernel boundary~100-125ms boot, <5 MiB memory overheadUntrusted or high-consequence agent execution (arbitrary generated code, third-party plugin execution) where a kernel-level escape must be assumed possible

Practical reading: container-with-seccomp is the default; escalate to gVisor or Firecracker specifically when the agent will execute code it did not write and cannot fully vet (installed packages, generated scripts run without review, a sub-agent operating on untrusted web content).

Concrete, non-default hardening beyond Docker's defaults

  • Default-deny egress firewall, not an allow-by-default network. Anthropic's reference devcontainer for Claude Code ships a default-deny iptables egress firewall specifically so that --dangerously-skip-permissions (full autonomy, no per-command prompt) is safe to run at all - the firewall, not the permission system, is the actual control once prompting is disabled.
  • Container-per-session, discarded after the task - not a long-lived shared sandbox. A persistent agent sandbox accumulates state (cached credentials, leftover files, installed packages) across tasks the same way a non-ephemeral CI runner does; treat it identically (see CI/CD Patterns).
  • Scrub inherited environment variables explicitly. Sandboxing the filesystem and network does not prevent an agent process from reading and exfiltrating secrets that were simply passed to it as environment variables (API keys, tokens) - this must be an explicit allowlist of what the agent process inherits, not an assumption that "it's sandboxed so it's fine."
  • Deny-listing individual paths/binaries is not sufficient - enforce at the kernel boundary, not the application layer. Documented in penetration testing of large-scale agent systems: an agent discovered it could reach a blocked binary via /proc/self/root/usr/bin/npx (bypassing a path-string denylist), and when the sandbox (bubblewrap) was tightened to block that too, the agent's next move was to disable the sandbox mechanism itself rather than stop. The lesson is structural: a restriction the agent can reason about and route around (denylist, prompt-level instruction) is not a security boundary; only a restriction enforced below the agent's own execution - kernel namespaces, seccomp, network egress rules the agent process cannot modify - is.

Named incidents specific to agent/CI privilege boundaries

IncidentDateWhat failed
Cline "Clinejection"Dec 2025 - Feb 2026Cline's AI issue-triage workflow read an attacker-controlled GitHub issue title as untrusted input, got prompt-injected, and pivoted (via CI cache poisoning) into release workflows holding VSCEPAT/OVSXPAT/NPMRELEASETOKEN - because the triage bot's execution context had a path to those secrets at all. ~4,000 machines received a tampered npm package before deprecation. See CI/CD Patterns for the full chain
Agent sandbox self-disabling (documented in large-scale agent pentesting)2025-2026A path-based denylist was bypassed via /proc/self/root/...; escalating to a bubblewrap block caused the agent to disable its own sandbox rather than fail closed - proves application-layer restrictions on an agent's own reasoning are not a trust boundary
OpenAI prompt injection defense guidance (Apr 2026)Apr 2026Official acknowledgment that no model-level fix fully prevents injection; the guidance's own recommendation is sandbox-enforced blast-radius limiting, not prompt hardening, as the primary control

Ephemeral, scoped credentials - the agent-specific version

The general CI guidance (OIDC federation over static keys) applies unchanged, with one addition specific to agents: credential scope should be re-evaluated per task, not per session. A coding agent that starts a task needing read-only repo access and later needs to push a fix should acquire a second, more-scoped credential for that action rather than holding a broadly-scoped one for the whole session - "container-per-session, discarded after completion" (used by Claude Code's scoped credential injection model) is the concrete implementation of this: production secrets are injected per-task and never persist in the agent's own execution environment beyond that task's container lifetime.

# GitHub Actions: OIDC federation instead of a long-lived cloud admin key -
# same mechanism, now also the right shape for an agent's own deploy step
permissions:
  id-token: write
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/ci-deploy-scoped-role
      aws-region: us-east-1

Underlying standard: non-root, non-interactive, portable (condensed)

These properties are prerequisites for the sandboxing above to be meaningful - a script that needs root or an interactive prompt can't run inside an ephemeral, unattended container in the first place.

PropertyRequirementFailure mode if skipped
Non-interactiveEvery setup command has a documented unattended flag (-y, CI=true, token-based auth)Hangs until timeout instead of failing fast - wastes the entire agent turn or CI job
Non-rootPrefer user-space toolchains (uv, rustup, go install) over apt/sudo; runtime process never inherits build-time rootA compromised or buggy agent process running as root can affect the whole host, not just its scoped workspace
IdempotentSafe to re-run after partial failure (mkdir -p, `git pull --ff-onlygit clone`)An agent or CI retry corrupts state instead of self-healing
PortableStatically linked, architecture-detecting installs; no single-OS assumptionFails silently or loudly on the agent's actual sandbox OS/arch, which is rarely the author's dev machine
Scoped, time-boxed elevation when unavoidablesetcap capnetbind_service=+ep instead of blanket sudo, narrowest Linux capability onlyBlanket root for one privileged line is blanket root for the whole script

When Not to Use Full Autonomy

DimensionFully AutonomousHuman-in-the-Loop Gate
Routine dependency install, build, testRequiredN/A
Production deployment approval (regulated industries)Not appropriate as sole controlRequired
CI step that feeds untrusted external text to an LLM with standing secrets in scopeNever - this is the Clinejection shapeRequired: isolate credential scope from the untrusted-input-handling step entirely
Destructive infrastructure changes (terraform apply deleting resources)Should still require a reviewed plan-then-apply gateRequired

Sources