# 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),
}
