Skip to content

Allow binding sockets in the macOS sandbox when networking is blocked - #30865

Closed
fmeum wants to merge 4 commits into
bazelbuild:masterfrom
fmeum:claude/bazel-issue-14828-4ms8yp
Closed

Allow binding sockets in the macOS sandbox when networking is blocked#30865
fmeum wants to merge 4 commits into
bazelbuild:masterfrom
fmeum:claude/bazel-issue-14828-4ms8yp

Conversation

@fmeum

@fmeum fmeum commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Description

The macOS sandbox denied bind() outright when network access was blocked, so any action that starts a local server failed under --experimental_sandbox_default_allow_network=false or the block-network tag. This adds three rules to the generated SBPL profile: binding to any local address, and Unix domain sockets in both directions. Servers commonly bind the wildcard address (0.0.0.0 or ::), which the localhost filter does not match, which is why the bind rule is unrestricted.

Outbound traffic stays restricted to loopback, so a blocked spawn still cannot reach another host: connecting to the default gateway and to a public address both fail with EPERM, while loopback and the machine's own addresses succeed.

One limitation is worth flagging for review and is documented in the code. Inbound traffic cannot be constrained the same way, because macOS decides network-inbound from the local address alone, and its localhost filter matches every address assigned to the machine rather than just 127.0.0.1 and ::1. Filtering network-inbound on the remote address is not an option either: such a rule compiles but has no effect, confirmed by placing (deny network-inbound (remote ip "*:*")) last in the profile and observing that accepts still succeed, whereas the same rule keyed on the local address breaks listening. A peer elsewhere on the network can therefore reach a spawn that listens on a non-loopback address. The Linux sandbox does not have this gap because its network namespace only ever has a loopback interface. Closing it would mean disallowing the wildcard bind, which is the bug this PR fixes.

The remaining commits are test-only.

The new coverage is added as integration tests rather than as assertions on the generated profile text, since exercising real binds through the sandbox is what actually establishes that the profile works, and it runs on Linux as well. DarwinSandboxedSpawnRunnerTest is left untouched.

The networking integration test gains cases for binding to 127.0.0.1, ::1, localhost, a Unix socket, and a non-loopback address of the machine, plus a check that blocked spawns cannot reach a non-loopback address. Where the expected outcome genuinely differs per platform it is asserted either way rather than skipped, so no case is silently dropped on either OS.

--sandbox_fake_hostname is only implemented by the linux-sandbox and is silently accepted elsewhere. That test previously returned early outside Linux; it now runs the same test with and without the flag and asserts that the hostname becomes localhost on Linux and is unchanged everywhere else.

testing_server.py retried every bind failure with a new port forever, which would hang a caller waiting for the started line if the address could never be bound. It now only retries on EADDRINUSE.

Motivation

Fixes #14828.

Build API Changes

No

Checklist

  • I have added tests for the new use cases (if any).
  • I have updated the documentation (if applicable).

Release Notes

RELNOTES: The macOS sandbox now allows actions to bind sockets when network access is blocked, matching the behavior of the Linux sandbox. Note that macOS cannot restrict inbound connections by peer, so an action that listens on a non-loopback address is reachable from other hosts on the network; outbound traffic remains restricted to loopback.

@google-cla

google-cla Bot commented Aug 25, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@fmeum
fmeum force-pushed the claude/bazel-issue-14828-4ms8yp branch from 2a276ec to bc8d63c Compare August 26, 2026 11:18
@fmeum fmeum changed the title Claude/bazel issue 14828 4ms8yp Allow binding sockets in the macOS sandbox when networking is blocked Aug 26, 2026
@fmeum
fmeum force-pushed the claude/bazel-issue-14828-4ms8yp branch from bc8d63c to fdaa76b Compare August 26, 2026 11:25
fmeum and others added 4 commits August 26, 2026 14:52
The sandbox profile generated for block-network spawns denied
`network-bind`: it allowed accepting and establishing loopback
connections, but binding a socket - what any local test server does
first - was still rejected by `(deny network*)`. As a result, tests
using e.g. OkHttp MockWebServer or Dropwizard failed on macOS while
passing inside the Linux sandbox's network namespace.

