Skip to content

fix: chmod gh-aw config dirs before privilege drop in entrypoint#1711

Open
Mossaka wants to merge 2 commits intomainfrom
fix/1463-chmod-gh-aw-config-files
Open

fix: chmod gh-aw config dirs before privilege drop in entrypoint#1711
Mossaka wants to merge 2 commits intomainfrom
fix/1463-chmod-gh-aw-config-files

Conversation

@Mossaka
Copy link
Copy Markdown
Collaborator

@Mossaka Mossaka commented Apr 6, 2026

Summary

  • On self-hosted runners where the GitHub Actions runner runs as root, gh-aw creates /tmp/gh-aw/ (MCP config) and /opt/gh-aw/safeoutputs/ (safe-output target) as root-owned directories. After AWF drops privileges via capsh --user=<host-user>, the unprivileged chroot user cannot read or write these paths.
  • Adds a chmod -R a+rwX block in entrypoint.sh before the privilege drop to make these directories accessible to the chroot user.

Closes #1463

Test plan

  • Verify on a self-hosted runner (root runner user, non-root host user) that MCP config at /tmp/gh-aw/mcp-config/config.toml is readable
  • Verify safe-output writes to /opt/gh-aw/safeoutputs/outputs.jsonl succeed
  • Verify no regression on GitHub-hosted runners (directories may not exist; the if -d guards skip gracefully)

🤖 Generated with Claude Code

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

github-actions bot commented Apr 6, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 86.20% 86.29% 📈 +0.09%
Statements 86.07% 86.16% 📈 +0.09%
Functions 87.41% 87.41% ➡️ +0.00%
Branches 78.57% 78.62% 📈 +0.05%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 86.6% → 87.0% (+0.39%) 86.1% → 86.5% (+0.38%)

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

Copy link
Copy Markdown
Contributor

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

