From cd5821669c4b496b4ca82afc19602b71a129a159 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 9 Feb 2026 13:36:43 -0800 Subject: [PATCH] fix: use wildcard in allowLocalBinding seatbelt rules for IPv6 dual-stack compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modern runtimes like Java create IPv6 dual-stack sockets by default. When binding such a socket to 127.0.0.1, the kernel represents the address as ::ffff:127.0.0.1 (IPv4-mapped IPv6). macOS Seatbelt's "localhost" filter only matches 127.0.0.1 and ::1, not the IPv4-mapped variant, causing bind() to fail with EPERM. Seatbelt only supports two host values in IP filters: "localhost" and "*". Since we can't specify ::ffff:127.0.0.1 explicitly, change to (local ip "*:*"). This is safe because the (local ip) filter matches the LOCAL endpoint of connections — internet-bound traffic originates from non-loopback interfaces, so it remains blocked by the (deny default) rule. Fixes: https://github.com/anthropics/claude-code/issues/18545 --- src/sandbox/macos-sandbox-utils.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/sandbox/macos-sandbox-utils.ts b/src/sandbox/macos-sandbox-utils.ts index b9e7378..ec8261a 100644 --- a/src/sandbox/macos-sandbox-utils.ts +++ b/src/sandbox/macos-sandbox-utils.ts @@ -492,10 +492,17 @@ function generateSandboxProfile({ profile.push('(allow network*)') } else { // Allow local binding if requested + // Use "*:*" instead of "localhost:*" because modern runtimes (Java, etc.) create + // IPv6 dual-stack sockets by default. When binding such a socket to 127.0.0.1, + // the kernel represents it as ::ffff:127.0.0.1 (IPv4-mapped IPv6). Seatbelt's + // "localhost" filter only matches 127.0.0.1 and ::1, NOT ::ffff:127.0.0.1. + // Using (local ip "*:*") is safe because it only matches the LOCAL endpoint — + // internet-bound connections originate from non-loopback interfaces, so they + // remain blocked by (deny default). if (allowLocalBinding) { - profile.push('(allow network-bind (local ip "localhost:*"))') - profile.push('(allow network-inbound (local ip "localhost:*"))') - profile.push('(allow network-outbound (local ip "localhost:*"))') + profile.push('(allow network-bind (local ip "*:*"))') + profile.push('(allow network-inbound (local ip "*:*"))') + profile.push('(allow network-outbound (local ip "*:*"))') } // Unix domain sockets for local IPC (SSH agent, Docker, etc.) if (allowAllUnixSockets) {