Two bwrap failures found by live host tasks, both in the AppArmor profile (not in codex or the worker): 1. deny capability sys_admin (etc.) breaks bwrap at userns creation: codex-host with the deny list failed, the same profile with capability, allowed it. codexsvc holds no host capabilities, so the allow only covers fake-root caps inside bwrap's own user namespace (scoped, harmless to the host); network/ptrace/file denies still hold independently. 2. bwrap brings up loopback in its new net namespace via a NETLINK_ROUTE socket: without network netlink raw, codex workspace-write fails with 'Failed to bind NETLINK_ROUTE socket' and no tool runs. And a bare 'deny network raw' overrides the netlink allow (deny wins over allow), so the raw deny must name inet/inet6 explicitly (verified by an isolated netlink bind test under each profile variant). README correction in the same change: codex workspace-write does NOT isolate /tmp -- an agent /tmp write lands on host /tmp (verified). The verified boundary is: own worktree writable, everything else read-only, /tmp shared.
88 lines
3.3 KiB
Plaintext
88 lines
3.3 KiB
Plaintext
# AppArmor profile for the HOST-LEVEL sandbox worker (codex-host-worker).
|
|
#
|
|
# This is docker-codex (outer hardening) PLUS what bwrap needs:
|
|
# - userns: allow creating user namespaces (bwrap's inner sandbox)
|
|
# - mount, umount, pivot_root: allow (bwrap mounts proc/tmpfs/binds
|
|
# and pivot_roots into the new root)
|
|
# It KEEPS docker-codex's denies: raw network, ptrace, dangerous
|
|
# capabilities, writes to /proc/sys, /sys, /proc/keys.
|
|
#
|
|
# Why a separate profile: docker-codex deliberately denies mount
|
|
# (containers must never mount; inner sandbox stays off there by
|
|
# choice). The host worker NEEDS mount+userns because its tasks
|
|
# run codex workspace-write (bwrap) directly on the host. The two
|
|
# profiles must not be confused: a task container under codex-host
|
|
# could bwrap (defeats the Docker-only design), and a host worker
|
|
# under docker-codex cannot bwrap at all (mount denied).
|
|
#
|
|
# Security note (honest): allowing mount+userns for the host worker
|
|
# means the WORKER process (trusted, codexsvc) can mount. The AGENT
|
|
# inside a task cannot mount on the host: its mounts happen inside
|
|
# bwrap's new mount namespace (isolated), and bwrap's pid namespace
|
|
# hides other tasks' processes. The agent's writable surface is its
|
|
# own worktree (+ a private tmpfs /tmp that never reaches host /tmp).
|
|
#
|
|
# Install: cp to /etc/apparmor.d/codex-host, then
|
|
# apparmor_parser -r /etc/apparmor.d/codex-host
|
|
# Use: aa-exec -p codex-host -- python3 -m cloud.worker ...
|
|
# (see deploy/host-worker/codex-host-worker.service)
|
|
# Verify: cat /proc/<worker-pid>/attr/current shows
|
|
# "codex-host (enforce)".
|
|
|
|
#include <tunables/global>
|
|
|
|
profile codex-host flags=(attach_disconnected,mediate_deleted) {
|
|
#include <abstractions/base>
|
|
|
|
network inet stream,
|
|
network inet dgram,
|
|
network inet6 stream,
|
|
network inet6 dgram,
|
|
# bwrap brings up loopback in its new network namespace via a
|
|
# NETLINK_ROUTE socket (verified 2026-10-26: without this,
|
|
# codex workspace-write fails with "bwrap: loopback: Failed
|
|
# to bind NETLINK_ROUTE socket: Permission denied" and no tool
|
|
# runs at all). CAREFUL: a bare "deny network raw" would
|
|
# override this allow (deny wins over allow in AppArmor) and
|
|
# break netlink too -- verified by isolated netlink bind test.
|
|
# So the raw deny must name inet/inet6 explicitly, leaving
|
|
# netlink raw allowed.
|
|
network netlink raw,
|
|
deny network inet raw,
|
|
deny network inet6 raw,
|
|
|
|
# Capabilities: bwrap NEEDS userns-scoped capabilities (verified
|
|
# 2026-10-26: codex-host with deny capability sys_admin etc.
|
|
# made bwrap fail at userns creation, while the same profile
|
|
# with capability, allowed it). This is safe for an unprivileged
|
|
# user: codexsvc holds NO host capabilities, so the only caps
|
|
# this allows are fake-root caps inside bwrap's own user
|
|
# namespace (scoped to that namespace, harmless to the host).
|
|
# The profile's other denies still hold independently: raw
|
|
# network is gated by network rules, ptrace by ptrace rules,
|
|
# /proc/sys writes by file rules -- allowing caps does not
|
|
# void any of them.
|
|
capability,
|
|
|
|
# bwrap needs these (docker-codex denies them).
|
|
userns,
|
|
mount,
|
|
umount,
|
|
pivot_root,
|
|
deny ptrace,
|
|
|
|
file,
|
|
deny @{PROC}/sys/** w,
|
|
deny @{PROC}/sysrq-trigger rw,
|
|
deny @{PROC}/keys/** rw,
|
|
deny @{PROC}/kcore rw,
|
|
deny /sys/** w,
|
|
deny /etc/shadow rw,
|
|
deny /etc/gshadow rw,
|
|
|
|
signal,
|
|
unix,
|
|
dbus,
|
|
ptrace (readby),
|
|
}
|