Skip to content

fix: resolve host.docker.internal DNS in chroot mode for MCP servers#555

Merged
Mossaka merged 7 commits intomainfrom
fix/chroot-host-docker-internal-dns
Feb 6, 2026
Merged

fix: resolve host.docker.internal DNS in chroot mode for MCP servers#555
Mossaka merged 7 commits intomainfrom
fix/chroot-host-docker-internal-dns

Conversation

@Mossaka
Copy link
Collaborator

@Mossaka Mossaka commented Feb 6, 2026

Summary

  • In chroot mode, MCP servers (github, playwright, safeoutputs) fail to connect to the MCP gateway because host.docker.internal doesn't resolve inside the chroot
  • Docker adds host.docker.internal to the container's /etc/hosts via extra_hosts, but the chroot uses the host's /etc/hosts which lacks this entry
  • This caused both Smoke Claude and Smoke Copilot to fail on PR fix: set NO_PROXY for host gateway to bypass Squid for MCP #554 (all 3 MCP servers showed fetch failed)

Changes

src/docker-manager.ts: When chroot + host access are both enabled, mount a writable copy of /etc/hosts (in workDir) instead of the read-only original. This lets the entrypoint inject the missing entry without modifying the actual host file.

containers/agent/entrypoint.sh: Before entering the chroot, read the host.docker.internal entry from the container's /etc/hosts (where Docker placed it) and append it to the writable copy. Cleanup removes the entry on exit.

Test plan

  • TypeScript builds cleanly
  • All 732 unit tests pass
  • CI checks pass (build, lint, type check, integration tests)
  • Smoke Claude: all 3 MCP servers (github, playwright, safeoutputs) connect successfully
  • Smoke Copilot: MCP tools available and used (not just bash workarounds)

🤖 Generated with Claude Code

In chroot mode, MCP servers fail to connect to the MCP gateway because
host.docker.internal doesn't resolve. Docker adds this entry to the
container's /etc/hosts via extra_hosts, but the chroot uses a separate
copy of the host's /etc/hosts which lacks this entry.

Fix: mount a writable copy of /etc/hosts (instead of the read-only
original) when host access is enabled, and inject the host.docker.internal
entry from the container's /etc/hosts before entering the chroot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 6, 2026 16:27
@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

💫 TO BE CONTINUED... Smoke Claude was cancelled! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

