Repository navigation
Description
Describe the bug
Summary
Gradle fails inside the Copilot CLI macOS sandbox because its client cannot connect to its localhost daemon, despite the configuration explicitly enabling outbound and local networking.
The daemon starts successfully and listens on 127.0.0.1. However, the generated policy for the failing command contains:
{
"network": {
"egress": {
"default": "deny"
},
"ingress": {
"default": "allow",
"hostLoopback": "deny"
},
"runtimeConfig": {
"networkProxy": "http://127.0.0.1:57901"
}
}
}The Gradle daemon in that run listened on port 57903 , not the permitted proxy port 57901 .
An independent socket probe also confirmed that binding and listening on localhost succeeds, while connecting to that listener fails with EPERM .
This appears to be a persistence or regression of #5105, which is currently closed.
Environment
- GitHub Copilot CLI: 1.0.107
- macOS: 26.7.1
- Architecture: arm64
- Gradle wrapper: 9.8.2
- Java: Eclipse Temurin 25.0.1
The project is a small standalone Java application using Gradle’s application plugin.
Relevant configuration
The following is a sanitized excerpt. An additional private artifact-registry hostname and unrelated settings have been omitted.
{
"sandbox": {
"enabled": true,
"allowBypass": true,
"allowDevToolAccess": true,
"addCurrentWorkingDirectory": true,
"auth": {
"git": true,
"gh": true
},
"userPolicy": {
"network": {
"allowOutbound": true,
"allowLocalNetwork": true,
"allowedHosts": [
"127.0.0.1",
"services.gradle.org"
],
"blockedHosts": []
}
}
}
}Additional filesystem grants provide read/write access to the Gradle user home and read access to the JDK/toolchain locations. The user-level filesystem deny list is empty.
Steps to reproduce
- On macOS, use Copilot CLI 1.0.107 with sandboxing enabled.
- Enable outbound and local networking, with a non-empty host allowlist that includes 127.0.0.1 . Git and GitHub CLI credential masking are also enabled in this reproduction.
- Open a Java project using the Gradle 9.8.2 wrapper and Temurin Java 25.0.1 .
- Ask Copilot to execute:
./gradlew build --debug --no-daemon - Inspect the debug output and the generated command policy under:
~/.copilot/session-state//policies/
Expected behavior
With allowLocalNetwork: true , sandboxed commands should be able to make direct TCP connections to localhost, including to build daemons they start themselves, subject to any explicitly enforced managed restrictions.
A deny-by-default egress policy is understandable when a proxy enforces the remote host allowlist. However, the requested loopback permission should be represented by an appropriate loopback exception.
If local networking intentionally means only proxy-mediated HTTP access, the documentation and settings UI should clearly distinguish that from direct localhost TCP access.
Actual behavior and diagnostic evidence
The daemon starts successfully
Gradle reports:
Successfully started process 'Gradle build daemon'
The daemon log confirms that it is listening on:
127.0.0.1:57903
The client then attempts that connection and immediately reports:
Trying to connect to address /127.0.0.1.
Cannot connect to address /127.0.0.1, skipping.
The command exits with code 1:
Could not connect to the Gradle daemon.
No build tasks execute.
The effective policy denies host loopback
The policy corresponding to the debug build contains:
"ingress": {
"default": "allow",
"hostLoopback": "deny"
}Its proxy endpoint is http://127.0.0.1:57901.
This is consistent with allowing listeners and access to the proxy, while denying direct connections to other localhost ports. Adding 127.0.0.1 to allowedHosts does not remove this effective denial.
A standalone socket probe reproduces the denial
A Python probe created a TCP listener on an ephemeral IPv4 loopback port and attempted to connect to it from the same process:
Loopback bind/listen succeeded: 127.0.0.1:57947
Loopback connect failed: PermissionError, errno=1, Operation not permitted
This isolates the failure from Gradle, dependency resolution, and external networking.
Proxy variables are injected
A subsequent sandboxed command received all uppercase and lowercase forms of HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY, pointing to a local proxy. NO_PROXY and no_proxy were unset.
Gradle’s daemon connection uses direct TCP, not HTTP, so these proxy variables cannot carry the daemon protocol.
--no-daemon does not avoid the connection
Gradle still launches a single-use daemon because the wrapper JVM does not satisfy the build’s JVM requirements. The debug output identifies insufficient maximum heap size; the wrapper starts with -Xmx64m.
These commands all failed with the daemon connection error:
./gradlew build
./gradlew build --no-daemon -Dorg.gradle.jvmargs=
./gradlew build --debug --no-daemon
Requested investigation
Please check how sandbox.userPolicy.network.allowLocalNetwork maps to MXC’s network.ingress.hostLoopback when proxy enforcement is active.
In this reproduction, local networking is requested, but the generated policy still sets hostLoopback: "deny". Please clarify whether this is a policy-generation defect, a managed-policy override, or an intentional restriction.
If intentional, please document a supported way to permit build-daemon loopback connections without disabling the sandbox or removing remote host filtering.
Affected version
No response
Steps to reproduce the behavior
No response
Expected behavior
No response
Additional context
No response
Activity