feat(sandbox): Podman-in-Docker vervangt de Maven host-agent, plus hardening (#44) - #46
Merged
Conversation
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>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#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>
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>
…nde 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>
…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>
…iner) (#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>
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>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
…#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>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ericwout-overheid
requested review from
loek-rijksoverheid and
mreuvekamp
as code owners
June 10, 2026 14:11
…ild-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>
…allback) (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 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>
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>
…+ smoke groen) (#44) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#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>
…oc-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>
ericwout-overheid
marked this pull request as draft
June 10, 2026 15:15
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>
…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>
…e 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>
…ebreid (#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>
…#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>
…rding (#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>
…into scratch/integ
# Conflicts: # claude-sandbox/host-agents/maven/requirements.in # claude-sandbox/host-agents/maven/requirements.txt
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`.
This was referenced Aug 4, 2026
Closed
Closed
…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.
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.
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.
) 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.
#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 ready for review
August 5, 2026 08:52
mreuvekamp
approved these changes
Aug 6, 2026
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.
Vervangt de Maven host-agent door rootless Podman ín de sandbox, en hardent de container zodat die vervanging ook standhoudt.
Deze PR bundelt de stack die eerder in #76, #80, #81, #86 en #88 stond. Die zijn gesloten: ze waren op elkaar gestapeld, waardoor werk dat verderop weer werd teruggedraaid alsnog gereviewd moest worden.
Wat verandert
De Maven host-agent verdwijnt.
maven_agent.py,run.shenrequirements.*gaan eruit. Die bridge draaidemvnop de host namens Claude — per ontwerp een container→host code-execution-bridge, en op Linux luisterde hij auth-loos op0.0.0.0:7777. Dat is issue #44.Rootless Podman-in-Docker komt ervoor in de plaats. Testcontainers draait binnen de sandbox in plaats van erbuiten: compose-overrides voor Linux en macOS, een AppArmor-profiel, een seccomp-blocklist,
setup-host.shen een smoke-test met sample-project. Opt-in viaINSTALL_PODMAN=true.De container zelf wordt geharded. Root-entrypoint dat de firewall opzet en daarna onherroepelijk naar
claudedropt, image-brede setuid-strip, een AppArmor-guard die fail-closed gaat als het profiel in complain-modus staat, en een vertrouwdPATHin de rootfase.Netwerk.
pastawordt de netwerkmodus voor alle geneste containers (lost #82 op). Nevenwinst: nested egress passeert de OUTPUT-chain en valt daarmee onder dezelfde domein-allowlist als de sandbox zelf — een netavark-bridge routeerde via FORWARD, waar de allowlist niet zit.Multi-uid als opt-in voor database-images die naar een tweede uid chownen.
Geen fuse-overlayfs. Overlay-storage vereist
/dev/fusein de container en opent daarmee de FUSE-laag van de gedeelde host-kernel. De winst is snelheid, niet functionaliteit. Storage is onvoorwaardelijkvfs.Onderbouwing op één plek
docs/adr/0001is 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:Bij de code staat wat je moet weten om díé regel te begrijpen; het volledige dreigingsverhaal staat één keer in de ADR. Dat scheelde flink wat herhaling:
entrypoint-root.sh44→27 commentaarregels, de multiuid-override 38→20,entrypoint.sh88→62, de Dockerfile 104→80.Geverifieerd
Handmatig op een gehardende Tuxedo-host (Ubuntu,
apparmor_restrict_unprivileged_userns=1), alle stappen groen:FATAL./dev/fuseafwezig,storage.confenpodman infobeidevfs, en de setuid-enumeratie geeft alleen nognewuidmapennewgidmap—fusermount3stond eerder in de uitzonderingslijst en is daar nu uit.claude-sandbox/docs/hardening-verificatie.md:core_pattern-write geweigerd, profiel inenforce,CapEff: 0000000000000000.init-firewall.shalsclaudefaalt,iptables -Lweigert,sudobestaat niet.podman infogeeft twee uid-mappings ([{0 1000 1} {1 100000 65536}]) en de smoke-test draaitPostgresSmokeTestmee. Dit was de belangrijkste regressietest — het bewijst datnewuidmapnog privileges wint, en dus dat de bewust ontbrekende--no-new-privsintact is.In CI: build, shellcheck, Trivy, CodeQL.
Niet geverifieerd
podman machine, Rancher Desktop) zijn in deze ronde niet opnieuw gedraaid.TESTCONTAINERS_HOST_OVERRIDE=localhoststaat er nog. De toelichting erbij klopte niet meer sinds de pasta-default en is herschreven, maar of de variabele zélf nog nodig is onder pasta is niet vastgesteld. Weghalen zonder testbewijs zou een regressie kunnen zijn die pas in iemands Testcontainers-run opduikt.Trivy DS-0002
De Dockerfile eindigt op
USER rootomdatinit-firewall.shNET_ADMINnodig heeft;entrypoint-root.shdropt daarna. De melding is onderdrukt in.trivyignoremet onderbouwing: de agent bereikt de rootfase nooit, en de paden die er nog wel naartoe leiden (docker execzonder-u, een eigen--entrypoint) vereisen Docker-CLI-toegang op de host — wie die heeft is al host-root. Het alternatief zonder root in de hoofdcontainer is uitgezocht en levert een fail-open bij herstart op; uitgewerkt in #98.Volgorde
Merge #99 vóór of samen met deze PR — ADR §4.5 en §5 verwijzen ernaar voor de eigen-kernel-route.
#96 (plandocumenten) is niet blokkerend: er staat geen enkele link vanuit deze PR naar die documenten. De ADR noemt het ontwerpdocument nog in platte tekst als historische bron, dus #96 eerst mergen houdt die vermelding kloppend.