Updates the agent container entrypoint to ensure gh-aw runtime/config directories created as root on self-hosted runners remain accessible after the chroot privilege drop, preventing silent failures in MCP tool discovery and safe-output writes (closes #1463).

Changes:

  • Recursively relaxes permissions on /host/tmp/gh-aw before executing the chrooted agent command.
  • Recursively relaxes permissions on /host/opt/gh-aw/safeoutputs before executing the chrooted agent command.
Show a summary per file
File Description
containers/agent/entrypoint.sh Adds a pre-privilege-drop permission fixup for host-mounted gh-aw config and safe-output directories in chroot mode.

Copilot's findings

Tip

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

  • Files reviewed: 1/1 changed files
  • Comments generated: 3

Comment on lines +741 to +745
chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

chmod -R a+rwX makes these host-mounted directories (especially /host/opt/gh-aw/safeoutputs) world-readable and world-writable, and the permission change persists on the host after the job. This is broader than necessary and can allow unintended cross-user tampering / data exposure on multi-user self-hosted runners. Consider changing ownership to the intended chroot user (e.g., chown -R ${HOST_UID}:${HOST_GID}) and using a more restrictive mode (e.g., user/group +rwX only) instead of granting access to a.

Suggested change
chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true
chown -R "${HOST_UID}:${HOST_GID}" /host/tmp/gh-aw 2>/dev/null || true
chmod -R ug+rwX,o-rwX /host/tmp/gh-aw 2>/dev/null || true
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
chown -R "${HOST_UID}:${HOST_GID}" /host/opt/gh-aw/safeoutputs 2>/dev/null || true
chmod -R ug+rwX,o-rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 4d51fdc. Replaced chmod -R a+rwX with chown -R "${HOST_UID}:${HOST_GID}" so that only the specific agent user gets access, not all users on the host. This addresses the world-writable concern on multi-user self-hosted runners.

Comment on lines +741 to +746
chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true
echo "[entrypoint] Made /host/opt/gh-aw/safeoutputs accessible to chroot user"
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The success logs here are unconditional even if chmod fails (errors are redirected and || true forces a 0 exit). This can be misleading when debugging permission issues on self-hosted runners. Consider only printing the success message when the chmod command succeeds, and logging a warning when it fails.

Suggested change
chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true
echo "[entrypoint] Made /host/opt/gh-aw/safeoutputs accessible to chroot user"
if chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null; then
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
else
echo "[entrypoint][WARN] Failed to make /host/tmp/gh-aw accessible to chroot user"
fi
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
if chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null; then
echo "[entrypoint] Made /host/opt/gh-aw/safeoutputs accessible to chroot user"
else
echo "[entrypoint][WARN] Failed to make /host/opt/gh-aw/safeoutputs accessible to chroot user"
fi

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 4d51fdc. The log messages are now conditional — success prints the ownership transfer with the UID:GID, and failure prints a [WARN] message. This makes debugging permission issues on self-hosted runners much easier.

Comment on lines +737 to +747
# Make gh-aw config directories readable/writable by the chroot user.
# On self-hosted runners these directories are created by the host-side
# gh-aw tooling as root, so the unprivileged chroot user cannot access them.
if [ -d /host/tmp/gh-aw ]; then
chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true
echo "[entrypoint] Made /host/tmp/gh-aw accessible to chroot user"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true
echo "[entrypoint] Made /host/opt/gh-aw/safeoutputs accessible to chroot user"
fi
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This change fixes an important chroot-mode failure mode on root-runner self-hosted setups, but there isn’t a regression test covering it. Adding an integration test that creates root-owned /tmp/gh-aw and /opt/gh-aw/safeoutputs on the host and then verifies the chrooted agent can read the MCP config and write safe-outputs would help prevent future breakage.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good suggestion. This would require an integration test that creates root-owned directories and verifies the chroot agent can still access them. Deferring this to a follow-up since it needs Docker/sudo and would be better suited as a chroot integration test in the existing test suite. The smoke tests passing on this PR provide some coverage in the meantime.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Security Review

The fix correctly addresses the root cause (root-owned directories inaccessible after privilege drop), and the if [ -d ... ] guards and placement before the privilege drop are both correct.

However, chmod -R a+rwX is unnecessarily broad and introduces a security concern:

Issue: World-writable permissions on security-sensitive directories

File: containers/agent/entrypoint.sh (new lines ~737–745)

chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true
# ...
chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true

a+rwX grants read + write to all users (owner, group, and others). Both paths live on the host filesystem (bind-mounted as /host), so this affects any process running on the host machine, not just the agent container.

Concrete risks:

  1. /host/tmp/gh-aw (MCP config) — world-writable MCP config allows any unprivileged host process to modify MCP server definitions before or during the agent run. On self-hosted runners that may run multiple jobs concurrently, a compromised or untrusted job could tamper with another job's MCP configuration.

  2. /host/opt/gh-aw/safeoutputs (outputs.jsonl) — world-writable safe-outputs allows any host process to inject content into outputs.jsonl, which could produce fraudulent GitHub issue comments or PR actions attributed to the agent.

Recommended fix

HOST_UID and HOST_GID are already set earlier in the same script (lines ~8–9). Use chown instead of chmod to limit access to the specific agent user only:

if [ -d /host/tmp/gh-aw ]; then
  chown -R "$HOST_UID:$HOST_GID" /host/tmp/gh-aw 2>/dev/null || true
  echo "[entrypoint] Transferred /host/tmp/gh-aw ownership to chroot user ($HOST_UID:$HOST_GID)"
fi
if [ -d /host/opt/gh-aw/safeoutputs ]; then
  chown -R "$HOST_UID:$HOST_GID" /host/opt/gh-aw/safeoutputs 2>/dev/null || true
  echo "[entrypoint] Transferred /host/opt/gh-aw/safeoutputs ownership to chroot user ($HOST_UID:$HOST_GID)"
fi

This gives the agent user full access (as owner) without granting write permission to arbitrary other users on the host.

Generated by Security Guard for issue #1711 · ● 99.7K ·

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@Mossaka
Copy link
Copy Markdown
Collaborator Author

Mossaka commented Apr 6, 2026

Security Review

[CONCERN] chmod -R a+rwX /host/tmp/gh-aw is overly broad — covers security-sensitive subdirectories

  • File: containers/agent/entrypoint.sh (new lines in the diff)

  • Code: chmod -R a+rwX /host/tmp/gh-aw 2>/dev/null || true

  • Explanation: /tmp/gh-aw is a shared tree that contains multiple subdirectories with different security profiles:

    • /tmp/gh-aw/mcp-config/ — MCP server configuration (the stated target of this fix)
    • /tmp/gh-aw/aw-prompts/ — system prompt files
    • /tmp/gh-aw/sandbox/ — sandbox logs and agent output
    • /tmp/gh-aw/mcp-logs/ — MCP logs (already protected by tmpfs overlay, so this chmod is harmless for this subpath)
    • /tmp/gh-aw/safeoutputs/ — safe-output staging area read by post-agent steps

    Making the entire tree world-writable means the agent can modify the MCP config, prompts, and sandbox log directories. While the agent is already inside the container and many of these files are consumed before the agent runs, the recursive write permission on /tmp/gh-aw/safeoutputs/ is particularly concerning: the agent could tamper with safe-output artifacts that downstream workflow steps trust.

  • Suggested action: Scope the chmod to only the specific subdirectories the agent actually needs. For example:

    if [ -d /host/tmp/gh-aw/mcp-config ]; then
      chmod -R a+rwX /host/tmp/gh-aw/mcp-config 2>/dev/null || true
    fi

    If other subdirectories are also needed, enumerate them explicitly rather than opening the whole tree. This follows least-privilege principles.

[CONCERN] chmod -R a+rwX /host/opt/gh-aw/safeoutputs allows agent to tamper with safe-output config and validation

  • File: containers/agent/entrypoint.sh (new lines in the diff)

  • Code: chmod -R a+rwX /host/opt/gh-aw/safeoutputs 2>/dev/null || true

  • Explanation: The safe-outputs directory contains three categories of files:

    1. outputs.jsonl — the actual output file the agent writes to (write access needed, this is correct)
    2. config.json — safe-output configuration controlling output behavior
    3. tools.json / validation.json — tool definitions and validation rules that constrain what the agent can output

    Making the entire directory world-writable means the agent can modify config.json, tools.json, and validation.json — effectively rewriting the rules that govern its own output. A compromised agent could relax validation constraints and then write arbitrary outputs that downstream steps trust.

  • Suggested action: Consider one of:

    • (a) Only chmod the outputs.jsonl file (or create it with the right ownership): chmod a+rw /host/opt/gh-aw/safeoutputs/outputs.jsonl 2>/dev/null || true
    • (b) Make the directory itself writable but keep config/tools/validation files read-only: chmod a+rwX /host/opt/gh-aw/safeoutputs && chmod a+rw /host/opt/gh-aw/safeoutputs/outputs.jsonl
    • (c) Use chown to the target UID instead of world-writable permissions: chown -R ${HOST_UID}:${HOST_GID} /host/opt/gh-aw/safeoutputs — this is more precise and doesn't weaken permissions for other users/processes.

[INFO] a+rwX vs targeted ownership

  • Explanation: Both chmod blocks use a+rwX (all users: read, write, and execute-if-directory). Since the entrypoint already has HOST_UID and HOST_GID variables from the UID/GID mapping logic earlier in the script, using chown -R ${HOST_UID}:${HOST_GID} would be strictly more precise — it grants access only to the specific user the agent will run as, rather than world-writable. This matters in multi-user or shared-runner scenarios.
  • Suggested action: Replace chmod -R a+rwX with chown -R ${HOST_UID}:${HOST_GID} for both paths. This achieves the same goal (the chroot user can access the files) without making them world-writable.

[INFO] No iptables or network security impact

  • The changes do not affect setup-iptables.sh, host-iptables.ts, squid-config.ts, or any network filtering logic.
  • No command injection risk — the paths are hardcoded string literals, not derived from user input.
  • No path traversal risk — the -d guards check for directory existence of fixed paths.
  • The tmpfs overlay on /host/tmp/gh-aw/mcp-logs takes precedence over the chmod, so MCP log hiding is unaffected.

Summary

The fix addresses a real problem (root-owned directories inaccessible to the unprivileged agent on self-hosted runners), but the permissions are broader than necessary. The two main concerns are:

  1. /host/tmp/gh-aw chmod is recursive over the entire tree rather than scoped to mcp-config (the stated need).
  2. /host/opt/gh-aw/safeoutputs chmod makes config and validation files writable, allowing the agent to tamper with its own output constraints.

Recommended fix: use chown -R ${HOST_UID}:${HOST_GID} instead of chmod -R a+rwX, and scope /tmp/gh-aw to only the subdirectories the agent needs.

Replace `chmod -R a+rwX` with `chown -R $HOST_UID:$HOST_GID` to avoid
making gh-aw config directories world-writable on the host filesystem.
Also make success/failure log messages conditional on the actual command
result to aid debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Mossaka
Copy link
Copy Markdown
Collaborator Author

Mossaka commented Apr 6, 2026

Addressed all review feedback in 4d51fdc:

Security (Copilot + Security Guard): Replaced chmod -R a+rwX with chown -R "${HOST_UID}:${HOST_GID}" for both /host/tmp/gh-aw and /host/opt/gh-aw/safeoutputs. This transfers ownership to the specific agent user instead of making directories world-writable, eliminating the cross-user tampering risk on multi-user self-hosted runners.

Logging: Made success/failure messages conditional on the actual chown exit code. Success now logs the UID:GID used; failure logs a [WARN] message to aid debugging.

Integration test: Deferred to a follow-up — requires Docker/sudo setup and is better suited as a chroot integration test.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Smoke Test Results — Run 24044349061

Test Result
GitHub MCP: "Fix Secret Digger..." (#1704), "⚡ pelis-agent-factory-advisor..." (#1701)
Playwright: github.com title contains "GitHub"
File write: /tmp/gh-aw/agent/smoke-test-claude-24044349061.txt
Bash verify: file contents confirmed

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Smoke test matrix (titles only):

  • GitHub MCP last 2 merged PRs: ✅
  • Safe Inputs GH CLI (pr list --limit 2): ❌
  • Playwright title contains GitHub: ❌
  • Tavily web search returned >=1 item: ❌
  • File write /tmp/gh-aw/agent/smoke-test-codex-24044349001.txt: ✅
  • Bash cat verification: ✅
  • Discussion interaction + mystical oracle note: ❌
  • Build npm ci && npm run build: ✅
    Overall status: FAIL

🔮 The oracle has spoken through Smoke Codex

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

🤖 Smoke Test Results

Test Result
GitHub MCP connectivity
GitHub.com HTTP connectivity
File write/read

PR: fix: chmod gh-aw config dirs before privilege drop in entrypoint
Author: @Mossaka

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot

@github-actions github-actions bot mentioned this pull request Apr 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Smoke Test: GitHub Actions Services Connectivity

Check Result Details
Redis PING (redis-cli) ❌ FAILED redis-cli not installed; port 6379 timed out (blocked by AWF dangerous-port rules)
PostgreSQL pg_isready ❌ FAILED host.docker.internal:5432 — no response (port blocked)
PostgreSQL SELECT 1 ❌ FAILED Could not connect (port 5432 blocked)

Root cause: The AWF sandbox iptables rules block database ports (Redis 6379, PostgreSQL 5432) as "dangerous ports". The --allow-host-service-ports flag would be needed to bypass this blocklist for host gateway connections.

🔌 Service connectivity validated by Smoke Services

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.14.1 v20.20.2
Go go1.22.12 go1.22.12

Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environment.

Tested by Smoke Chroot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants