Skip to content

feat(sandbox): Podman-in-Docker vervangt de Maven host-agent, plus hardening (#44) - #46

Merged
ericwout-overheid merged 77 commits into
mainfrom
feat/maven-podman-poc
Aug 6, 2026
Merged

feat(sandbox): Podman-in-Docker vervangt de Maven host-agent, plus hardening (#44)#46
ericwout-overheid merged 77 commits into
mainfrom
feat/maven-podman-poc

Conversation

@ericwout-overheid

@ericwout-overheid ericwout-overheid commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

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.sh en requirements.* gaan eruit. Die bridge draaide mvn op de host namens Claude — per ontwerp een container→host code-execution-bridge, en op Linux luisterde hij auth-loos op 0.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.sh en een smoke-test met sample-project. Opt-in via INSTALL_PODMAN=true.

De container zelf wordt geharded. Root-entrypoint dat de firewall opzet en daarna onherroepelijk naar claude dropt, image-brede setuid-strip, een AppArmor-guard die fail-closed gaat als het profiel in complain-modus staat, en een vertrouwd PATH in de rootfase.

Netwerk. pasta wordt 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/fuse in de container en opent daarmee de FUSE-laag van de gedeelde host-kernel. De winst is snelheid, niet functionaliteit. Storage is onvoorwaardelijk vfs.

Breaking change: PODMAN_STORAGE_DRIVER bestaat niet meer. Stond die op overlay, verwijder hem uit .env en draai eenmalig podman system reset in de container — anders botst de bestaande overlay-store met de vfs-config.

Onderbouwing op één plek

docs/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:

# Verwijder deze regel dus niet "voor de veiligheid" — zie ADR 0001
# §2.3.3 "Privilege-drop zonder --no-new-privs".

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.sh 44→27 commentaarregels, de multiuid-override 38→20, entrypoint.sh 88→62, de Dockerfile 104→80.

Geverifieerd

Handmatig op een gehardende Tuxedo-host (Ubuntu, apparmor_restrict_unprivileged_userns=1), alle stappen groen:

  • Build en start met de podman-override; firewall opgezet, geen FATAL.
  • Fuse-verwijdering: /dev/fuse afwezig, storage.conf en podman info beide vfs, en de setuid-enumeratie geeft alleen nog newuidmap en newgidmapfusermount3 stond eerder in de uitzonderingslijst en is daar nu uit.
  • Hardeningsprotocol uit claude-sandbox/docs/hardening-verificatie.md: core_pattern-write geweigerd, profiel in enforce, CapEff: 0000000000000000.
  • Egress niet self-service: init-firewall.sh als claude faalt, iptables -L weigert, sudo bestaat niet.
  • Multi-uid: podman info geeft twee uid-mappings ([{0 1000 1} {1 100000 65536}]) en de smoke-test draait PostgresSmokeTest mee. Dit was de belangrijkste regressietest — het bewijst dat newuidmap nog privileges wint, en dus dat de bewust ontbrekende --no-new-privs intact is.
  • Een echt Quarkus/Redis-project draait groen via podman in de sandbox.

In CI: build, shellcheck, Trivy, CodeQL.

Niet geverifieerd

  • Alleen op Linux getest. De macOS-paden (podman machine, Rancher Desktop) zijn in deze ronde niet opnieuw gedraaid.
  • TESTCONTAINERS_HOST_OVERRIDE=localhost staat 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 root omdat init-firewall.sh NET_ADMIN nodig heeft; entrypoint-root.sh dropt daarna. De melding is onderdrukt in .trivyignore met onderbouwing: de agent bereikt de rootfase nooit, en de paden die er nog wel naartoe leiden (docker exec zonder -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.

ericwout-overheid and others added 14 commits June 10, 2026 13:52
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 and others added 7 commits June 10, 2026 14:21
…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 ericwout-overheid changed the title feat(maven-podman): rootless Podman-in-Docker voor Testcontainers (#44) feat(maven-podman): rootless Podman-in-Docker als vervanger van de Maven host-agent (#44) Jun 10, 2026
@ericwout-overheid
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>
ericwout-overheid and others added 3 commits June 10, 2026 15:54
…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>
ericwout-overheid and others added 5 commits August 4, 2026 08:32
…#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>
# 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`.
Comment thread claude-sandbox/Dockerfile Fixed
…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.
Comment thread claude-sandbox/docs/hardening-verificatie.md
Comment thread claude-sandbox/podman/apparmor/claude-sandbox-podman Outdated
Comment thread claude-sandbox/podman/apparmor/claude-sandbox-podman Outdated
Comment thread claude-sandbox/podman/README.md Outdated
Comment thread claude-sandbox/podman/README.md Outdated
Comment thread claude-sandbox/Dockerfile Outdated
Comment thread claude-sandbox/entrypoint-root.sh
Comment thread claude-sandbox/entrypoint.sh Outdated
Comment thread docs/adr/0001-maven-testcontainers-sandbox-isolatie.md Outdated
Comment thread docs/maximale-isolatie-linux.md Outdated
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.
Comment thread claude-sandbox/podman/apparmor/claude-sandbox-podman Outdated
Comment thread claude-sandbox/podman/README.md Outdated
Comment thread docs/adr/0001-maven-testcontainers-sandbox-isolatie.md Outdated
Comment thread claude-sandbox/.env.sample
#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
ericwout-overheid marked this pull request as ready for review August 5, 2026 08:52
@ericwout-overheid
ericwout-overheid merged commit f6558ee into main Aug 6, 2026
8 checks passed
@ericwout-overheid
ericwout-overheid deleted the feat/maven-podman-poc branch August 6, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants