docs: handleiding maximale isolatie op Linux (eigen kernel) (#44) - #99
Draft
ericwout-overheid wants to merge 1 commit into
Draft
docs: handleiding maximale isolatie op Linux (eigen kernel) (#44)#99ericwout-overheid wants to merge 1 commit into
ericwout-overheid wants to merge 1 commit into
Conversation
De podman-opzet uit #46 deelt de host-kernel, dus een kernel-escape blijft een restrisico. Dit document beschrijft hoe je die laag óók sluit door de sandbox op Linux in een VM te draaien (podman in Lima of Multipass), met Kata en gVisor als alternatieven. Losgetrokken uit #46 omdat het ongetest is en een extra stap beschrijft die niemand nodig heeft om podman te gebruiken. Die PR hoeft er niet op te wachten. Niet geverifieerd: geen van de beschreven routes is opgezet of gedraaid. Behandel dit als een startpunt voor iemand die het uitzoekt, niet als een werkend recept.
ericwout-overheid
requested review from
jonrust-minbzk,
loek-rijksoverheid and
mreuvekamp
as code owners
August 5, 2026 07:25
ericwout-overheid
added a commit
that referenced
this pull request
Aug 5, 2026
#44) Vier punten uit de tweede review op #46: - Het aa-complain/dmesg/aa-enforce-recept stond in de kop van het AppArmor-profiel. Dat beschrijft wat je doet als het profiel in de weg zit, niet wat het afdwingt. Verhuisd naar ADR 0001 §2.5.4 "Als het profiel te strak blijkt". In het profiel blijft staan dat je niet mag terugvallen op flags=(unconfined) — dat is een eigenschap van het profiel zelf, en staat op de plek waar iemand die vlag zou zetten. - De drie installatievarianten (Linux, podman machine, Rancher) zijn subkoppen onder één "Installeren" geworden. Het waren losse ##-secties op hetzelfde niveau als "Multi-uid" en "Fallbacks", terwijl het alternatieven van dezelfde stap zijn. De wegwijzertabel linkt er nu naartoe. - Het uitstel van de eigen-kernel-route verwees nergens naar. Beide plekken (§4.5 en §5) wijzen nu naar #99, met de kanttekening dat die routes ongetest zijn. - OPEN_HTTPS had in .env.sample geen enkele toelichting en ALLOWED_DOMAINS alleen "Alleen nodig als OPEN_HTTPS niet true is". Net de verkeerde om kaal te laten: bij de default true is de allowlist een no-op, dus wie dat mist denkt beschermd te zijn terwijl al het uitgaand HTTPS openstaat. Beide uitgeschreven, inclusief dat de allowlist vóór de privilege-drop gelezen wordt en een wijziging dus een recreate vereist. Geverifieerd tegen init-firewall.sh:84 dat het allowlist-blok inderdaad alleen draait bij OPEN_HTTPS != true, en tegen regel 71 dat het script terugvalt op false als de variabele ontbreekt — dat laatste staat er nu bij, want het betekent dat een ontbrekende regel de sandbox stránger maakt. Testresultaten van deze PR op een gehardende Tuxedo-host: alle stappen groen, inclusief multi-uid (twee uid-mappings, PostgresSmokeTest) en het hardeningsprotocol.
ericwout-overheid
marked this pull request as draft
August 5, 2026 09:03
ericwout-overheid
added a commit
that referenced
this pull request
Aug 6, 2026
…rdening (#44) (#46) * docs(maven-podman): ontwerp rootless Podman-in-Docker PoC (#44) Maven+Testcontainers in de sandbox via nested rootless Podman i.p.v. de host-agent-bridge. Sluit de container->host code-execution van #44: geen bridge, geen Docker-socket, geen --privileged. Cross-platform, lichter dan sysbox/microVM. PoC moet seccomp/fuse/subuid/egress uitwijzen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): implementatieplan PoC (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sandbox): rootless Podman-in-Docker PoC voor Maven+Testcontainers (#44) INSTALL_PODMAN build-ARG (default false) installeert podman + fuse-overlayfs + uidmap + passt/slirp4netns en zet subuid/subgid + rootless storage.conf. compose.override.podman.yml.example levert /dev/fuse + seccomp-stand. PoC-map met sample Testcontainers-project, smoke-test.sh en README (run-stappen, ALLOWED_DOMAINS-egress, fallbacks). Niet live testbaar in deze omgeving (geen runtime); gebruiker draait smoke-test op de host. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): apparmor=unconfined voor rootless podman (#44) newuidmap faalde met "write to uid_map failed: Operation not permitted". Root cause: AppArmor docker-default-profiel medieert writes naar /proc/<pid>/uid_map op Debian/Ubuntu-hosts. Userns (initieel), CAP_SETUID/SETGID (bounding set), setuid-root newuidmap en NoNewPrivs=0 waren allemaal in orde; AppArmor was de enige overgebleven blokkade. seccomp stond al op unconfined, label=disable dekt alleen SELinux. README-fallbacktabel bijgewerkt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(maven-podman): single-uid + AppArmor-userns-profiel voor gehardende hosts (#44) PoC wees uit dat naïeve multi-uid rootless podman niet werkt op gehardende Ubuntu/Tuxedo (apparmor_restrict_unprivileged_userns=1): host blokkeert élke userns-map, en de privileged newuidmap-range faalt apart. Herziene aanpak: - Single-uid modus: geen subuid/subgid voor claude -> podman mapt alleen eigen uid als root, gebruikt newuidmap niet. ignore_chown_errors=true in storage.conf. - Custom AppArmor-profiel (flags=(unconfined) { userns, }) + setup-host.sh laadt het op de host. Restrictie blijft systeembreed aan; alleen deze container krijgt userns. Override verwijst naar het profiel i.p.v. apparmor=unconfined. - Spec uitgebreid met PoC-bevindingen, per-setup matrix en security-trade-off. - README herzien: per-setup flow, setup-host.sh, fallbacks. Nog te verifieren op een gehardende host (kan niet in deze werkomgeving). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): /dev/net/tun device + storage.conf via entrypoint (#44) PoC-voortgang op gehardende host: AppArmor-userns-profiel lost userns op met sysctl=1 (self-map OK), single-uid podman info werkt, en met ignore_chown_errors slaagt image-extractie. Resterend: pasta-netwerk faalde op ontbrekend /dev/net/tun. - compose-override: /dev/net/tun device erbij (NET_ADMIN had de sandbox al). - entrypoint: schrijft rootless storage.conf (single-uid + fuse-overlayfs + ignore_chown_errors) idempotent bij start, want een bestaand named volume schaduwt de baked-in image-versie. - README-fallbacks: pasta/tun + storage.conf-shadow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): leeg default_sysctls (ping_group_range RO in container) (#44) Nested container start faalde op crun "open /proc/sys/net/ipv4/ping_group_range: Read-only file system": podman zet die sysctl default, maar /proc/sys is RO in de outer container. Entrypoint schrijft nu ook containers.conf met default_sysctls=[] zodat crun geen sysctls probeert te zetten. README-fallback bijgewerkt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): systempaths=unconfined voor nested proc-mount (#44) Nested container start faalde op crun "mount proc: Operation not permitted": Docker maskeert /proc-paden, waardoor de kernel (mount_too_revealing) een nieuwe procfs in de geneste mount-namespace weigert. systempaths=unconfined heft de masked/RO /proc-paden op de outer container op. Peelt outer-sandbox-hardening verder af — security-trade-off staat in de override-comment en spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): mkdir socket-dir in smoke-test (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): SmokeIT -> SmokeTest zodat surefire hem draait (#44) mvn test (surefire) sloeg *IT stil over -> groene build zonder dat de Testcontainers-test ooit liep. Hernoemd naar *Test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): netavark firewall_driver=iptables (geen nft nodig) (#44) PoC GESLAAGD (geverifieerd: Tests run: 1, Failures: 0, Errors: 0 — Testcontainers alpine startte via podman). Testcontainers maakt een bridge-netwerk → netavark riep default `nft` aan, niet in de image. iptables-driver gebruikt iptables-nft (al aanwezig). Entrypoint zet dit nu in containers.conf. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(maven-podman): negeer sample target/ (mvn build-output) (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): PoC geslaagd — werkende config + security-balans (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): volledige Testcontainers-build bevestigd (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(maven-podman): TESTCONTAINERS_HOST_OVERRIDE=localhost + echte-build-bevestiging (#44) Andere sessie draaide een echt Quarkus-project (Redis-stack Dev-Services): 289+46 tests groen via podman in de sandbox. Gap: containers met port-wait (Postgres/Redis) time-outten omdat Testcontainers de netavark bridge-gateway (10.88.0.1) als host resolvet terwijl rootless podman op localhost publisht. Fix: TESTCONTAINERS_HOST_OVERRIDE=localhost in smoke-test + README + spec. De alpine-GenericContainer miste dit (geen port-wait). Host-agent hiermee niet meer nodig in de sandbox (blijft fallback). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(maven-podman): setup-host.sh robuuster (modprobe tun/fuse, abi-fallback) (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): seccomp-verfijning geprobeerd — unconfined blijkt nodig (#44) Geverifieerd: Docker-default seccomp faalt op 'cannot clone: Operation not permitted' (podman re-exec gebruikt clone(CLONE_NEW*)). seccomp=unconfined is dus nodig, geen gold-plating. Spec: hardening-verfijning-sectie met het tailored-seccomp-vervolgpad + waarom apparmor/systempaths nauwelijks te versmallen zijn. De veiligheid zit in de scoping, niet in het wegpoetsen van de inherente relaxaties. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(maven-podman): tailored seccomp-blocklist i.p.v. unconfined (#44) defaultAction=ALLOW (zodat podman's clone/unshare/mount/setns werken — Docker-default brak hierop) + ERRNO-deny op de gevaarlijke kernel-escape-syscalls die Docker-default ook blokkeert (module-load, kexec, reboot, iopl/ioperm, swap, klok-zetten, bpf, perf_event_open, open_by_handle_at, acct, _sysctl, vm86). Strikt veiliger dan unconfined zonder podman te breken. Override verwijst naar seccomp/podman-sandbox.json (pad relatief t.o.v. compose-bestand). Nog te verifieren op de host (recreate + smoke). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): tailored seccomp-profiel geverifieerd (Seccomp:2 + smoke groen) (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): ADR 0001 + host-agent als fallback gedocumenteerd (#44) #44-DoD: afweging vastgelegd als ADR (docs/adr/0001-...): podman-in-docker als voorkeur, host-agent als fallback, security-balans, goedkope hardening, C/D uitgesteld. maven-mcp-agent.md verwijst nu naar het podman-alternatief en documenteert de host-agent expliciet als fallback + de goedkope hardening (dedicated least-priv user, projecten buiten gedeelde map, bind 127.0.0.1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): herschrijf naar as-is oplossing i.p.v. PoC; dir poc-podman→podman (#44) Niet langer een PoC maar een voorstel tot echt gebruik dat de Maven host-agent beoogt te vervangen (PR-review + collega-tests; bij succes kan de host-agent weg, mits objectief beter). Teksten ontdaan van PoC-framing in README, spec, ADR, maven-mcp-agent, override- en script-comments. Map host-agents/maven/poc-podman → podman (+ seccomp-pad in override). smoke-test eindmelding "PoC GESLAAGD" → "OK — Testcontainers werkt". Plan-doc blijft als historisch record. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): corrigeer zwakke host-agent-rechtvaardiging (#44) De host-agent is een DEKKINGS-fallback (hosts waar podman-in-docker nog niet kan + Mac/Win te verifieren), geen security-upgrade. De eerdere claim "voor wie de outer-relaxaties niet wil" klopt niet: die relaxaties verbreden het kernel-oppervlak van de container (escape vereist nog een exploit), terwijl de host-agent code direct op de host draait — voor een op container-escape beduchte gebruiker juist zwakker. ADR + maven-mcp-agent bijgewerkt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(maven-podman): storage-driver via .env, default vfs i.p.v. fuse (#44) Issue-comment merkt terecht op: /dev/fuse is een (klein) kernel-risico en hoort pas toegevoegd bij traagheid, niet als default. Tot nu toe was fuse-overlayfs + /dev/fuse de default. Nu: - Default storage = vfs (geverifieerd: single-uid + [storage.options.vfs] ignore_chown_errors=true, smoke groen, geen /dev/fuse). - Configureerbaar via .env: PODMAN_STORAGE_DRIVER=vfs|overlay + PODMAN_FUSE_DEVICE=/dev/null|/dev/fuse. Entrypoint genereert storage.conf uit de driver; valt terug op vfs + waarschuwing als overlay gevraagd is maar /dev/fuse ontbreekt (footgun-guard). - compose-override: /dev/fuse vervangen door ${PODMAN_FUSE_DEVICE:-/dev/null} (no-op default) + PODMAN_STORAGE_DRIVER env-passthrough. - Dockerfile baked storage.conf → vfs; fuse-overlayfs blijft geïnstalleerd zodat opt-in geen rebuild vergt (inert zonder /dev/fuse). - Spec/ADR/README/.env.sample bijgewerkt: vfs default, fuse opt-in + security-noot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): review-ronde 1 — Dockerfile-comments uit RUN, smoke sourcet sdkman (#44) - Dockerfile: inline #-comments uit de \-gecontinueerde case-RUN gehaald (werkte op buildkit, maar fragiel op classic builder) → naar comment-blok erboven. - smoke-test.sh: sourcet nu zelf sdkman + faalt met duidelijke melding als mvn ontbreekt, zodat het standalone werkt (niet alleen via de README-wrapper). Standalone geverifieerd: smoke groen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(maven-podman): review-ronde 2 (security) — seccomp-blocklist uitgebreid (#44) Security-review: de blocklist miste escape-relevante, cap-gated syscalls die Docker-default wél blokkeert en die podman/JVM-Testcontainers niet nodig hebben. Toegevoegd: userfaultfd, io_uring_{setup,enter,register}, NUMA (mbind/set_mempolicy/migrate_pages/move_pages), process_vm_{readv,writev}, process_madvise, fanotify_init, kcmp, pidfd_getfd, en module-load-rest (create_module/query_module/get_kernel_syms). ptrace bewust NIET geblokkeerd (Docker-default laat het toe). Docs: vergelijkingstabel-rij geactualiseerd, seccomp-omschrijving + ptrace-noot, SELinux label=disable in de security-balans. Seccomp is create-time → uitgebreide blocklist nog op een recreate te bevestigen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): review-ronde 3 (consistentie) — spec Artefacten-tabel + .example (#44) Drift gevonden in de spec "Artefacten"-tabel (stond buiten de ontwerp-disclaimer): beschreef nog subuid baked + fuse-overlayfs storage + devices:[/dev/fuse]. Nu in lijn met de werkelijke config (single-uid, vfs-default, /dev/net/tun + ${PODMAN_FUSE_DEVICE}). ADR/spec verwijzen nu naar compose.override.podman.yml.example (met .example) i.p.v. de losse naam. Rename poc-podman→podman volledig schoon, geen PoC-refs in living docs (alleen in het historische plan-doc), links resolven. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(maven-podman): drop .example van podman-override (#44) compose.override.podman.yml.example → compose.override.podman.yml. Het bestand wordt direct met -f meegegeven (geen kopieer-template), dus .example was misleidend. Wordt niet auto-geladen (Compose auto-merget alleen compose.override.yml), dus blijft opt-in. Refs in README/spec/ADR/plan/Dockerfile/.env.sample/entrypoint bijgewerkt; header-noot "kopieer naar compose.override.yml" vervangen door expliciete -f-instructie + waarschuwing tegen hernoemen. Linux-override blijft .example (dat is wél een kopieer-template). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(podman): macOS Podman-machine support voor Testcontainers-sandbox Bevestigd op een Mac met Podman-machine (applehv → Fedora CoreOS): rootless podman-in-podman draait en de Maven+Testcontainers-smoke-test slaagt end-to-end. - Nieuwe compose.override.podman-macos.yml: apparmor=unconfined (Fedora CoreOS heeft geen AppArmor/userns-hardening), label=disable voor SELinux, absoluut seccomp-pad via ${PWD} (podman leest het profiel client-side). - Hernoem compose.override.podman.yml -> compose.override.podman-linux.yml en werk alle verwijzingen bij (README, setup-host.sh, entrypoint.sh, ADR, spec, plan). - README: macOS-sectie met de drie afwijkingen (podman-compose i.p.v. podman compose, BUILDAH_FORMAT=docker, absoluut seccomp-pad), fallback- rijen voor de macOS-fouten, en per-setup matrix bijgewerkt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(maven-podman): drop PODMAN_FUSE_DEVICE, /dev/fuse als uncomment-opt-in (#44) PODMAN_FUSE_DEVICE was redundant naast PODMAN_STORAGE_DRIVER. Compose kan een devices-entry niet uit de waarde van een var afleiden (geen conditionals in interpolatie), dus i.p.v. een tweede env-var is /dev/fuse nu een uitgecommentarieerde regel in de overrides die je uncomment voor overlay. PODMAN_STORAGE_DRIVER blijft de enige .env-knop; entrypoint waarschuwt + valt terug op vfs als overlay gekozen is zonder het device (geen broken start). Linux- + macOS-override, entrypoint, .env.sample, README en spec bijgewerkt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): Kata-handleiding voor maximale isolatie op Linux (#44) Nieuwe gids docs/kata-linux-maximale-isolatie.md: Kata Containers (eigen guest-kernel per container) als de kernel-escape-laag op Linux-native — de plek waar die grens by default ontbreekt. Inclusief: voorwaarden (KVM/nested virt), eerlijke noot over Docker-Engine↔Kata-integratiefrictie (shim-v2 vs containerd/ nerdctl), interactie met de podman-hardening (host-AppArmor-profiel waarschijnlijk overbodig onder Kata), en caveats. Mac/Windows: kernel-grens al aanwezig (VM via Docker Desktop/Rancher/podman machine) → Kata niet nodig, met WSL2/projects-mount kanttekeningen. Niet getest in deze omgeving; gemarkeerd als te verifiëren. Gelinkt vanuit ADR (Optie D) + podman-README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(maven-podman): maximale-isolatie-gids omgebogen naar podman-in-een-VM (#44) Hernoemd kata-linux-maximale-isolatie.md → maximale-isolatie-linux.md. Aanbevolen route is nu de sandbox in een lichtgewicht VM draaien (podman in Lima/Multipass): echte kernel-grens, podman+Testcontainers ongewijzigd, snapshot/wegwerp = makkelijke recovery, en de in-VM-hardening mag simpeler (VM is de grens). Kata + gVisor als alternatieven met waarom-niet. Mac/Windows: kernel-grens al aanwezig. Plus de shared-mount-spanning (smal+RO+geen-autorun vs git-als-grens). ADR + podman README-links bijgewerkt. Niet getest hier; gemarkeerd als te verifiëren. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(podman): entrypoint-guard + docs voor vergeten compose-override Nested/detached podman faalde omdat de sandbox met alleen compose.yml startte, zonder compose.override.podman-*.yml: geen /dev/net/tun (pasta faalt) en geen systempaths=unconfined (proc-mount geweigerd). De INSTALL_PODMAN build-arg en de runtime-override waren ontkoppeld en niets verbond ze, dus faalde het pas veel later en stil. - entrypoint.sh: waarschuw bij opstart als podman aanwezig is maar /dev/net/tun ontbreekt (betrouwbare tell dat de override ontbreekt) - README.md + opstarten-en-afsluiten.md: kruisverwijzing bij INSTALL_PODMAN=true naar de override + per-OS-matrix Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(maven-podman): multi-uid rootless podman als opt-in (#44) Single-uid kan geen images draaien die naar een tweede uid chownen: postgres, mysql en mariadb falen met `chown: ...: Invalid argument`, waarna Testcontainers alleen "Container did not start correctly" meldt. Multi-uid lost dat op, maar vereist CAP_SYS_ADMIN in de outer container. De kernel laat een uid_map-write toe aan wie die capability heeft in de doel-namespace of er de eigenaar van is (ns->owner == euid). newuidmap is setuid-root, dus euid 0 terwijl de namespace van uid 1000 is: de eigenaars-route valt weg en de capability is vereist. Docker laat CAP_SYS_ADMIN per default uit de bounding set, dus setuid-root kan hem niet krijgen. Daarom opt-in en niet default: `claude` krijgt de capability niet rechtstreeks in handen (CapEff=0), maar de impact van een root-escalatie in de container groeit wel. Op Mac/Windows zit er nog een VM-kernelgrens onder, op Linux met bare Docker niet. - PODMAN_MULTIUID build-arg zet de subuid-range in de image - compose.override.podman-multiuid.yml geeft de capability, te stapelen op de platform-override - entrypoint waarschuwt bij multi-uid zonder CAP_SYS_ADMIN, zodat je het bij de start ziet i.p.v. halverwege een build - ignore_chown_errors alleen nog in single-uid; in multi-uid zou het een echte chown-fout maskeren Meegenomen omdat de regel toch herschreven werd: de `|| true` stond aan het eind van de &&-keten en maakte daarmee ook een falende `apt-get install podman` groen. Nu gescopet tot alleen de sed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(maven-podman): PostgresSmokeTest voor de multi-uid modus (#44) De bestaande alpine-smoke chownt niet en wacht niet op een poort, en dekt dus noch de single-uid-beperking noch het TESTCONTAINERS_HOST_OVERRIDE-probleem af. PostgresSmokeTest doet beide: PostgreSQLContainer plus een echte JDBC-query over de published port. smoke-test.sh leest /etc/subuid en geeft -Dpodman.multiuid door, zodat de test in single-uid netjes wordt overgeslagen in plaats van te falen op iets dat daar niet kan werken. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(maven-podman): newuidmap-EPERM herleid, Rancher/macOS bevestigd (#44) Blokkade #2 in de spec stond genoteerd als "oorzaak niet sluitend herleid". Dat is nu de ontbrekende CAP_SYS_ADMIN, met de meettabel erbij. Let op de probe die misleidend slaagt: newuidmap als root op root's eigen userns gaat via de eigenaars-route, niet via de capability. Dat verklaart waarschijnlijk waarom capabilities bij de oorspronkelijke eliminatie als uitgesloten genoteerd stond. Rancher Desktop op macOS is bevestigd: Alpine-VM zonder AppArmor of SELinux, geen userns-hardening, /dev/net/tun en /dev/fuse aanwezig, dus geen enkele host-instelling nodig. Rancher's eigen docker compose stuurt het seccomp-profiel inline mee en dockerd accepteert dat, waar podman's API het weigert met "file name too long". Verder de single-uid-beperking expliciet gemaakt in de per-setup matrix en twee rijen aan de fallback-tabel toegevoegd. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(maven-podman): multi-uid bevestigd op macOS podman machine (#44) Postgres via Testcontainers draait nu ook groen op een applehv podman-machine: PODMAN_MULTIUID=true plus de multiuid-override gestapeld op de macOS-override, via podman-compose. De machine is rootful, dus cap_add SYS_ADMIN landt echt in de bounding set — op een rootless machine is dat niet getest, dat staat er expliciet bij. Tweede vondst onderweg: een met de hand aangepaste containers.conf in het claude-home volume brak podman met een pause.pid-fout. De entrypoint schrijft dat bestand alleen als het ontbreekt, dus zulke drift blijft stil staan en schaduwt de bedoelde config. De spec claimde "idempotent geschreven", wat het niet is; dat is gecorrigeerd, met een fallback-rij in de README en de vraag of storage.conf en containers.conf gelijk getrokken moeten worden als openstaand punt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(maven-podman): podman-socket bij container-start i.p.v. per build (#44) Testcontainers viel om met "chown: Invalid argument" op postgres terwijl subuid-range en CAP_SYS_ADMIN gewoon aanwezig waren. Oorzaak: het eerste podman-commando maakt het pause-proces aan dat de user-namespace vastlegt, en alles daarna joint dat proces. Kwam dat single-uid op, dan bleef de hele container single-uid — ook nadat de oorzaak weg was. Eén bewezen trigger is no_new_privs: newuidmap is setuid-root en wint onder die vlag geen privileges, dus de range-write faalt met dezelfde EPERM als bij een ontbrekende capability. `setpriv --no-new-privs podman info` reproduceert het, en met een gezond pause-proces geeft diezelfde aanroep wél de volledige mapping — joinen vergt de capability niet. Daarom zet de entrypoint de socket nu bij de start op, in een schone omgeving voor er iets anders draait, en waarschuwt hij als de mapping dan tóch single-uid is. De client-env staat op container-niveau in de podman-overrides en niet in een shell-profiel: /etc/profile.d geldt alleen voor login-shells en ~/.bashrc alleen voor interactieve, dus een build die via `bash -c` start kreeg uit geen van beide iets mee. De smoke-test start geen eigen service meer als er al een draait. Dat was geen cosmetica: een tweede service op hetzelfde pad kan niet binden en verwijdert bij het afsluiten de socket van de eerste, waarna Testcontainers terugvalt op /var/run/docker.sock. Hij faalt nu ook hard als multi-uid aanstaat maar podman één uid mapt. Geverifieerd op een verse container (macOS podman machine): smoke-test groen incl. PostgresSmokeTest, en 359 tests groen in een Quarkus-module met Dev Services + Flyway tegen postgres:18, zonder handmatige env. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): ontwerp voor bridge-verwijdering en hardening (#44) Twee gestapelde PR's. PR A verwijdert de Maven host-agent en geeft de podman-in-sandbox-set een eigen plek onder claude-sandbox/podman/. PR B sluit de container-escape uit de security-review op #76: root-entrypoint met privilege-drop in plaats van de NOPASSWD SETENV-sudoers-regel, een AppArmor-profiel dat daadwerkelijk mediateert, en een ruimere seccomp-blocklist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): implementatieplan voor bridge-verwijdering en hardening (#44) Zestien taken over twee gestapelde PR's, met per taak een controle die eerst faalt. Legt onder meer vast dat de omschakeling naar USER root de gedocumenteerde exec-commando's raakt, en waarom --no-new-privs bewust ontbreekt bij de privilege-drop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(sandbox): podman-set naar claude-sandbox/podman/ (#44) De podman-in-sandbox-set stond in de boom van de Maven host-agent, waardoor wijzigingen aan podman lazen als wijzigingen aan die agent. Verplaatst naar een eigen top-level map, met de seccomp-paden in beide overrides mee. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(sandbox): Maven host-agent verwijderd (#44) De host-agent draaide mvn op de host als de host-user, met pom.xml en mvnw uit de projects-map die de sandbox kan schrijven — een container→host code-execution-bridge. Podman-in-de-sandbox vervangt hem, dus hij kan weg. Verwijdert ook requirements.txt (509 regels). Dependabot heeft geen pip-entry in de config, maar security-updates scannen manifests repo-breed; die alert-stroom vervalt hiermee. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): podman-gids als enige ingang, verwijzingen rechtgetrokken (#44) Alle paden naar de oude host-agents-boom bijgewerkt. De podman-README is nu de enige plek met bedieningsstappen; de rest verwijst ernaar. Voegt een expliciete lijst ondersteunde platforms toe, inclusief wat niet bevestigd is nu er geen terugvaloptie meer is. De per-setup-matrix en de sectie Openstaand herhaalden die status en verwijzen er nu naar. De twee 2026-06-10-documenten zijn historisch: daarin zijn alleen paden bijgewerkt, geen inhoud. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(adr): 0001 naar Geaccepteerd, host-agent-spoor eruit (#44) Het ADR was voorwaardelijk geformuleerd: de host-agent zou vervallen zodra de podman-opzet breed bevestigd was. Dat is gebeurd, dus dit is de afronding die het document zelf aankondigde. De sectie "Goedkope hardening host-agent" is weg — die ging volledig over een component die niet meer bestaat. De security-balans blijft in deze PR staan zoals hij was; die wordt in de hardening-PR bijgewerkt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): reviewfixes op comments en verwijzingen (#44) Uit de reviewronde op deze PR: - Dockerfile-comment: "i.p.v. via de host" (route bestaat niet meer) weg, en het niet-bestaande compose.override.podman.yml vervangen door de echte -linux/-macos-bestanden. - .gitignore: dode host-agents/**-regels weg. - De seccomp-paduitleg die ik eerder toevoegde was fout: Compose resolvet een relatief pad tegen de projectdirectory (map van het eerste -f-bestand), niet tegen de werkdirectory. Rechtgezet in beide overrides én in de README-tabel, die het tegenovergestelde beweerde. - macos-override: het voorbeeldcommando gebruikte `podman compose` (delegeert naar docker-compose, seccomp-inline faalt) met een niet-bestaande bestandsnaam; nu podman-compose met het juiste pad. ${PWD:?} faalt hard als PWD niet gezet is, i.p.v. een pad vanaf filesystem-root te vormen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(sandbox): host.docker.internal-route verwijderd (#44) Deze route — de compose.override.linux.yml.example, het extra_hosts-comment in compose.yml en de OUTPUT-ACCEPT-regel in init-firewall.sh — bestond uitsluitend om de container de Maven host-agent op poort 7777 te laten bereiken. Die agent is verwijderd, dus de route is een uitzondering in een default-DROP allowlist zonder consument. De firewall-comment noodde bovendien nog uit tot "MCP-servers op de host". De HOST_NETWORK-ACCEPT (regel eronder) blijft; die dekt Docker DNS-forwarding en IDE-koppelingen binnen het bridge-netwerk. Alleen het VM-interne host.docker.internal-IP buiten dat netwerk (Docker Desktop/Rancher) valt weg. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(adr): capability-claim gecorrigeerd, verworpen alternatief bewaard (#44) Reviewfixes op ADR 0001 nu het op Geaccepteerd staat: - "Géén CAP_SYS_ADMIN" klopte niet meer: de multi-uid opt-in uit #76 zet hem in de bounding set. Gecorrigeerd, met de nuance dat claude CapEff=0 heeft. De volledige herschrijving van de security-balans volgt in de hardening-PR. - Sectie "Overwogen en verworpen" toegevoegd: de host-agent en de Docker-socket-runner, met de reden waarom de host-agent ook geen veiliger alternatief is. Die redenering was bij het schrappen van spoor 2 verdwenen. - "Optie C/D" verwees naar een A/B die uit het document geknipt was; hernoemd naar "sysbox/microVM". - De consequentie voor Windows/WSL2-werkplekken expliciet benoemd en de "geen gebruikers"-onderbouwing gekwalificeerd als teamspecifiek. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): waarschuwing voor host-side builds + startcommando's ontdubbeld (#44) - README:35-37 raadde aan de projects-map "buiten docker te bouwen, testen of opstarten" — dat is de #44-aanval zonder agent: een rogue pom.xml/mvnw/ Makefile/git-hook draait dan met host-rechten. Met het verwijderen van de host-agent verdween ook de tegenmaatregel (die stond in maven-mcp-agent.md en de ADR-hardeningsectie), terwijl het risico aan de gedeelde mount hangt. Waarschuwing teruggezet op de plek waar de mount beschreven wordt. - Het macOS-startcommando stond woordelijk op drie plekken en dreef al uiteen (mist BUILDAH_FORMAT/--build). De kopieën in README en opstarten-en-afsluiten wijzen nu naar podman/README.md i.p.v. een eigen, driftend commando. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(podman): migratieblok, platformtabellen samengevoegd, cwd + sudo expliciet (#44) - "Migratie vanaf de host-agent" toegevoegd: git rm stopt geen draaiende run.sh (auth-loze listener op 0.0.0.0:7777) of MCP-registratie die in het claude-home volume een rebuild overleeft. Drie opruimstappen. - De status-tabel en de per-setup-matrix somden dezelfde platforms met verschillende labels op; samengevoegd tot één tabel Platform | Status | Wat je nodig hebt, met de niet-ondersteunde platforms als expliciete rij. - Eén regel bovenaan "Stappen" dat alles vanuit claude-sandbox/ draait (stap 2 ging al uit van een cd die pas in stap 3 stond). - Stap 2 vermeldt nu dat setup-host.sh sudo op de host vereist — op een beheerde werkplek zonder admin loop je daar anders vast. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: historische banner op de 06-10-documenten (#44) Die twee documenten worden vanuit de compose-overrides, de Dockerfile en de podman-README aangehaald als ontwerpbron, maar hun statusregel zei nog "in review" en één regel beschreef de host-agent nog als beschikbare fallback — in tegenspraak met de README ("geen terugvaloptie meer"). Een banner bovenaan markeert ze als historisch en de fallback-bijzin is geschrapt. De inhoud blijft verder bevroren; padverwijzingen als maven_agent.py beschrijven bewust de toenmalige situatie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf(sandbox): .dockerignore om de build-context klein te houden (#44) De build-context is claude-sandbox/, maar de Dockerfile COPY't alleen vendor/, skills/, init-firewall.sh en entrypoint.sh. Zonder .dockerignore werd bij elke --build de hele boom ingepakt en naar de daemon gestuurd, inclusief projects/ (de default mountlocatie met gebruikerscheckouts) en podman/sample/target/. Sluit die uit; de image blijft identiek. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(sandbox): root-entrypoint met privilege-drop i.p.v. sudoers SETENV (#44) De firewall liep via een NOPASSWD-sudoers-regel met de SETENV-tag. Die tag laat BASH_ENV de env_reset van sudo overleven, waarmee uid 1000 in één commando container-root werd — en zonder userns-remap is dat host-root-uid. Dezelfde regel maakte de egress-allowlist self-service: claude kon init-firewall.sh opnieuw draaien met OPEN_HTTPS=true. De container start nu als root, zet de firewall op en dropt met setpriv naar claude. sudo en de sudoers-regel zijn weg. Beide entrypoints zijn root-eigendom, want een door claude schrijfbaar bestand dat als root start zou het gat gewoon verplaatsen. Bewust geen --no-new-privs: setuid-root newuidmap loopt daarop stuk (gemeten in #76), waarna multi-uid podman degradeert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): exec-commando's met -u claude (#44) De container start sinds de vorige commit als root (om de firewall op te zetten) en dropt daarna naar claude. Daardoor geeft `docker compose exec claude …` en `podman exec claude-sandbox …` zonder -u een root-shell. Alle gedocumenteerde exec-commando's gaan naar `-u claude`, met een noot dat in `compose exec -u claude claude` de eerste claude de user en de tweede de service is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(podman): AppArmor-profiel afgeleid van docker-default (#44) Het profiel was flags=(unconfined) met één userns-regel: de MAC-laag deed niets. Samen met systempaths=unconfined en het ontbreken van userns-remap maakte dat /proc/sys/kernel/core_pattern schrijfbaar voor container-root, en dat is host-root code-execution via de core-dump usermode-helper. Nu afgeleid van docker-default met behoud van de /proc/sys- en /sys-denies, plus userns en mount/umount/pivot_root omdat geneste podman daar niet zonder kan. Sluit de core_pattern-write ook als systempaths=unconfined blijft staan. Het profiel is niet in deze omgeving te parsen (apparmor_parser is host-side); verificatie loopt via het testprotocol en het aa-complain/dmesg-recept in de profiel-header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(podman): setup-host.sh weigert profielbestanden met vreemde stanzas (#44) apparmor_parser -r vervangt profielen op de naam die in het bestand staat. Een toegevoegde `profile docker-default flags=(unconfined) { }` zou dus de afscherming van alle containers op de host slopen — en dat valt in een configbestand veel minder op dan in een script. Nu wordt geweigerd wat niet precies één claude-sandbox-podman-profiel is. Voegt ook het aa-complain/dmesg-recept toe aan de slotmelding, voor als het strakkere profiel podman breekt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(podman): keyring- en legacy-syscalls op de seccomp-blocklist (#44) keyctl, add_key en request_key openen de kernel-keyring, een terugkerend LPE-oppervlak dat via userns bereikbaar is. quotactl, syslog, uselib, ustat, sysfs en de pciconfig-familie zijn legacy-oppervlak. Rootless podman, crun en pasta gebruiken geen van alle. De blocklist-vorm blijft: een allowlist zet clone/unshare/mount/setns achter CAP_SYS_ADMIN en breekt daarmee rootless podman. 42 → 53 geblokkeerde syscalls. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): testprotocol voor de hardening + openstaand onderzoek (#44) "De escape is dicht" is een bewering die bewijs nodig heeft, en die kan alleen op een draaiende container geleverd worden. Het protocol test zowel dat de escape sluit (sudo weg, core_pattern-write geweigerd, CapEff=0) als dat de sandbox blijft werken (smoke-test groen), met de negatieve tests expliciet erbij. Legt ook de drie openstaande onderzoeksvragen vast (systempaths versmallen, userns-remap, bounding set) in de meettabel van de spec. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): security-balans herschreven na de hardening (#44) De multiuid-override stelde dat CAP_SYS_ADMIN "de impact van een root-escalatie ín de container" vergroot. Zonder userns-remap is container-root gelijk aan host-root-uid, dus die impact is host-breed. En de noot noemde nog een sudoers-regel die nu weg is. ADR 0001 heeft nu een security-balans in drie delen: wat dicht is (bridge, de sudo-route naar container-root, de self-service egress-allowlist, de /proc/sys-write), wat open blijft (outer-relaxaties, CAP_SYS_ADMIN in de multi-uid opt-in, kernel-escapes), en welke laag welke escape sluit (root-entrypoint voor het bereiken van root, AppArmor voor wat root dan kan). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(sandbox): setuid-strip + AppArmor-enforce-borging sluiten de resterende root-routes (#44) Uit de security-review op deze PR — twee gaten die de defense-in-depth in de multi-uid opt-in ondermijnden: 1. setuid-route. De privilege-drop laat bewust --no-new-privs weg (nodig voor newuidmap) en houdt de bounding set. Daardoor kon claude na de drop euid 0 + bounding-set-caps winnen via elke setuid-root-binary (su, mount, passwd, ...); de kernel vult bij een setuid-exec de permitted caps uit de bounding set, in multi-uid inclusief CAP_SYS_ADMIN. De Dockerfile stript nu alle setuid/setgid- bits behalve newuidmap/newgidmap/fusermount3. Dat sluit ook de setuid mount/ umount, en daarmee de proc-remount-bypass op binary-niveau. 2. AppArmor-complain-preconditie. In de multi-uid-stand is het AppArmor-profiel de laatste laag tegen de core_pattern-escape. Ons eigen debug-recept raadt aa-complain aan; blijft het profiel in complain staan, dan dwingen de denies niet af. entrypoint-root.sh faalt nu hard als ons profiel in complain-modus draait (macOS/unconfined blijft ongemoeid — daar zit een VM-grens onder). Samen maken deze de root-entrypoint-laag en de AppArmor-laag weer echt onafhankelijk, zoals de ADR-balans claimt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): security-balans eerlijk over de mount-bypass en de setuid-route (#44) De doc-claim "AppArmor weigert /proc/sys-writes ongeacht de mount-opties" was onjuist: de deny is padgebonden aan /proc, dus een verse proc-mount met CAP_SYS_ADMIN omzeilt hem. Gecorrigeerd in de multiuid-override en de ADR: - De setuid-strip is wat de weg naar CAP_SYS_ADMIN sluit; dat is de primaire mitigatie, met AppArmor als tweede laag voor de escapeklasse zelf. - De ADR benoemt nu expliciet dat AppArmor de /proc/sys-usermode-helper-klasse sluit, niet elke container-root-capability (mount/pivot_root/caps staan toe). - --no-new-privs-rationale toegevoegd aan de ADR. - Het blokkeren van de mount-bypass in het profiel zelf (child-profiel voor de nested runtime) staat als vierde onderzoeksvraag in de spec-meettabel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): testprotocol uitgebreid + doc-drift opgeruimd (#44) Reviewfixes: - Testprotocol test nu ook de lagen die het meest onder de loep lagen: /proc/self/attr/current moet enforce zijn (nooit complain), setuid-enumeratie moet alleen newuidmap/newgidmap/fusermount3 tonen, en CapBnd bevestigt de multi-uid bounding-set-trade-off. De smoke-test-commando's kregen een cd zodat ze letterlijk uitvoerbaar zijn. - Het aa-complain/dmesg/aa-enforce-recept stond 3x; het testprotocol is nu de canonieke plek, de profiel-header verwijst ernaar. - Niet-resolveerbare "PR B"/"PR #76"-verwijzingen in code en operationele docs vervangen door "deze hardening" en de verwijzing naar de spec-meettabel. - Twee exec-commando's zonder -u in het historische 06-10-plan kregen -u claude. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(sandbox): complain-check debug-escape + ADR-nauwkeurigheid (#44) Uit de tweede security-reviewronde — twee punten die de fixes zelf introduceerden, geen security-gat maar wel echt: - De complain-check faalde hard vóór de drop, waardoor het aa-complain- debugrecept uit het testprotocol niet meer in een draaiende container te reproduceren was. Nu degradeert ALLOW_APPARMOR_COMPLAIN=true de fatal tot een waarschuwing. Fail-closed by default; alleen de operator kan die env zetten vóór de drop, claude bereikt die fase niet. - De ADR overclaimde de onafhankelijkheid van de AppArmor-laag ("en omgekeerd"): AppArmor alléén stopt geen CAP_SYS_ADMIN-houder (de proc-mount-bypass), dus het is defense-in-depth voor het directe /proc/sys-pad, niet een zelfstandige vervanger van de setuid-strip. Ook de enforce-borging preciezer geformuleerd (dekt complain, niet "profiel niet toegepast"). Plus een getcap-regel in het testprotocol: de setuid-strip raakt geen file-capabilities, dus die apart controleren. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(sandbox): registries automatisch in de whitelist bij podman (#44) De docker.io-registries die Testcontainers nodig heeft, moesten in de strikte-whitelist-modus (OPEN_HTTPS=false) met de hand aan ALLOWED_DOMAINS worden toegevoegd. init-firewall.sh voegt ze nu zelf toe als rootless podman in de image zit — consistent met hoe de Maven/SDKman-domeinen al onvoorwaardelijk in de lijst staan. Zonder podman verbreedt het de whitelist niet, en bij OPEN_HTTPS=true (default) is de hele lijst een no-op. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(sandbox): root-shell-waarschuwing alleen tonen als je echt root bent (#44) De waarschuwing stond systeembreed in /etc/bash.bashrc en /etc/zsh/zshrc achter `if [ -t 1 ]` — dat checkt alleen op een tty, niet op de uid. Daardoor kreeg ook `claude` de "je bent root"-melding bij elke interactieve shell. De per-user /root-versie vuurde impliciet alleen voor root; die beperking ging verloren bij de omschakeling naar systeembreed. Guard aangevuld met [ "$(id -u)" -eq 0 ]. Blootgelegd door de host-verificatie (docker exec -tiu claude gaf de melding). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(podman): keyring-syscalls weer toestaan — crun heeft ze nodig (#44) De seccomp-uitbreiding blokkeerde keyctl/add_key/request_key op de aanname dat crun ze niet gebruikt. Fout: crun maakt bij elke container-start een session-keyring via keyctl(KEYCTL_JOIN_SESSION_KEYRING), waardoor geneste podman faalde met "crun: create keyring: Operation not permitted". Docker's default-profiel staat deze syscalls juist toe, precies hierom. Blootgelegd door de host-smoke-test (stap 2, nested container). De overige B5-toevoegingen (quotactl/syslog/uselib/ustat/sysfs/pciconfig_*) raken crun niet en blijven. ADR- en override-tekst gecorrigeerd. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(podman): /proc/sys/net toestaan in AppArmor — netavark heeft het nodig (#44) De van docker-default afgeleide deny `@{PROC}/sys/[^k]** w` blokkeerde ook /proc/sys/net. netavark (podman's netwerk-backend) zet bij het starten van een geneste container net-sysctls (ipv6 autoconf) en faalde met "netavark: failed to set autoconf sysctl: Permission denied". Docker doet netwerk-setup buiten AppArmor; geneste podman draait netavark ín de container onder dit profiel. `[^k]` -> `[^kn]` zondert /proc/sys/net uit naast /proc/sys/kernel. net-sysctls zijn per-netns namespaced (raakt alleen de eigen netns, niet de host); de core_pattern-escape zit in /proc/sys/kernel en blijft gedekt. Blootgelegd door de host-smoke-test (stap 4, SmokeTest). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(podman): pasta als default-netwerkmodus zodat Testcontainers werkt (#44) netavark faalde in nested rootless podman met "failed to set autoconf sysctl: Permission denied": voor een bridge zet het een IPv6-sysctl op een interface in de outer container-netns, die door init_user_ns geowned wordt (geen userns-remap), dus rootless podman mag er /proc/sys/net niet schrijven. Dit blokkeerde alle Testcontainers-runs op een gehardende host. De entrypoint zet nu netns="pasta" in containers.conf. Pasta geeft elke container een eigen netwerk met port-forwarding naar localhost en vermijdt de bridge. Op een echte host bevestigd: smoke-test groen incl. PostgresSmokeTest (multi-uid). Geen testcode aangepast — de netns-default geldt ook voor containers die via de podman-Docker-API starten. Beperking: container-naar-container over netwerknamen (Testcontainers Network) werkt niet zonder userns-remap; dat blijft de userns-remap-spike. Bestaande volumes krijgen de netns-regel via een idempotente migratie in de entrypoint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(podman): reviewfixes pasta — robuustere migratie, doc-drift, #82 (#44) Uit de reviewronde op deze PR: - containers.conf-migratie robuuster: de grep matcht nu ook een ingesprongen netns (geen duplicaat-key die podman's TOML-parser breekt), en bij een config zonder [containers]-sectie wordt die sectie geappend i.p.v. een sed die stil niets doet. Post-check waarschuwt als het toevoegen tóch faalt. - README-drift rechtgetrokken: twee plekken zeiden nog dat de entrypoint containers.conf "alleen aanmaakt als hij ontbreekt"; hij plaatst nu ook de netns-regel bij (overige drift blijft staan). - Openstaand-bullet toegevoegd voor de container-naar-container-beperking, en de vage "spike/#44"-verwijzing vervangen door het concrete ticket #82. - Egress-winst gedocumenteerd: nested pasta-egress loopt via de OUTPUT-chain en valt onder de allowlist, waar de bridge via FORWARD die allowlist kon omzeilen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(sandbox): reviewfixes registry-whitelist — interne registry + wording (#44) Uit de reviewronde op deze PR: - Happy-path-noot toegevoegd (README stap 1 + .env.sample): gebruik je een eigen/interne registry (Harbor, Nexus, mirror), zet die dan wél in ALLOWED_DOMAINS. Voorkomt dat een hergebruiker denkt dat álle registries automatisch gaan. - .env.sample-wording gelijkgetrokken met de code: de firewall keyt op "podman in de image" (command -v podman), niet op de INSTALL_PODMAN-flag zelf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(plannen): plan- en designdocumenten naar een eigen PR De vier documenten onder `docs/superpowers/` zijn 2738 van de 4477 toegevoegde regels en bevatten geen code. Ze gaan naar een losse PR zodat deze PR beperkt blijft tot wat er daadwerkelijk aan de container verandert. Inhoudelijk ongewijzigd — alleen verplaatst naar branch `docs/podman-stack-plannen`. * chore(ci): DS-0002 onderdrukken met motivering — root-fase is bewust (#44) De Dockerfile eindigt op `USER root` omdat init-firewall.sh NET_ADMIN nodig heeft; entrypoint-root.sh dropt daarna onherroepelijk naar `claude`. Trivy DS-0002 kijkt naar de gedeclareerde image-gebruiker en ziet die drop niet. De check is hier een false positive: de agent bereikt de root-fase nooit (geen sudo, setuid-bits gestript, geen weg terug na setpriv), en de twee paden die er nog wel naartoe leiden — `docker exec` zonder -u en een eigen --entrypoint — vereisen Docker-CLI-toegang op de host. Wie die heeft is al host-root en zit buiten de grens die de sandbox bewaakt. Het alternatief zonder root in de hoofdcontainer is onderzocht en levert een fail-open op bij herstart; uitgewerkt in #98. Geverifieerd: geen docker-socket gemount in compose.yml of de drie overrides; geen sudoers-regel in de image. Niet geverifieerd: of Trivy na deze wijziging groen wordt — dat blijkt uit de CI-run op deze push. * feat(podman)!: fuse-overlayfs-storage laten vallen, alleen vfs (#44) Review-punt: als /dev/fuse een security-risico is, ondersteun het dan niet half. De overlay-driver was opt-in, uitgecommentarieerd in beide overrides en gaf alleen snelheidswinst — terwijl de filesystem-snelheid met vfs tot nu toe voldoet. Weg: het `fuse-overlayfs`-pakket, de `/dev/fuse`-device-regels, de `PODMAN_STORAGE_DRIVER`-env met zijn terugvalpad, en het laden van de fuse-kernelmodule in setup-host.sh. storage.conf is nu onvoorwaardelijk vfs. Bijvangst voor de hardening: `fusermount3` hoeft niet meer uitgezonderd te worden van de setuid-strip in de Dockerfile. Die strip is daarmee strikter — alleen newuidmap/newgidmap blijven setuid. hardening-verificatie.md verwacht dat nu ook zo. BREAKING CHANGE: PODMAN_STORAGE_DRIVER bestaat niet meer. Stond die op `overlay`, verwijder hem uit .env; de container draait voortaan op vfs. Eenmalig `podman system reset` in de container ruimt de oude overlay-store op. Geverifieerd: `bash -n` op alle drie de scripts, YAML-parse op compose.yml en de drie overrides, en een grep die aantoont dat er buiten de spec geen fuse/overlay-verwijzing meer staat. Niet geverifieerd: geen image gebouwd en geen smoke-test gedraaid — dat moet op een echte host. * docs(adr): ADR 0001 als enige plek voor de isolatie-onderbouwing (#44) Review-punt: dezelfde uitleg stond op meerdere plekken (no_new_privs op vijf, de core_pattern-escape op drie), en 15 comments verwezen naar het spec-document — een historisch verslag, geen naslagwerk. ADR 0001 is herschreven met genummerde secties die de opstartvolgorde volgen: bouwfase (2.2), rootfase (2.3), gebruikersfase (2.4), relaxaties op de buitenste container (2.5). Code verwijst met nummer én titel, zodat de verwijzing leesbaar blijft en een hernummering opvalt: # Verwijder deze regel dus niet "voor de veiligheid" — zie ADR 0001 # §2.3.3 "Privilege-drop zonder --no-new-privs". Bij de code blijft staan wat je moet weten om díé regel te begrijpen; het volledige dreigingsverhaal staat één keer in de ADR. Bewust niet andersom: bij regels als de ontbrekende --no-new-privs en de setuid-strip is het risico dat iemand ze later "opruimt", dus daar staat nu expliciet waarom ze er zijn. Commentaarregels: entrypoint-root.sh 44->27, multiuid-override 38->20, entrypoint.sh 88->62, Dockerfile 104->80. Verder meegenomen uit de review: all-caps kopjes zijn normale zinnen geworden; "peelt" -> "pelt"; de multiuid-override beschrijft de eindtoestand i.p.v. "sinds de hardening"; de netavark-uitleg bij TESTCONTAINERS_HOST_OVERRIDE klopte niet meer sinds de pasta-default; het AppArmor-profiel noemt de echte compose-aanroep i.p.v. "docker ..."; de verwijzing naar de Copilot-bug is vervangen door het tijdloze punt (socket-toegang is host-root). Geverifieerd: `bash -n` op beide entrypoints, YAML-parse op compose.yml en de drie overrides, en een grep die aantoont dat er geen spec-verwijzing meer in levende code staat en geen all-caps kopje meer over is. Niet geverifieerd: geen image gebouwd. * docs(podman): README opschonen na review, isolatiedoc naar eigen PR (#44) Review-punten op claude-sandbox/podman/README.md: - Migratiesectie vanaf de host-agent verwijderd. Die was geschreven voor bestaande gebruikers, maar dat zijn er twee en die zijn al gemigreerd. Nieuwe lezers zouden een procedure aantreffen voor iets wat er nooit was. - Voor de installatiestappen staat nu een wegwijzer met de drie varianten (Linux, macOS podman machine, Rancher Desktop). De sectie heet "Stappen op Linux" i.p.v. "Stappen op de host" — hij was al Linux-only, alleen stond dat er niet. Stap 2 is expliciet Linux-only i.p.v. "onschadelijk elders". - "de fallback-tabel" bestond niet als term; het is nu een link naar "Fallbacks als het niet meteen draait" met de concrete symptoomrij erbij. - "bewuste ontsnappingsklep" is uitgeschreven: containers.conf wordt niet overschreven, dus handmatige aanpassingen blijven staan — inclusief de keerzijde dat je dan afwijkt zonder melding. - De openstaande bullet "seccomp/apparmor verder verfijnen (zie spec)" is weg: dat was een intentie zonder eigenaar of criterium. - Multi-uid op gehardend Ubuntu is bevestigd getest; de bullet die het tegendeel beweerde is weg en de platformtabel is gelijkgetrokken met de macOS-rij. hardening-verificatie.md heeft een kop gekregen die zegt wat het verifieert, wanneer je het draait en wat je nodig hebt. Het delegeerde zijn context aan de vier documenten die ernaar verwijzen. docs/maximale-isolatie-linux.md is uit deze PR gehaald — ongetest, staat los van de podman-stap en hoeft deze PR niet op te houden. De twee verwijzingen ernaar zijn vervangen door de inhoudelijke strekking, dus er blijft geen dode link achter. Het document volgt in een eigen PR. Geverifieerd: script over alle *.md in de repo. Geen enkele link die deze PR toevoegt of aanpast is stuk. Twee links blijven rood — `../issues` in docs/verantwoording.md en `../../issues` in docs/oefeningen/verbeteringen.md — maar die waren al stuk op main en worden in #97 gerepareerd. * docs: tweede reviewronde — koppen, diagnoserecept en .env-documentatie (#44) Vier punten uit de tweede review op #46: - Het aa-complain/dmesg/aa-enforce-recept stond in de kop van het AppArmor-profiel. Dat beschrijft wat je doet als het profiel in de weg zit, niet wat het afdwingt. Verhuisd naar ADR 0001 §2.5.4 "Als het profiel te strak blijkt". In het profiel blijft staan dat je niet mag terugvallen op flags=(unconfined) — dat is een eigenschap van het profiel zelf, en staat op de plek waar iemand die vlag zou zetten. - De drie installatievarianten (Linux, podman machine, Rancher) zijn subkoppen onder één "Installeren" geworden. Het waren losse ##-secties op hetzelfde niveau als "Multi-uid" en "Fallbacks", terwijl het alternatieven van dezelfde stap zijn. De wegwijzertabel linkt er nu naartoe. - Het uitstel van de eigen-kernel-route verwees nergens naar. Beide plekken (§4.5 en §5) wijzen nu naar #99, met de kanttekening dat die routes ongetest zijn. - OPEN_HTTPS had in .env.sample geen enkele toelichting en ALLOWED_DOMAINS alleen "Alleen nodig als OPEN_HTTPS niet true is". Net de verkeerde om kaal te laten: bij de default true is de allowlist een no-op, dus wie dat mist denkt beschermd te zijn terwijl al het uitgaand HTTPS openstaat. Beide uitgeschreven, inclusief dat de allowlist vóór de privilege-drop gelezen wordt en een wijziging dus een recreate vereist. Geverifieerd tegen init-firewall.sh:84 dat het allowlist-blok inderdaad alleen draait bij OPEN_HTTPS != true, en tegen regel 71 dat het script terugvalt op false als de variabele ontbreekt — dat laatste staat er nu bij, want het betekent dat een ontbrekende regel de sandbox stránger maakt. Testresultaten van deze PR op een gehardende Tuxedo-host: alle stappen groen, inclusief multi-uid (twee uid-mappings, PostgresSmokeTest) en het hardeningsprotocol. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Jordy Onrust <285647185+jonrust-minbzk@users.noreply.github.com> Co-authored-by: Mark Reuvekamp <mark.reuvekamp@rijksoverheid.nl>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wat
Handleiding om de sandbox op Linux in een VM te draaien (podman in Lima of Multipass), met Kata en gVisor als alternatieven. Eén nieuw bestand, geen wijziging aan bestaande.
Waarom apart
Losgetrokken uit #46. De podman-opzet daar deelt de host-kernel, dus een kernel-escape blijft een restrisico — dit document beschrijft hoe je die laag óók sluit. Maar het is een extra stap die niemand nodig heeft om podman te gebruiken, en #46 hoeft er niet op te wachten.
Niet geverifieerd
Geen van de beschreven routes is opgezet of gedraaid. Behandel dit als een startpunt voor wie het uitzoekt, niet als een werkend recept. Dat is ook de reden dat het niet in #46 thuishoort: die PR bevat wel geteste stappen.