Date: 2026-01-26
Reviewers: Codex (pair), Claude (pair)
Repo: possibilities/browser-in-apple-container
Branch: main @ 60cda3f
Minimal ARM64 Chromium container for Apple's apple/container micro-VM runtime (macOS Tahoe / Apple Silicon). Exposes Chrome DevTools Protocol (CDP) on port 9222 for headless browser automation. Uses supervisord to orchestrate D-Bus, Mutter (Wayland headless compositor), Chromium, and socat (port forwarding).
~520 lines across 12 files. 4 commits.
Files: services/socat.conf, scripts/chromium-launch.sh:28
Found by: Both reviewers
socat binds to 0.0.0.0 and Chromium sets --remote-allow-origins=*. Any network peer reaching port 9222 gets full browser control: arbitrary JS execution, cookie theft, file access.
Fix: Bind socat to 127.0.0.1 by default. Document that users must publish to 127.0.0.1:9222:9222 only. Consider an auth proxy for wider exposure.
File: configs/dbus-system.conf:17-29
Found by: Both reviewers
Allows every user to own any bus name and call any method (allow own="*", allow user="*"). A compromised Chromium renderer could impersonate arbitrary D-Bus services.
Fix: Scope own= to names Mutter actually needs (org.gnome.Mutter.*, org.gnome.Shell) and restrict send_destination.
Found by: Claude
The most important anti-detection flag is missing: --disable-blink-features=AutomationControlled. Without it, navigator.webdriver === true — the first check every anti-bot system makes.
Also missing:
- WebRTC leak policy (
WebRtcLocalIpsAllowedUrls) - UA override (default Debian Chromium UA contains
Debian) - WebGL vendor/renderer fingerprint spoofing
--disable-features=IsolateOrigins,site-per-process
Fix: Add --disable-blink-features=AutomationControlled to hardcoded flags at minimum. Consider WebRTC policy and UA override.
File: scripts/entrypoint.sh:65-95
Found by: Codex
entrypoint.sh starts supervisord then enters sleep infinity. If supervisord crashes, the container appears "healthy" but all services are dead and never restart.
Fix: Use supervisord -n (nodaemon mode) with exec, making supervisord PID 1, or add tini/dumb-init.
File: services/chromium.conf:6-7
Found by: Claude
stopsignal=KILL and stopwaitsecs=0 means Chromium never flushes profile data, risking user-data corruption on every stop/restart.
Fix: stopsignal=TERM, stopwaitsecs=10, let KILL be the fallback.
File: services/chromium.conf:5
Found by: Claude
Supervisord considers Chromium "started" immediately. Combined with SIGKILL, creates a tight crash-kill-restart loop with no backoff.
Fix: startsecs=3, startretries=5.
File: scripts/chromium-launch.sh:48-51
Found by: Both reviewers
sed-based JSON parsing breaks on multi-line JSON, escaped quotes, or nested objects. A malicious /chromium/flags mount could inject flags like --disable-web-security.
Fix: Use jq for parsing, or validate each flag starts with --.
Files: scripts/entrypoint.sh:2, scripts/chromium-launch.sh
Found by: Claude
Both scripts only use set -e. Unset variables silently expand to empty while running as root.
Fix: set -euo pipefail in both scripts.
File: scripts/entrypoint.sh:93-95
Found by: Claude
Bash as PID 1 does not reap orphaned child processes. Over long-running sessions, zombies accumulate from supervisord forks and Chromium subprocesses.
Fix: Add tini or use exec supervisord -n as PID 1.
File: scripts/entrypoint.sh:63
Found by: Claude
Trap only catches TERM and INT. If wait_for_* fails via set -e, cleanup never runs and supervisord children are orphaned.
Fix: Add trap cleanup EXIT.
Files: services/*.conf
Found by: Claude
No stdout_logfile_maxbytes or backups. Long-running containers will fill disk.
Fix: Add stdout_logfile_maxbytes=10MB, stdout_logfile_backups=2 to each service.
File: scripts/chromium-launch.sh:75
Found by: Claude
Session bus address is set to the system bus socket — semantically incorrect, works only because D-Bus policy is wide open (C2).
Files: scripts/entrypoint.sh:15,26,37
Found by: Claude
Loop variable $i is assigned but never used. ShellCheck SC2034.
File: README.md:14-16
Found by: Claude
Shows Chromium on port 9222 directly, missing the socat forwarding layer and internal port 9221.
File: scripts/chromium-launch.sh:16
Found by: Claude
Uses SIGKILL for stale process cleanup. SIGTERM with fallback to SIGKILL would be safer.
File: Containerfile:24
Found by: Claude
Could use bash's /dev/tcp instead to reduce image size.
File: Containerfile:27
Found by: Claude
fc-cache -f runs at build time only. Custom fonts mounted at runtime won't be cached. Minor since Chromium calls fc-match on demand.
File: Containerfile:65
Found by: Claude
If source files already have execute permission in the repo, this is a no-op.
Both reviewers highlighted these strengths:
- Clean separation of concerns — entrypoint orchestrator, chromium-launch flag builder, individual supervisord service files
- Explicit startup ordering with readiness gates (socket/port checks) prevents race conditions between D-Bus, Mutter, and Chromium
- Singleton lock cleanup (
chromium-launch.sh:11-13) prevents stale lock files from blocking restarts - Flag deduplication logic (
chromium-launch.sh:58-68) avoids conflicts between hardcoded and user-provided flags - Custom D-Bus config pragmatically works around apple/container capability limitations
- socat port forwarding correctly works around Debian Chromium ignoring
--remote-debugging-address - Unprivileged browser user (UID 1000) for Chromium execution
- Centralized logging through supervisord simplifies troubleshooting
- Explicit Wayland/DBUS/XDG environment setup in chromium-launch.sh aids portability
| Priority | Issue | Fix |
|---|---|---|
| P0 | C3 | Add --disable-blink-features=AutomationControlled to Chromium flags |
| P0 | C1 | Bind socat to 127.0.0.1 by default |
| P1 | H2/H3 | Chromium stopsignal=TERM, stopwaitsecs=10, startsecs=3 |
| P1 | H1/M1 | Use supervisord -n as PID 1 (solves zombie reaping + crash detection) |
| P1 | H5/M2 | set -euo pipefail in both scripts + trap cleanup EXIT |
| P2 | C2 | Scope D-Bus policy to Mutter's actual bus names |
| P2 | M3 | Add log rotation to all service configs |
| P2 | H4 | Replace sed JSON parsing with jq |
| P3 | L1 | Fix README architecture diagram |
| P3 | L2 | Use SIGTERM before SIGKILL in chromium-launch.sh cleanup |