📰 DEVELOPING STORY: Smoke Copilot reports was cancelled. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests failed Smoke Chroot was cancelled - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 82.16% 81.87% 📉 -0.29%
Statements 82.19% 81.91% 📉 -0.28%
Functions 81.95% 81.95% ➡️ +0.00%
Branches 75.48% 75.41% 📉 -0.07%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 81.9% (-1.34%) 82.5% → 81.2% (-1.29%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Add tests verifying that:
- enableChroot + enableHostAccess mounts a writable chroot-hosts copy
- enableChroot without enableHostAccess keeps read-only /etc/hosts mount

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

📰 DEVELOPING STORY: Smoke Copilot reports was cancelled. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

💫 TO BE CONTINUED... Smoke Claude was cancelled! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests failed Smoke Chroot was cancelled - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 82.16% 82.14% 📉 -0.02%
Statements 82.19% 82.17% 📉 -0.02%
Functions 81.95% 81.95% ➡️ +0.00%
Branches 75.48% 75.54% 📈 +0.06%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 83.1% (-0.12%) 82.5% → 82.4% (-0.10%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical issue where MCP servers fail to connect in chroot mode because host.docker.internal doesn't resolve. Docker adds this hostname to the container's /etc/hosts via extra_hosts, but chroot mode uses a mount of the host's /etc/hosts which lacks this entry. The fix creates a writable copy of /etc/hosts when both chroot and host access are enabled, allowing the entrypoint to inject the missing DNS entry.

Changes:

  • Creates writable copy of /etc/hosts in chroot+host-access mode instead of read-only mount
  • Entrypoint injects host.docker.internal from container's /etc/hosts into chroot's copy
  • Cleanup removes the injected entry on exit to avoid side effects

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/docker-manager.ts Conditionally creates writable /etc/hosts copy in workDir when both chroot and host access are enabled; falls back to minimal hosts file if host's /etc/hosts is unreadable
containers/agent/entrypoint.sh Reads host.docker.internal entry from container's /etc/hosts and appends to chroot's copy; cleanup removes the entry using sed on exit

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

);

// Mount /etc/hosts for host name resolution inside chroot
// When host access is enabled, we mount a writable COPY so the entrypoint
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "When host access is enabled" but this code is inside the if (config.enableChroot) block (line 440), so it only runs when chroot is enabled. The comment should be more precise: "When BOTH chroot and host access are enabled" to make it clear this is specific to the combination of both features, not just host access alone. This would improve code clarity and prevent future confusion.

Suggested change
// When host access is enabled, we mount a writable COPY so the entrypoint
// When BOTH chroot and host access are enabled, we mount a writable COPY so the entrypoint

Copilot uses AI. Check for mistakes.
Comment on lines 330 to 331
# Remove the host.docker.internal line we added (runs inside chroot perspective)
CLEANUP_CMD="${CLEANUP_CMD}; sed -i '/host.docker.internal/d' /etc/hosts 2>/dev/null || true"
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sed command uses a pattern /host.docker.internal/d which will delete ANY line containing "host.docker.internal", not just the line that was added. If the chroot-hosts file (copied from the host's /etc/hosts) originally contained a host.docker.internal entry, that original entry would also be deleted by this cleanup.

While unlikely (since the host's /etc/hosts typically wouldn't have this Docker-specific entry), this could cause issues in edge cases. A safer approach would be to:

  1. Store the exact line that was added in a variable
  2. Use grep -v with the exact line, or use sed with line number matching
  3. Or use sed with a more specific pattern that matches the exact IP and entry format

However, given that host.docker.internal is Docker-specific and unlikely to be in the host's /etc/hosts, and the fact that this cleanup uses || true to ignore errors, this is a minor edge case. The current implementation is acceptable but could be more precise.

Suggested change
# Remove the host.docker.internal line we added (runs inside chroot perspective)
CLEANUP_CMD="${CLEANUP_CMD}; sed -i '/host.docker.internal/d' /etc/hosts 2>/dev/null || true"
# Remove the specific host.docker.internal line we added (runs inside chroot perspective)
CLEANUP_CMD="${CLEANUP_CMD}; sed -i '/^127\.0\.0\.1[[:space:]]\+host\.docker\.internal$/d' /etc/hosts 2>/dev/null || true"

Copilot uses AI. Check for mistakes.
Comment on lines 494 to 507
if (config.enableHostAccess) {
const chrootHostsPath = path.join(config.workDir, 'chroot-hosts');
try {
fs.copyFileSync('/etc/hosts', chrootHostsPath);
logger.debug(`Copied /etc/hosts to ${chrootHostsPath} for chroot host access`);
} catch {
// Fall back to empty file if host /etc/hosts is not readable
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n');
logger.debug('Created minimal chroot-hosts (could not read host /etc/hosts)');
}
agentVolumes.push(`${chrootHostsPath}:/host/etc/hosts`);
} else {
agentVolumes.push('/etc/hosts:/host/etc/hosts:ro');
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no test coverage for the combination of enableChroot and enableHostAccess being both true. The existing tests at line 528-565 test chroot mode, and tests at line 975-1027 test host access, but none test them together.

This new functionality that creates a writable copy of /etc/hosts specifically when both flags are enabled should be covered by a test. The test should verify:

  1. When both enableChroot: true and enableHostAccess: true, a writable mount is created (no :ro suffix)
  2. The mount path includes chroot-hosts
  3. When only enableChroot: true (without host access), the mount is read-only with :ro suffix
  4. The file system operation (copyFileSync) is properly mocked to avoid side effects

Consider adding a test case like:

it('should mount writable /etc/hosts copy when both chroot and host access are enabled', () => {
  const config = { ...mockConfig, enableChroot: true, enableHostAccess: true };
  const result = generateDockerCompose(config, mockNetworkConfig);
  const volumes = result.services.agent.volumes as string[];
  
  // Should mount writable chroot-hosts file (not read-only)
  const hostsMount = volumes.find(v => v.includes('/host/etc/hosts'));
  expect(hostsMount).toBeDefined();
  expect(hostsMount).toMatch(/chroot-hosts:\/host\/etc\/hosts$/);
  expect(hostsMount).not.toContain(':ro');
});

Copilot uses AI. Check for mistakes.
Comment on lines 498 to 501
logger.debug(`Copied /etc/hosts to ${chrootHostsPath} for chroot host access`);
} catch {
// Fall back to empty file if host /etc/hosts is not readable
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n');
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file permissions on the created chroot-hosts file should be explicitly set to ensure it's writable by the container. While Docker mounts typically preserve permissions, the file will inherit the Node.js process umask which may vary. Since the entrypoint.sh needs to append to this file (line 197 in entrypoint.sh), ensuring it has the correct permissions would make the code more robust.

Consider adding explicit permission setting after file creation:

  • After fs.copyFileSync(): add fs.chmodSync(chrootHostsPath, 0o644);
  • In the fallback fs.writeFileSync(): add { mode: 0o644 } option

This is a defensive programming practice that would prevent potential issues in environments with restrictive umask settings.

Suggested change
logger.debug(`Copied /etc/hosts to ${chrootHostsPath} for chroot host access`);
} catch {
// Fall back to empty file if host /etc/hosts is not readable
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n');
fs.chmodSync(chrootHostsPath, 0o644);
logger.debug(`Copied /etc/hosts to ${chrootHostsPath} for chroot host access`);
} catch {
// Fall back to empty file if host /etc/hosts is not readable
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n', { mode: 0o644 });

Copilot uses AI. Check for mistakes.
- Clarify comment: both chroot AND host access required
- Add explicit chmod 0o644 on chroot-hosts file for robustness
- Use more precise sed pattern for cleanup to avoid removing unrelated entries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

💫 TO BE CONTINUED... Smoke Claude failed! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 82.16% 82.03% 📉 -0.13%
Statements 82.19% 82.07% 📉 -0.12%
Functions 81.95% 81.95% ➡️ +0.00%
Branches 75.48% 75.44% 📉 -0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 82.6% (-0.60%) 82.5% → 82.0% (-0.56%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

📰 DEVELOPING STORY: Smoke Copilot reports failed. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Security Review: Writable /etc/hosts Mount

I've identified one security consideration that warrants discussion:

⚠️ Writable /etc/hosts Mount in Chroot Mode

Location: src/docker-manager.ts:494-508

The PR changes /etc/hosts from read-only to writable when both enableChroot and enableHostAccess are enabled:

if (config.enableHostAccess) {
  const chrootHostsPath = path.join(config.workDir, 'chroot-hosts');
  // ... copy/create hosts file ...
  agentVolumes.push(`${chrootHostsPath}:/host/etc/hosts`);  // ⚠️ No :ro flag
}

Security Implications:

  1. DNS Poisoning Risk: Malicious agent code can modify /etc/hosts to redirect domains to attacker-controlled IPs
  2. Limited Cleanup: The cleanup logic (containers/agent/entrypoint.sh:332) only removes host.docker.internal entries - other malicious entries would persist until container cleanup
  3. Expanded Attack Surface: Previously all chroot system files were read-only; this creates a writable exception

Mitigations Present:

  • ✅ Only applies with both enableChroot=true AND enableHostAccess=true (explicit opt-in)
  • ✅ Mounts a copy in workDir, not the actual host's /etc/hosts
  • ✅ Capabilities dropped before user code execution
  • ✅ Isolated to container workDir (limited blast radius)

Recommendations:

  1. Consider alternatives: Could we use a read-only mount with host.docker.internal pre-injected? Or leverage Docker's host mapping more directly?

  2. Add validation: Enhance cleanup to detect unexpected modifications and log warnings

  3. Document the trade-off: Update security docs to note that --enable-host-access + --enable-chroot allows agent code to modify DNS resolution

Verdict

This is a calculated security trade-off for functionality, not a critical vulnerability. The feature is properly gated behind opt-in flags and the writable file is isolated. However, users should understand they're trusting agent code not to perform DNS manipulation when using both flags together.

cc: @Mossaka - Are there read-only alternatives we could explore, or is the writable mount necessary for the MCP server use case?

AI generated by Security Guard

The smoke tests use pre-built GHCR images, so entrypoint.sh changes
don't take effect. Move the host.docker.internal injection to
docker-manager.ts which resolves the Docker bridge gateway IP and
writes it directly to the chroot-hosts file before the container starts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

💫 TO BE CONTINUED... Smoke Claude failed! Our hero faces unexpected challenges...

try {
fs.copyFileSync('/etc/hosts', chrootHostsPath);
} catch {
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n');

Check failure

Code scanning / CodeQL

Insecure temporary file High

Insecure creation of file in
the os temp dir
.

Copilot Autofix

AI 1 day ago

In general, to fix insecure temporary file issues, you ensure that: (1) the directory in which you create files is safe (owned by the process, with restrictive permissions), (2) you avoid race conditions and symlink attacks (by using atomic operations or checking for symlinks before writing), and (3) you set restrictive file permissions at creation time instead of relying on defaults.

For this specific code, we should keep using config.workDir but harden the creation and handling of chrootHostsPath. A minimal and effective fix, without altering higher‑level behaviour, is:

  1. Ensure config.workDir exists with safe permissions before we use it (but we must not change code outside the shown snippet, so we’ll limit ourselves to the local path handling).
  2. When creating chrootHostsPath, use fs.openSync with exclusive creation flags (O_CREAT | O_EXCL) and a restrictive mode (e.g. 0o600) to avoid races and ensure the file is created with safe permissions. Then write the content via that descriptor.
  3. If the file already exists, open it with O_WRONLY | O_TRUNC and avoid following symlinks by verifying that the path is not a symlink using lstatSync and S_ISLNK logic (in Node, by checking stats.isSymbolicLink()).
  4. When appending the host.docker.internal entry, again verify that the file is not a symlink and open it safely before appending.

Because we can’t change imports except to add well‑known ones, and we already import fs and path, we’ll implement this logic directly using Node’s fs API. We’ll replace the current try { copyFileSync } catch { writeFileSync } and appendFileSync/chmodSync logic for chrootHostsPath with a small block that safely creates or truncates the file and then writes or appends through file descriptors, while still ending with a final chmodSync(chrootHostsPath, 0o644) to match existing functionality.

Suggested changeset 1
src/docker-manager.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/docker-manager.ts b/src/docker-manager.ts
--- a/src/docker-manager.ts
+++ b/src/docker-manager.ts
@@ -494,10 +494,41 @@
     // to the MCP gateway running on the host.
     if (config.enableHostAccess) {
       const chrootHostsPath = path.join(config.workDir, 'chroot-hosts');
+      // Safely create or truncate the chroot hosts file without following symlinks.
       try {
-        fs.copyFileSync('/etc/hosts', chrootHostsPath);
+        let fd: number | undefined;
+        try {
+          // Try to create the file exclusively with restrictive permissions.
+          fd = fs.openSync(chrootHostsPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
+        } catch (createErr: any) {
+          // If the file exists, ensure it is not a symlink, then truncate it.
+          if (createErr && createErr.code === 'EEXIST') {
+            const stats = fs.lstatSync(chrootHostsPath);
+            if (stats.isSymbolicLink()) {
+              throw new Error(`Refusing to use symlink for chroot hosts file at ${chrootHostsPath}`);
+            }
+            fd = fs.openSync(chrootHostsPath, fs.constants.O_WRONLY | fs.constants.O_TRUNC);
+          } else {
+            throw createErr;
+          }
+        }
+        if (fd === undefined) {
+          throw new Error(`Failed to open chroot hosts file at ${chrootHostsPath}`);
+        }
+        try {
+          // Populate the file with a copy of /etc/hosts, or a minimal localhost entry on failure.
+          try {
+            const etcHostsContent = fs.readFileSync('/etc/hosts');
+            fs.writeFileSync(fd, etcHostsContent);
+          } catch {
+            fs.writeFileSync(fd, '127.0.0.1 localhost\n');
+          }
+        } finally {
+          fs.closeSync(fd);
+        }
       } catch {
-        fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n');
+        // As a last resort, write a minimal localhost entry directly.
+        fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n', { mode: 0o600 });
       }
       // Resolve host-gateway IP from Docker bridge and append host.docker.internal
       try {
@@ -507,6 +536,11 @@
         ]);
         const hostGatewayIp = stdout.trim();
         if (hostGatewayIp) {
+          // Ensure we are not appending to a symlink.
+          const stats = fs.lstatSync(chrootHostsPath);
+          if (stats.isSymbolicLink()) {
+            throw new Error(`Refusing to append to symlink for chroot hosts file at ${chrootHostsPath}`);
+          }
           fs.appendFileSync(chrootHostsPath, `${hostGatewayIp}\thost.docker.internal\n`);
           logger.debug(`Added host.docker.internal (${hostGatewayIp}) to chroot-hosts`);
         }
EOF
@@ -494,10 +494,41 @@
// to the MCP gateway running on the host.
if (config.enableHostAccess) {
const chrootHostsPath = path.join(config.workDir, 'chroot-hosts');
// Safely create or truncate the chroot hosts file without following symlinks.
try {
fs.copyFileSync('/etc/hosts', chrootHostsPath);
let fd: number | undefined;
try {
// Try to create the file exclusively with restrictive permissions.
fd = fs.openSync(chrootHostsPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
} catch (createErr: any) {
// If the file exists, ensure it is not a symlink, then truncate it.
if (createErr && createErr.code === 'EEXIST') {
const stats = fs.lstatSync(chrootHostsPath);
if (stats.isSymbolicLink()) {
throw new Error(`Refusing to use symlink for chroot hosts file at ${chrootHostsPath}`);
}
fd = fs.openSync(chrootHostsPath, fs.constants.O_WRONLY | fs.constants.O_TRUNC);
} else {
throw createErr;
}
}
if (fd === undefined) {
throw new Error(`Failed to open chroot hosts file at ${chrootHostsPath}`);
}
try {
// Populate the file with a copy of /etc/hosts, or a minimal localhost entry on failure.
try {
const etcHostsContent = fs.readFileSync('/etc/hosts');
fs.writeFileSync(fd, etcHostsContent);
} catch {
fs.writeFileSync(fd, '127.0.0.1 localhost\n');
}
} finally {
fs.closeSync(fd);
}
} catch {
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n');
// As a last resort, write a minimal localhost entry directly.
fs.writeFileSync(chrootHostsPath, '127.0.0.1 localhost\n', { mode: 0o600 });
}
// Resolve host-gateway IP from Docker bridge and append host.docker.internal
try {
@@ -507,6 +536,11 @@
]);
const hostGatewayIp = stdout.trim();
if (hostGatewayIp) {
// Ensure we are not appending to a symlink.
const stats = fs.lstatSync(chrootHostsPath);
if (stats.isSymbolicLink()) {
throw new Error(`Refusing to append to symlink for chroot hosts file at ${chrootHostsPath}`);
}
fs.appendFileSync(chrootHostsPath, `${hostGatewayIp}\thost.docker.internal\n`);
logger.debug(`Added host.docker.internal (${hostGatewayIp}) to chroot-hosts`);
}
Copilot is powered by AI and may make mistakes. Always verify output.
The smoke tests used pre-built GHCR images (--image-tag 0.13.4) which
don't include recent fixes to entrypoint.sh and setup-iptables.sh
(host.docker.internal iptables bypass, NO_PROXY, etc.).

Update the postprocessor to also replace --image-tag/--skip-pull with
--build-local so containers are built from source, matching the AWF
binary which is already built from source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

💫 TO BE CONTINUED... Smoke Claude was cancelled! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests failed Smoke Chroot was cancelled - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 82.16% 82.03% 📉 -0.13%
Statements 82.19% 82.07% 📉 -0.12%
Functions 81.95% 81.95% ➡️ +0.00%
Branches 75.48% 75.44% 📉 -0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 82.6% (-0.60%) 82.5% → 82.0% (-0.56%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests failed Smoke Chroot was cancelled - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

💫 TO BE CONTINUED... Smoke Claude was cancelled! Our hero faces unexpected challenges...

Replace --image-tag 0.13.4 --skip-pull with --build-local in all smoke
and build-test lock.yml workflows so containers are built from source.
This ensures setup-iptables.sh has the host.docker.internal bypass code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 82.16% 82.03% 📉 -0.13%
Statements 82.19% 82.07% 📉 -0.12%
Functions 81.95% 81.95% ➡️ +0.00%
Branches 75.48% 75.44% 📉 -0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 82.6% (-0.60%) 82.5% → 82.0% (-0.56%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Security Review: Writable /etc/hosts Mount

I've identified a security concern in this PR that needs to be addressed before merging.

Issue: Writable /etc/hosts Creates DNS Redirection Risk

Location: src/docker-manager.ts:519

When both enableChroot and enableHostAccess are enabled, the PR mounts /etc/hosts as writable instead of read-only:

// Line 519 - No :ro flag means writable
agentVolumes.push(`${chrootHostsPath}:/host/etc/hosts`);

Security Impact:

Malicious code running inside the container could modify the hosts file to redirect DNS lookups to attacker-controlled IPs, bypassing the firewall's domain allowlist:

# Attacker could inject:
echo "192.168.1.100 api.github.com" >> /etc/hosts
# Now traffic to api.github.com (an allowed domain) goes to attacker's IP

While the entrypoint.sh cleanup logic (lines 329-332) removes the host.docker.internal entry on exit, it doesn't prevent malicious modifications during execution.

Recommended Mitigations

Option 1: Use Docker's native --add-host (Preferred)

Instead of modifying /etc/hosts, pass the mapping via Docker Compose's extra_hosts:

// In src/docker-manager.ts, when enableChroot && enableHostAccess:
const agent = {
  // ... existing config
  extra_hosts: [
    'host.docker.internal:host-gateway'  // Docker resolves host-gateway to bridge IP
  ]
};

This avoids file modifications entirely and is the recommended Docker approach.

Option 2: Make the mount read-only after injection

If file modification is necessary:

  1. Create chroot-hosts with host.docker.internal entry pre-injected (as you do now)
  2. Mount it as read-only: agentVolumes.push(${chrootHostsPath}:/host/etc/hosts:ro)

This prevents runtime modifications while still providing the mapping.

Option 3: Add integrity monitoring

If writable is required for other reasons:

  • Add a hash check in entrypoint.sh to detect unauthorized modifications
  • Log warnings if /etc/hosts is modified beyond the expected host.docker.internal entry

Why This Matters

The AWF's security model relies on L7 domain filtering. If an attacker can redirect allowed domains to arbitrary IPs, they can:

  • Exfiltrate data to attacker-controlled servers
  • Bypass egress restrictions
  • Undermine the firewall's purpose

Please consider implementing one of the mitigations above before merging.


Other Changes: The rest of the PR looks good from a security perspective - workflow changes and entrypoint logic are properly scoped.

AI generated by Security Guard

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Node.js Build Test Results ✅

Project Install Tests Status
clsx PASS PASS ✅
execa PASS PASS ✅
p-limit PASS PASS ✅

Overall: PASS ✅

All Node.js projects installed dependencies and passed their test suites successfully.

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Build Test: Rust - ❌ FAILED

Error: Rust toolchain not available in test environment.

Project Build Tests Status
fd N/A FAIL - cargo not found
zoxide N/A FAIL - cargo not found

Overall: FAIL

Issue

The test environment does not have Rust installed. The cargo command is required to build and test Rust projects.

Solution

The workflow needs to install Rust before running tests:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env

AI generated by Build Test Rust

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Go Build Test Results ✅

Project Download Tests Status
color 1/1 PASS ✅
env 1/1 PASS ✅
uuid 1/1 PASS ✅

Overall: PASS ✅

All Go projects successfully downloaded dependencies and passed tests.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

C++ Build Test Results

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS

All C++ projects built successfully.

AI generated by Build Test C++

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Smoke Test Results (Run ID: 21760357211)

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved merged PRs
  • ✅ Playwright: Navigated to GitHub, title contains "GitHub"
  • ✅ File Writing: Created /tmp/gh-aw/agent/smoke-test-copilot-21760357211.txt
  • ✅ Bash Tool: Verified file content successfully

Status: PASS 🎉

cc @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Claude Smoke Test Results

Last 2 merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data
  • ✅ Playwright: Page title "GitHub · Change is constant. GitHub keeps you ahead. · GitHub"
  • ✅ File creation: Created /tmp/gh-aw/agent/smoke-test-claude-21760357243.txt
  • ✅ Bash: Verified file content "Smoke test passed for Claude at Fri Feb 6 17:53:23 UTC 2026"

Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Build Test: Bun - Results

Project Install Tests Status
elysia 0/0 FAIL
hono 0/0 FAIL

Overall: FAIL

Error Details

Elysia Project:

  • Install: Failed with error: An internal error occurred (NotDir)
  • Test: Bun test crashed with Aborted (core dumped) (exit code 134)

Hono Project:

  • Install: Failed with error: An internal error occurred (NotDir)
  • Test: Bun test crashed with Aborted (core dumped) (exit code 134)

Environment Information

  • Bun Version: 1.3.8
  • System: Linux kernel 6.11.0-1018-azure (Ubuntu 24.04 container)
  • Repository: Successfully cloned Mossaka/gh-aw-firewall-test-bun

Root Cause

Both projects have minimal package.json files with no dependencies, and bun install fails with a NotDir error. Additionally, bun test crashes with a core dump, indicating a compatibility issue with the container environment.

The test repository appears to be a minimal test case that exposes compatibility issues with Bun running inside Docker containers or restricted environments.

AI generated by Build Test Bun

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

❌ Java Build Test Failed

Error: Maven installation is corrupted and unavailable.

Issue Details

-Dclassworlds.conf=/usr/share/apache-maven-3.9.12/bin/m2.conf: line 1: 
/usr/share/apache-maven-3.9.12/boot/plexus-classworlds-2.9.0.jar: 
cannot execute binary file: Exec format error

Test Results

Project Compile Tests Status
gson - FAILED
caffeine - FAILED

Overall: FAILED

Root Cause

The Apache Maven installation at /usr/share/apache-maven-3.9.12/ appears to be corrupted. The JAR file cannot be executed, preventing any Maven commands from running.

Required Action

The GitHub Actions runner needs a working Maven installation before this workflow can succeed.

AI generated by Build Test Java

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Chroot Version Comparison Test Results

Runtime Host Version Chroot Version Match?
Python 3.12.12 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall Result: ❌ Tests failed - version mismatches detected

The chroot environment successfully uses host binaries for Go, but Python and Node.js versions differ between host and container environments.

AI generated by Smoke Chroot

@Mossaka Mossaka merged commit 1a31005 into main Feb 6, 2026
79 of 81 checks passed
@Mossaka Mossaka deleted the fix/chroot-host-docker-internal-dns branch February 6, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant