Adds a macOS sandbox for the goosed process using seatbelt (sandbox-exec)
and an HTTP CONNECT proxy for egress filtering. All outbound network
traffic from goosed is forced through a local proxy that enforces domain
blocking, IP restrictions, and SSH allowlisting.
## Sandbox profile (seatbelt)
The sandbox profile is built programmatically via buildSandboxProfile()
rather than a static template, ensuring it is always regenerated fresh
on each spawn and never becomes stale across app updates.
Configurable protections (all default to enabled):
- GOOSE_SANDBOX_PROTECT_FILES: write-protect ~/.ssh, shell configs
- GOOSE_SANDBOX_BLOCK_RAW_SOCKETS: deny SOCK_RAW on AF_INET/AF_INET6
- GOOSE_SANDBOX_BLOCK_TUNNELING: block nc, ncat, netcat, socat, telnet
Always-on protections:
- All network denied except localhost (forces proxy usage)
- Sandbox config and goose config are write-protected
- Kernel extension loading denied
## Egress proxy
An HTTP/CONNECT proxy runs in the Electron main process on 127.0.0.1.
The sandboxed process uses it via HTTP_PROXY/HTTPS_PROXY env vars.
Blocking layers checked in order:
1. Loopback detection (prevents proxy-as-relay bypass)
2. Raw IP address blocking (no domain = blocked)
3. Local blocklist (blocked.txt, live-reloaded via fs.watch)
4. SSH/Git host restriction (port 22/2222/7999)
5. LaunchDarkly egress-allowlist flag (optional, with configurable failover)
Configurable proxy options:
- GOOSE_SANDBOX_ALLOW_IP: allow raw IP connections (default: blocked)
- GOOSE_SANDBOX_BLOCK_LOOPBACK: block loopback relay via proxy (default: off)
- GOOSE_SANDBOX_ALLOW_SSH: enable/disable SSH (default: on)
- GOOSE_SANDBOX_GIT_HOSTS: custom git host allowlist for SSH
- GOOSE_SANDBOX_SSH_ALL_HOSTS: allow SSH to any host (default: off)
- GOOSE_SANDBOX_LD_FAILOVER: LaunchDarkly failover mode (allow|deny|blocklist)
## Other changes
- Startup now throws an error if GOOSE_SANDBOX=true but sandbox-exec is
unavailable, instead of silently proceeding unsandboxed
- Domain normalization handles trailing dots, punycode, and IPv6 brackets
- Blocklist loaded once at startup and cached in memory with fs.watch
for live reload (no per-request disk I/O)
- Proxy resources cleaned up on app quit
- Removed unused dns import and resolveIPToHostnames function
- Formatted with prettier
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c551f-ba60-709e-b293-5dedad8d243f
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Katz <dkatz@squareup.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>