Skip to content

Instantly share code, notes, and snippets.

@possibilities
Created January 26, 2026 05:50
Show Gist options
  • Select an option

  • Save possibilities/0e4b0573d616338772e34dcfd248941b to your computer and use it in GitHub Desktop.

Select an option

Save possibilities/0e4b0573d616338772e34dcfd248941b to your computer and use it in GitHub Desktop.
Deep review: browser-in-apple-container — pair reviewed by Codex and Claude

Deep Review: browser-in-apple-container

Date: 2026-01-26 Reviewers: Codex (pair), Claude (pair) Repo: possibilities/browser-in-apple-container Branch: main @ 60cda3f


Overview

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.


Critical Issues

C1. CDP is an unauthenticated RCE surface

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.

C2. D-Bus policy is fully permissive

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.

C3. Anti-detection is ineffective

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.


High Issues

H1. PID 1 never monitors supervisord

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.

H2. Chromium gets SIGKILL, never graceful shutdown

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.

H3. startsecs=0 creates undetected crash loops

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.

H4. JSON flag parsing is fragile and injectable

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 --.

H5. No set -u or set -o pipefail in shell scripts

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.


Medium Issues

M1. Zombie process accumulation

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.

M2. Cleanup trap doesn't cover all exit paths

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.

M3. Unbounded log growth

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.

M4. DBUS_SESSION_BUS_ADDRESS points to system bus

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).

M5. Unused loop variable in wait_for_* functions

Files: scripts/entrypoint.sh:15,26,37 Found by: Claude

Loop variable $i is assigned but never used. ShellCheck SC2034.


Low Issues

L1. README architecture diagram is outdated

File: README.md:14-16 Found by: Claude

Shows Chromium on port 9222 directly, missing the socat forwarding layer and internal port 9221.

L2. pkill -9 on launch is overly aggressive

File: scripts/chromium-launch.sh:16 Found by: Claude

Uses SIGKILL for stale process cleanup. SIGTERM with fallback to SIGKILL would be safer.

L3. netcat-openbsd only used for nc -z

File: Containerfile:24 Found by: Claude

Could use bash's /dev/tcp instead to reduce image size.

L4. Font cache not rebuilt at runtime with mounted volumes

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.

L5. chmod +x may be redundant

File: Containerfile:65 Found by: Claude

If source files already have execute permission in the repo, this is a no-op.


Positive Practices

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

Recommended Fix Priority

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment