Skip to content

fix: chmod mkdtempSync dirs and add file-permissions-checker CI#6311

Merged
lpcox merged 3 commits into
mainfrom
fix/chmod-mkdtemp-and-permissions-ci
Jul 16, 2026
Merged

fix: chmod mkdtempSync dirs and add file-permissions-checker CI#6311
lpcox merged 3 commits into
mainfrom
fix/chmod-mkdtemp-and-permissions-ci

Conversation

@lpcox

@lpcox lpcox commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

Smoke CI run 29526686386 fails with:

[ERROR] Fatal error: Error: EACCES: permission denied, open '/tmp/awf-chroot-v7CTUn/hosts'

PR #6285 (merged today) added a diagnostic + fallback for the initial EACCES, but the fallback itself uses the same broken mkdtempSync pattern — creating a new temp dir that also gets mode=600 from the runner's restrictive umask (0177).

Root Cause

On Ubuntu 24.04 runners (ubuntu24/20260714.240), the process umask is 0177. Node.js mkdtempSync applies the umask to the default 0700 mode, resulting in directories with 0600 (missing the execute bit). Without the execute bit, no files can be created inside the directory → EACCES.

Fix

1. src/services/agent-volumes/hosts-file.ts

Added fs.chmodSync(dir, 0o700) after both mkdtempSync calls (primary path and fallback). chmodSync is not affected by umask — it sets the exact mode.

2. New CI: .github/workflows/file-permissions-checker.yml

Comprehensive checker that prevents this entire class of bug from recurring:

Static analysis (errors = CI fails):

  • mkdtempSync without a following chmodSync

Static analysis (warnings = informational):

  • mkdirSync with mode but no chmodSync backup
  • Cleanup file operations without try/catch (identifies 19 instances)
  • execa.sync('chmod') without error handling

Runtime tests (8 tests, run under simulated umask 0177):

  1. mkdtemp + chmod works
  2. mkdtemp WITHOUT chmod fails (validates threat model)
  3. mkdirSync + chmod works
  4. Exact hosts-file.ts pattern end-to-end
  5. Nested 3-level directory creation
  6. Artifact preservation (rename + readdir + rm)
  7. Cross-UID cleanup (chmod-repair-retry pattern)
  8. Chroot-home mountpoint loop

Runs on both ubuntu-latest and ubuntu-24.04 matrices and under forced restrictive umask.

Testing

  • All 9 existing hosts-file tests pass
  • New permissions checker passes locally (8/8 runtime tests, 0 static errors)
  • 23 warnings identified for future hardening (non-blocking)

Related