Allow `network-bind` for any local address: servers commonly bind to
the wildcard address (0.0.0.0 or ::), which the "localhost" filter
does not match, and a bound socket still cannot exchange traffic with
other hosts because inbound and outbound traffic remains restricted to
loopback. Also allow local Unix domain sockets, which are
filesystem-scoped IPC, in both directions. This matches the behavior of
the Linux sandbox, where arbitrary binds succeed but only the loopback
interface exists.

Adds a regression test that inspects the generated sandbox profile.

Fixes bazelbuild#14828
Extend bazel_sandboxing_networking_test.sh, which runs on both Linux and
macOS, with in-sandbox server coverage for the various kinds of
localhost binding:

* binding to 127.0.0.1, ::1, and the "localhost" hostname (in
  addition to the existing wildcard-address coverage via :loopback),
* binding and connecting to a Unix domain socket inside the sandbox.

Exercising real binds through the sandbox is what actually establishes
that the profile works; asserting on the generated profile text would
not.

testing_server.py gains a --bind_address flag so the test server can
bind a specific address instead of the wildcard address. The :loopback
target now also carries the tags under test so that the tag-based
block-network scenario exercises in-sandbox servers as well.
The negative direction - blocked spawns must not be able to reach
anything beyond loopback - was previously only covered when
REMOTE_NETWORK_ADDRESS was explicitly set, which CI does not do.

Add hermetic checks that do not require Internet connectivity, using a
non-loopback address of the local machine: one genrule connects to the
existing wildcard-bound test server through that address, another binds
its own server to it.

The expected outcome differs per platform, so assert it either way
instead of skipping. The linux-sandbox runs blocked spawns in a network
namespace that only has a loopback interface, so the address is
unreachable there and the bind fails. The macOS sandbox cannot express
"loopback only": the host part of an SBPL "ip" filter may only be "*" or
"localhost", and "localhost" matches every address assigned to the local
machine rather than just 127.0.0.1 and ::1, so both checks still succeed
there. Neither sandbox lets a blocked spawn reach any other host, which
remains covered by the remote-ip and remote-name checks.

Both checks are skipped on machines without a non-loopback address.

Also make testing_server.py fail fast when a bind cannot succeed:
retrying a permanent failure with a different port would loop forever
and hang the caller waiting for the "started" line.
The flag is only implemented by the linux-sandbox, which sets the
hostname to "localhost" inside a UTS namespace. Everywhere else it is
silently accepted and does nothing, and the test returned early outside
Linux, leaving that behavior unasserted.

Run the same test with and without the flag and compare the hostname it
observes: on Linux it must become "localhost", everywhere else it must
be unchanged. Comparing against the hostname seen by the very same test
avoids having to predict how the machine's hostname resolves.
@fmeum
fmeum force-pushed the claude/bazel-issue-14828-4ms8yp branch from fdaa76b to 1b8679e Compare August 26, 2026 12:53
@fmeum
fmeum marked this pull request as ready for review August 26, 2026 12:54
@github-actions github-actions Bot added team-Local-Exec Issues and PRs for the Execution (Local) team awaiting-review PR is awaiting review from an assigned reviewer labels Aug 26, 2026
@fmeum
fmeum requested a review from meisterT August 26, 2026 12:55
@meisterT meisterT added awaiting-PR-merge PR has been approved by a reviewer and is ready to be merge internally and removed awaiting-review PR is awaiting review from an assigned reviewer labels Aug 26, 2026
@github-actions github-actions Bot removed the awaiting-PR-merge PR has been approved by a reviewer and is ready to be merge internally label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team-Local-Exec Issues and PRs for the Execution (Local) team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

block-network does not allow binding to localhost on Mac

2 participants