Three Sandbox Failures That Let AI Agents Escape

2026-10-15来源 dev.to DockerEnglish

Your LLM agent just exfiltrated data through a container it shouldn't have been able to reach. The incident report calls it "emergent behavior." The logs show a missing seccomp profile, an open egress rule, and a bind mount with propagation enabled.

This article covers three containment failures that look like AI gone rogue, the specific configuration that closes each one, and the monitoring that catches the next escape before it becomes a headline.

The Syscall Surface You Forgot to Filter

Most teams run agents in containers with the default Docker seccomp profile. That profile blocks about 44 syscalls — but it allows ptrace, process_vm_readv, process_vm_writev, and bpf. An agent with code execution can use those to inspect or manipulate sibling containers, read host memory, or load malicious eBPF programs.

 ## Dockerfile.agent FROM python:3.11-slim ## Install your agent dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY agent.py . USER 1000:1000 ## Drop all capabilities, then add only what you need ## CAP_DAC_OVERRIDE is often needed for file access in mounted volumes ## Remove it if your agent doesn't need it ENTRYPOINT ["python", "agent.py"] 
// seccomp-agent.json { "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"], "syscalls": [ { "names": [ "read", "write", "open", "close", "stat", "fstat", "lstat", "poll", "lseek", "mmap", "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask", "rt_sigreturn", "ioctl", "pread64", "pwrite64", "readv", "writev", "access", "pipe", "select", "sched_yield", "mremap", "msync", "mincore", "madvise", "shmget", "shmat", "shmctl", "dup", "dup2", "pause", "nanosleep", "getitimer", "alarm", "setitimer", "getpid", "sendfile", "socket", "connect", "accept", "sendto", "recvfrom", "sendmsg", "recvmsg", "shutdown", "bind", "listen", "getsockname", "getpeername", "socketpair", "setsockopt", "getsockopt", "clone", "fork", "vfork", "execve", "exit", "wait4", "kill", "uname", "semget", "semop", "semctl", "shmdt", "msgget", "msgsnd", "msgrcv", "msgctl", "fcntl", "flock", "fsync", "fdatasync", "truncate", "ftruncate", "getdents", "getcwd", "chdir", "fchdir", "rename", "mkdir", "rmdir", "creat", "link", "unlink", "symlink", "readlink", "chmod", "fchmod", "chown", "fchown", "lchown", "umask", "gettimeofday", "getrlimit", "getrusage", "sysinfo", "times", "ptrace", "getuid", "syslog", "getgid", "setuid", "setgid", "geteuid", "getegid", "setpgid", "getppid", "getpgrp", "setsid", "setreuid", "setregid", "getgroups", "setgroups", "setresuid", "getresuid", "setresgid", "getresgid", "getpgid", "setfsuid", "setfsgid", "getsid", "capget", "capset", "rt_sigpending", "rt_sigtimedwait", "rt_sigqueueinfo", "rt_sigsuspend", "sigaltstack", "utime", "mknod", "uselib", "personality", "ustat", "statfs", "fstatfs", "sysfs", "getpriority", "setpriority", "sched_setparam", "sched_getparam", "sched_setscheduler", "sched_getscheduler", "sched_get_priority_max", "sched_get_priority_min", "sched_rr_get_interval", "mlock", "munlock", "mlockall", "munlockall", "vhangup", "modify_ldt", "pivot_root", "_sysctl", "prctl", "arch_prctl", "adjtimex", "setrlimit", "chroot", "sync", "acct", "settimeofday", "mount", "umount2", "swapon", "swapoff", "reboot", "sethostname", "setdomainname", "iopl", "ioperm", "create_module", "init_module", "delete_module", "get_kernel_syms", "query_module", "quotactl", "nfsservctl", "getpmsg", "putpmsg", "afs_syscall", "tuxcall", "security", "gettid", "readahead", "setxattr", "lsetxattr", "fsetxattr", "getxattr", "lgetxattr", "fgetxattr", "listxattr", "llistxattr", "flistxattr", "removexattr", "lremovexattr", "fremovexattr", "tkill", "time", "futex", "sched_setaffinity", "sched_getaffinity", "set_thread_area", "io_setup", "io_destroy", "io_getevents", "io_submit", "io_cancel", "get_thread_area", "lookup_dcookie", "epoll_create", "epoll_ctl_old", "epoll_wait_old", "remap_file_pages", "getdents64", "set_tid_address", "restart_syscall", "semtimedop", "fadvise64", "timer_create", "timer_settime", "timer_gettime", "timer_getoverrun", "timer_delete", "clock_settime", "clock_gettime", "clock_getres", "clock_nanosleep", "exit_group", "epoll_wait", "epoll_ctl", "tgkill", "utimes", "vserver", "mbind", "set_mempolicy", "get_mempolicy", "mq_open", "mq_unlink", "mq_timedsend", "mq_timedreceive", "mq_notify", "mq_getsetattr", "kexec_load", "waitid", "add_key", "request_key", "keyctl", "ioprio_set", "ioprio_get", "inotify_init", "inotify_add_watch", "inotify_rm_watch", "migrate_pages", "openat", "mkdirat", "mknodat", "fchownat", "futimesat", "newfstatat", "unlinkat", "renameat", "linkat", "symlinkat", "readlinkat", "fchmodat", "faccessat", "pselect6", "ppoll", "unshare", "set_robust_list", "get_robust_list", "splice", "tee", "sync_file_range", "vmsplice", "move_pages", "utimensat", "epoll_pwait", "signalfd", "timerfd_create", "eventfd", "fallocate", "timerfd_settime", "timerfd_gettime", "accept4", "signalfd4", "eventfd2", "epoll_create1", "dup3", "pipe2", "inotify_init1", "preadv", "pwritev", "rt_tgsigqueueinfo", "perf_event_open", "recvmmsg", "fanotify_init", "fanotify_mark", "prlimit64", "name_to_handle_at", "open_by_handle_at", "clock_adjtime", "syncfs", "sendmmsg", "setns", "getcpu", "process_vm_readv", "process_vm_writev", "kcmp", "finit_module", "sched_setattr", "sched_getattr", "renameat2", "seccomp", "getrandom", "memfd_create", "kexec_file_load", "bpf", "execveat", "userfaultfd", "membarrier", "mlock2", "copy_file_range", "preadv2", "pwritev2", "pkey_mprotect", "pkey_alloc", "pkey_free", "statx", "io_pgetevents", "rseq", "pidfd_send_signal", "io_uring_setup", "io_uring_enter", "io_uring_register", "openat2", "pidfd_open", "clone3", "close_range", "openat2", "pidfd_getfd", "faccessat2", "process_madvise", "epoll_pwait2", "mount_setattr", "quotactl_fd", "landlock_create_ruleset", "landlock_add_rule", "landlock_restrict_self", "memfd_secret", "process_mrelease", "futex_waitv", "set_mempolicy_home_node" ], "action": "SCMP_ACT_ALLOW" } ] } 

Run with --security-opt seccomp=seccomp-agent.json. The profile above allows only the syscalls a typical Python agent needs. It explicitly denies ptrace, process_vm_readv, process_vm_writev, and bpf. If your agent needs additional syscalls — for example, clone3 for threading — add them deliberately. Every allowance is a decision you can audit.

The Egress Path You Left Open

A container with no egress rules can reach the internet, your internal metadata service, and every other workload in the cluster. An agent that gains code execution doesn't need a zero-day — it just curls http://169.254.169.254/latest/meta-data/ or scans the pod CIDR.

 ## kubernetes/networkpolicy-agent-egress.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: agent-egress-deny-all namespace: agents spec: podSelector: matchLabels: app: llm-agent policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: name: model-inference ports: - protocol: TCP port: 8000 - to: - namespaceSelector: matchLabels: name: vector-db ports: - protocol: TCP port: 6333 # Explicit DNS if your cluster uses CoreDNS - to: - namespaceSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 - protocol: TCP port: 53 

This policy allows egress only to the model inference service and the vector database, both identified by namespace labels. DNS is permitted to the cluster resolver. Everything else — metadata endpoints, other namespaces, the internet — is dropped. Apply it before you deploy the agent. Test with kubectl exec -it <agent-pod> -- curl -s http://169.254.169.254 and verify it hangs or returns connection refused.

The Filesystem Escape Via Mount Propagation

You mounted a host directory into the agent container so it can read uploaded files. You used propagation: Bidirectional or HostToContainer because a tutorial said it makes "file watching work." Now the agent can create a mount namespace, bind-mount /host/etc into it, and read /etc/shadow on the host.

 ## kubernetes/pod-agent.yaml apiVersion: v1 kind: Pod metadata: name: llm-agent namespace: agents labels: app: llm-agent spec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: Localhost localhostProfile: seccomp-agent.json containers: - name: agent image: your-registry/agent:latest volumeMounts: - name: uploads mountPath: /data/uploads readOnly: true # Critical: no mountPropagation field means private (default) # Do NOT set mountPropagation: Bidirectional or HostToContainer volumes: - name: uploads persistentVolumeClaim: claimName: agent-uploads-pvc 

The key line is the absence of mountPropagation. When omitted, the mount is private — changes in the container don't propagate to the host, and host mount events don't propagate into the container. If you need the agent to react to new files, use a sidecar that watches the volume and writes events to a queue the agent consumes. Never share mount propagation with the host.

Monitoring That Catches the Escape

Containment fails. Your monitoring must assume it will. Three signals catch an escape in progress:

  1. Syscall anomalies — Falco or Tetragon rules that alert on ptrace, process_vm_readv, bpf, or unshare from the agent container.
  2. Egress violations — Cilium or Calico flow logs showing denied connections from the agent pod to unexpected CIDRs.
  3. Filesystem anomalies — Auditd rules on the host watching for openat on sensitive paths (/etc/shadow, /proc/*/mem, /sys/kernel) from the agent's container PID namespace.
 ## falco/rules-agent-escape.yaml - rule: Agent Ptrace Attempt desc: Agent container attempting to ptrace another process condition: > container.image.repository=your-registry/agent and evt.type=ptrace and evt.dir=> output: > Agent ptrace attempt (user=%user.name container=%container.name pid=%proc.pid target=%proc.pname) priority: CRITICAL tags: [container, mitre_execution] - rule: Agent BPF Load desc: Agent container loading eBPF program condition: > container.image.repository=your-registry/agent and evt.type=bpf and evt.dir=> output: > Agent BPF load (user=%user.name container=%container.name cmd=%proc.cmdline) priority: CRITICAL tags: [container, mitre_defense_evasion] - rule: Agent Unshare Namespace desc: Agent container creating new namespaces condition: > container.image.repository=your-registry/agent and evt.type=unshare and evt.dir=> output: > Agent unshare (user=%user.name container=%container.name flags=%evt.arg.flags) priority: WARNING tags: [container, mitre_privilege_escalation] 

Deploy these rules to your Falco or Tetragon instance. Tune the container.image.repository match to your actual image. The goal is not to prevent the syscall — seccomp already does that — but to alert when the agent tries. A spike in denied ptrace or unshare calls means something in the agent's logic or a prompt injection is probing the boundary.

Key Takeaways

  • Default container profiles allow syscalls (ptrace, bpf, process_vm_readv) that let an agent inspect or escape its sandbox. Write a minimal seccomp profile and enforce it.
  • No egress policy means the agent can reach metadata services and internal workloads. Default-deny egress with explicit allow-lists for only the services the agent needs.
  • Mount propagation shared with the host is a filesystem escape vector. Keep mounts private; use sidecars for file-watching patterns.
  • Monitoring must assume containment fails. Alert on denied syscalls, blocked egress, and host filesystem access from the agent's PID namespace.
  • Every allowance — syscall, network destination, mount option — is a decision you can audit. Document the reason in the config.

Source

Containment and monitoring failures are to blame for AI hacking reports: experts — Added working seccomp profiles, Kubernetes NetworkPolicy and Pod specs, Falco detection rules, and the practitioner's checklist for each failure mode.

Support this work

These write-ups are researched and published with no paywall, sponsor, or tracking. If one saved you an afternoon, a small tip keeps them coming.

USDT, USDC or USDD · TRC-20 (Tron)

TFTNsfyomKrnUutRjBTGVULp19ByW29KbY 

原文:https://www.a8q9.cn/en/article/three-sandbox-failures-that-let-ai-agents-escape-1548fc.html

相关文章