The EACCES fallback in hosts-file.ts (PR #6285) still crashed because the
fallback path also used mkdtempSync without overriding the restrictive
umask (0177) that some Ubuntu 24.04 runners apply. Both the primary and
fallback mkdtempSync now get an explicit chmodSync(dir, 0o700).

Additionally, adds a comprehensive file-permissions-checker CI workflow
that catches this class of bug via:

- Static analysis: detects mkdtempSync without chmod, mkdirSync mode
  without chmod, unguarded cleanup file ops, execa chmod without
  error handling
- Runtime tests: validates all critical directory creation patterns
  under a simulated restrictive umask (0177)

Closes github/gh-aw#46062

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8bef4805-c013-4d25-908f-125577735990
Copilot AI review requested due to automatic review settings July 16, 2026 19:41
The cross-uid-cleanup-resilience test creates a directory then writes
a file into it. Under umask 0177 (as set by the CI step), mkdirSync
creates the dir with mode 0600 (no execute bit), so the writeFileSync
fails before we even get to restrict the dir to 0555.

Fix: chmod the dir to 0755 immediately after creation, then restrict
it after writing the test file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8bef4805-c013-4d25-908f-125577735990
@github-actions

Copy link
Copy Markdown
Contributor

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 99.01% 99.04% 📈 +0.03%
Statements 98.95% 98.98% 📈 +0.03%
Functions 99.35% 99.35% ➡️ +0.00%
Branches 95.15% 95.15% ➡️ +0.00%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/log-directory-setup.ts 96.2% → 100.0% (+3.78%) 96.3% → 100.0% (+3.71%)

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes restrictive-umask failures when creating chroot hosts files and adds CI permission checks.

Changes:

  • Explicitly applies 0700 permissions to primary and fallback temporary directories.
  • Adds static permission analysis and runtime checks.
  • Adds a dedicated CI workflow.
Show a summary per file
File Description
src/services/agent-volumes/hosts-file.ts Hardens temporary directory permissions.
scripts/ci/check-file-permissions.ts Adds static and runtime permission checks.
.github/workflows/file-permissions-checker.yml Runs the new checker in CI.

Review details

Tip

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

  • Files reviewed: 3/3 changed files
  • Comments generated: 5
  • Review effort level: Medium

jobs:
file-permissions-check:
name: Check File Permission Patterns
runs-on: ubuntu-latest
Comment thread scripts/ci/check-file-permissions.ts Outdated
Comment on lines +52 to +54
// Extract the variable name assigned from mkdtempSync
const assignMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(?:fs\.)?mkdtempSync/);
if (!assignMatch) continue;
fs.unlinkSync(testFile);
return {
name: 'mkdtempSync-without-chmod-fails (umask enforcement)',
passed: true,
Comment on lines +83 to +85
// Create a chroot subdir like hosts-file.ts does
const chrootDir = fs.mkdtempSync(path.join(workDir, 'chroot-'));
fs.chmodSync(chrootDir, 0o700);
Comment thread scripts/ci/check-file-permissions.ts Outdated
Comment on lines +620 to +624
// Create a subdirectory and remove write permission (simulates root-owned dir)
const restrictedDir = path.join(testDir, 'restricted');
fs.mkdirSync(restrictedDir);
fs.writeFileSync(path.join(restrictedDir, 'file.txt'), 'data');
fs.chmodSync(restrictedDir, 0o555); // read+execute only
Comment thread scripts/ci/check-file-permissions.ts Fixed
- Wire matrix.os to runs-on so ubuntu-24.04 actually runs on that image
- Handle multiline mkdtempSync assignments in static analysis
  (look back up to 3 lines for split const/let declarations)
- Negative test now fails on Linux if EACCES can't be reproduced
  (validates the threat model is testable on the CI environment)
- Integration test imports actual built generateHostsFileMount function
  instead of reimplementing the pattern inline
- Rename cross-uid test to chmod-repair-retry-pattern (honest about
  what it tests: the local chmod+retry logic, not Docker-based repair)
- Add explicit mode to all writeFileSync calls (fixes CodeQL
  js/insecure-temporary-file alert)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8bef4805-c013-4d25-908f-125577735990
@github-actions

Copy link
Copy Markdown
Contributor

✅ Copilot review passed with no inline comments.

@lpcox Add the ready-for-aw label to this PR to trigger agentic CI smoke tests.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Smoke Gemini completed. All facets verified. 💎

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Build Test Suite completed successfully!

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (api-key) reports failed. AOAI BYOK (api-key) mode investigation needed...

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🔑 Smoke Copilot PAT PAT auth validated. All systems operational. ✅

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Contribution Check completed successfully!

PR #6311 follows the applicable CONTRIBUTING.md guidelines: clear description with related references, appropriate file organization, tests/runtime coverage for the permission fix, and no documentation update is required for this internal bug fix.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Smoke Claude passed

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (Entra) reports failed. AOAI BYOK (Entra) mode investigation needed...

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🔌 Smoke Services — All services reachable! ✅

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Security Guard has started processing this pull request

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Claude Engine Validation

Check Result
API Status ✅ PASS
GH Check ✅ PASS
File Status ✅ PASS

Overall Result: PASS

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Smoke Claude for #6311 · 56.1 AIC · ⊞ 3.3K ·
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: PAT Auth Validation

Test Result
GitHub MCP connectivity
GitHub.com HTTP ✅ (200)
File write/read ⚠️ (template vars unexpanded in runner)

Overall: PASS
Auth mode: PAT (COPILOT_GITHUB_TOKEN) | @lpcox

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🔑 PAT report filed by Smoke Copilot PAT
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

🔬 Smoke Test Results

Test Result
GitHub MCP Connectivity
GitHub.com HTTP ⚠️ pre-step vars unexpanded
File Write/Read ⚠️ pre-step vars unexpanded

Overall: PASS (engine reachable; template vars not resolved in this run)

@lpcox — smoke test complete.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

📰 BREAKING: Report filed by Smoke Copilot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK (Direct) Mode

Test Result
GitHub MCP (list PRs)
GitHub.com connectivity
File write/read
BYOK inference (agent → api-proxy → api.githubcopilot.com)

Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY) via api-proxy → api.githubcopilot.com

Overall: PASS

@lpcox

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🔑 BYOK report filed by Smoke Copilot BYOK
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Gemini Smoke Test Results

  • PR Reviews: ❌ (Access Denied by Secrecy Policy)
  • Connectivity: ❌ (No Internet Access)
  • File Writing: ✅
  • Bash Tool: ✅

Overall Status: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • localhost

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "localhost"

See Network Configuration for more information.

💎 Faceted by Smoke Gemini
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: GitHub Actions Services Connectivity

  • Redis PING: ❌ (host.docker.internal DNS fails; 172.17.0.1 unreachable)
  • PostgreSQL pg_isready: ❌ (no response)
  • PostgreSQL SELECT 1: ❌ (cannot connect)

Overall: FAIL — service containers are not reachable from this runner environment.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🔌 Service connectivity validated by Smoke Services
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smoke Test: API Proxy OpenTelemetry Tracing

Scenario Result Details
Module Loading ✅ Pass otel.js loads successfully; exports: startRequestSpan, setTokenAttributes, setBudgetAttributes, endSpan, endSpanError, shutdown, isEnabled + internal helpers
Test Suite ✅ Pass 39/39 tests passed in otel.test.js — covers ProxyAwareOtlpExporter, FileSpanExporter, FanOutSpanExporter, shutdown, span lifecycle
Env Var Forwarding ✅ Pass src/services/api-proxy-env-config.ts forwards GH_AW_OTLP_ENDPOINTS, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, GITHUB_AW_OTEL_TRACE_ID, GITHUB_AW_OTEL_PARENT_SPAN_ID, OTEL_SERVICE_NAME to api-proxy container
Token Tracker Integration ✅ Pass onUsage callback exists at line 343 of token-tracker-http.js — OTEL hook point is wired
OTEL Diagnostics ⚠️ N/A DinD not available in sandbox; file-based fallback (/var/log/api-proxy/otel.jsonl) is the graceful-degradation path when no OTLP endpoint is configured

Summary: All 4 directly testable scenarios pass. Scenario 5 (live span export) requires a running container and is expected-pending in this sandbox. No unexpected failures.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

📡 OTel tracing validated by Smoke OTel Tracing
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smoke Test: Docker Sbx Validation

Test Result
GitHub MCP ✅ Connected (filtered by secrecy policy — expected)
GitHub.com HTTP ⚠️ N/A — template vars not substituted
File Write/Read ⚠️ N/A — template vars not substituted

Overall: PARTIAL — MCP connectivity confirmed; pre-computed step outputs were not injected (template substitution failed in workflow).

PR #6311 author: @lpcox

📰 BREAKING: Report filed by Smoke Docker Sbx
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.18.0 v22.23.1 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

ALL_TESTS_PASSED: false — Python and Node.js versions differ between host and chroot environments.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Tested by Smoke Chroot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx passed ✅ PASS
Node.js execa passed ✅ PASS
Node.js p-limit passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Build Test Suite for #6311 · 42.2 AIC · ⊞ 6.9K ·
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test

Deduplicate OIDC test env isolation across API proxy config suites ✅
Refactor duplicated hosts-file mount test harness setup ✅
GitHub page title check ❌
File write/cat ✅
Discussion comment ✅
Build ❌ (npm ci registry 403)
Overall: FAIL

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • awmgmcpg
  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex
Add label ready-for-aw to run again

@lpcox
lpcox merged commit 8a12aca into main Jul 16, 2026
135 of 137 checks passed
@lpcox
lpcox deleted the fix/chmod-mkdtemp-and-permissions-ci branch July 16, 2026 21:05
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.

[aw] Smoke CI failed

3 participants