Skip to content

fix: only hide credential files if parent directory exists#737

Merged
lpcox merged 3 commits intoclaude/fix-gh-actions-workflowfrom
claude/fix-github-actions-workflow-again
Feb 12, 2026
Merged

fix: only hide credential files if parent directory exists#737
lpcox merged 3 commits intoclaude/fix-gh-actions-workflowfrom
claude/fix-github-actions-workflow-again

Conversation

@Claude
Copy link
Contributor

@Claude Claude AI commented Feb 12, 2026

Docker mount failures occur when AWF attempts to mount /dev/null over credential files whose parent directories don't exist on the host. The container's rootfs is read-only during initialization, preventing Docker from creating missing parent directories as mountpoints.

Changes

  • Check parent directory existence before adding /dev/null mounts for credential files
  • Applied to both normal mode (~/.cargo/credentials) and chroot mode (/host/.cargo/credentials)
  • Maintains security by still hiding credentials where parent directories exist
  • Logs skipped credential paths for debugging

Implementation

// Before: Always mount /dev/null over credential files
credentialFiles.forEach(credFile => {
  agentVolumes.push(`/dev/null:${credFile}:ro`);
});

// After: Only mount if parent directory exists
credentialFiles.forEach(credFile => {
  const parentDir = path.dirname(credFile);
  if (fs.existsSync(parentDir)) {
    agentVolumes.push(`/dev/null:${credFile}:ro`);
    hiddenCount++;
  }
});

This prevents mount failures in GitHub Actions runners and other environments where credential directories may not be pre-created, while preserving the security model of hiding sensitive files where their parent directories do exist.

Prevents Docker mount errors when credential file parent directories
don't exist on the host. This fixes the "read-only file system" error
when Docker tries to create mountpoints for non-existent parents.

The fix checks if the parent directory exists before adding /dev/null
mounts for credential files in both normal and chroot modes. This
maintains security while avoiding mount failures.

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
@Claude Claude AI changed the title [WIP] Fix failing GitHub Actions workflow agent fix: only hide credential files if parent directory exists Feb 12, 2026
@Claude Claude AI requested a review from lpcox February 12, 2026 05:50
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

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

@github-actions github-actions bot mentioned this pull request Feb 12, 2026
* Initial plan

* fix: replace /dev/null mounts with tmpfs for credential hiding

This fixes Docker mount errors like "read-only file system" that occur when
mounting /dev/null over credential files whose parent directories don't exist
in the container's filesystem.

The solution uses tmpfs mounts instead, which create empty in-memory filesystems
that overlay directories without requiring the target path to exist first.

Changes:
- Normal mode: Hide credential directories (~/.docker, ~/.ssh, ~/.aws, etc.) using tmpfs
- Chroot mode: Hide credential directories at /host paths using tmpfs
- Updated DockerService type to include tmpfs property
- Updated tests to verify tmpfs behavior instead of /dev/null mounts
- Fixed config mutation bug by using local variable instead of mutating config object

Closes the GitHub Actions failure where cargo credentials mounting failed with:
"error mounting "/dev/null" to rootfs at "/host/home/runner/.cargo/credentials":
create mountpoint ... read-only file system"

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

* fix: prevent .cargo volume/tmpfs mount conflict in chroot mode (#740)

* Initial plan

* fix: prevent .cargo volume/tmpfs mount conflict in chroot mode

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

* fix: handle missing docker-compose.yml during cleanup gracefully (#741)

* Initial plan

* fix: handle missing docker-compose.yml during cleanup gracefully

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
@lpcox lpcox marked this pull request as ready for review February 12, 2026 06:33
Copilot AI review requested due to automatic review settings February 12, 2026 06:33
@lpcox lpcox merged commit fce04c3 into claude/fix-gh-actions-workflow Feb 12, 2026
13 checks passed
@lpcox lpcox deleted the claude/fix-github-actions-workflow-again branch February 12, 2026 06:33
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

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

lpcox added a commit that referenced this pull request Feb 12, 2026
* Initial plan

* fix: create .copilot/logs mountpoint before docker mount

The agent container mount at ~/.copilot:ro followed by
~/agent-logs:~/.copilot/logs:rw fails in GitHub Actions because Docker
cannot create the logs subdirectory inside a read-only mount.

Fix by creating ~/.copilot/logs on the host before mounting, so Docker
doesn't need to create the mountpoint inside the read-only parent.

This affects GitHub Actions runners where ~/.copilot doesn't exist
before AWF runs.

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

* fix: only hide credential files if parent directory exists (#737)

* Initial plan

* fix: only hide credential files if parent directory exists

Prevents Docker mount errors when credential file parent directories
don't exist on the host. This fixes the "read-only file system" error
when Docker tries to create mountpoints for non-existent parents.

The fix checks if the parent directory exists before adding /dev/null
mounts for credential files in both normal and chroot modes. This
maintains security while avoiding mount failures.

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

* fix: replace /dev/null mounts with tmpfs for credential hiding (#738)

* Initial plan

* fix: replace /dev/null mounts with tmpfs for credential hiding

This fixes Docker mount errors like "read-only file system" that occur when
mounting /dev/null over credential files whose parent directories don't exist
in the container's filesystem.

The solution uses tmpfs mounts instead, which create empty in-memory filesystems
that overlay directories without requiring the target path to exist first.

Changes:
- Normal mode: Hide credential directories (~/.docker, ~/.ssh, ~/.aws, etc.) using tmpfs
- Chroot mode: Hide credential directories at /host paths using tmpfs
- Updated DockerService type to include tmpfs property
- Updated tests to verify tmpfs behavior instead of /dev/null mounts
- Fixed config mutation bug by using local variable instead of mutating config object

Closes the GitHub Actions failure where cargo credentials mounting failed with:
"error mounting "/dev/null" to rootfs at "/host/home/runner/.cargo/credentials":
create mountpoint ... read-only file system"

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

* fix: prevent .cargo volume/tmpfs mount conflict in chroot mode (#740)

* Initial plan

* fix: prevent .cargo volume/tmpfs mount conflict in chroot mode

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

* fix: handle missing docker-compose.yml during cleanup gracefully (#741)

* Initial plan

* fix: handle missing docker-compose.yml during cleanup gracefully

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

---------

Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation...

Copy link
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

This PR aims to prevent Docker mount failures during container initialization while preserving AWF’s credential-hiding security model by changing how sensitive paths are hidden in the generated Docker Compose config.

Changes:

  • Add tmpfs?: string[] support to the DockerService type for Docker Compose generation.
  • Switch credential hiding from /dev/null bind mounts to tmpfs-based mounts (and update unit tests accordingly).
  • Add a defensive fallback in stopContainers() to stop containers by name when docker-compose.yml is missing.

Reviewed changes

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

File Description
src/types.ts Extends the Docker Compose service type to support tmpfs mounts and documents intended usage.
src/docker-manager.ts Updates compose generation to use tmpfs mounts for hiding credential locations; also adds safer container shutdown fallback.
src/docker-manager.test.ts Updates/extends tests to assert tmpfs-based credential hiding and .cargo mount behavior.
Comments suppressed due to low confidence (1)

src/docker-manager.ts:751

  • Same issue as normal mode: adding a tmpfs mount at /host${userHome}/.npmrc targets what is normally a file path. This can cause Docker mount failures (file vs directory) or turn .npmrc into an empty directory. Use a /dev/null bind mount for the file (with a parent-dir existence check) if you need to hide only that file, or adjust the tmpfs strategy to mount a directory path.
    credentialTmpfsMounts.push(`/host${userHome}/.npmrc:rw,noexec,nosuid,size=1m`);

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

Comment on lines +716 to +723
// Also hide ~/.npmrc file (NPM registry tokens) - needs special handling as it's a file
// Mount its parent directory as tmpfs to hide it
const npmrcParent = effectiveHome;
if (!credentialTmpfsMounts.some(mount => mount.startsWith(`${npmrcParent}:`))) {
// Only add if we're not already mounting the entire home directory
// In practice, we'll mount ~/.npmrc as a tmpfs (which will be an empty directory)
credentialTmpfsMounts.push(`${effectiveHome}/.npmrc:rw,noexec,nosuid,size=1m`);
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

tmpfs mounts are directory mounts, but this adds a tmpfs at ${effectiveHome}/.npmrc which is typically a file. If .npmrc exists as a file in the image/home, Docker will likely fail with a "not a directory" error; if it doesn’t exist, Docker may create a directory named .npmrc, which can break tooling in unexpected ways. Prefer hiding .npmrc with a file-level /dev/null bind mount guarded by a parent-dir existence check, or mount a directory tmpfs at a valid directory path and handle .npmrc within that design.

This issue also appears on line 751 of the same file.

Suggested change
// Also hide ~/.npmrc file (NPM registry tokens) - needs special handling as it's a file
// Mount its parent directory as tmpfs to hide it
const npmrcParent = effectiveHome;
if (!credentialTmpfsMounts.some(mount => mount.startsWith(`${npmrcParent}:`))) {
// Only add if we're not already mounting the entire home directory
// In practice, we'll mount ~/.npmrc as a tmpfs (which will be an empty directory)
credentialTmpfsMounts.push(`${effectiveHome}/.npmrc:rw,noexec,nosuid,size=1m`);
}
// NOTE: ~/.npmrc is typically a file, not a directory. Avoid adding it to the tmpfs
// directory mounts here to prevent Docker "not a directory" errors when it exists
// as a file in the image or home directory. File-level hiding (e.g. bind-mounting
// /dev/null over ~/.npmrc) should be implemented via a separate bind-mount mechanism.

Copilot uses AI. Check for mistakes.
Comment on lines +694 to +699
// SECURITY: Hide credential directories using tmpfs (empty in-memory filesystem)
// This prevents prompt-injected commands from reading sensitive tokens
// even if the attacker knows the file paths
const credentialFiles = [
`${effectiveHome}/.docker/config.json`, // Docker Hub tokens
`${effectiveHome}/.npmrc`, // NPM registry tokens
`${effectiveHome}/.cargo/credentials`, // Rust crates.io tokens
`${effectiveHome}/.composer/auth.json`, // PHP Composer tokens
`${effectiveHome}/.config/gh/hosts.yml`, // GitHub CLI OAuth tokens
// SSH private keys (CRITICAL - server access, git operations)
`${effectiveHome}/.ssh/id_rsa`,
`${effectiveHome}/.ssh/id_ed25519`,
`${effectiveHome}/.ssh/id_ecdsa`,
`${effectiveHome}/.ssh/id_dsa`,
// Cloud provider credentials (CRITICAL - infrastructure access)
`${effectiveHome}/.aws/credentials`,
`${effectiveHome}/.aws/config`,
`${effectiveHome}/.kube/config`,
`${effectiveHome}/.azure/credentials`,
`${effectiveHome}/.config/gcloud/credentials.db`,
// even if the attacker knows the file paths.
// Using tmpfs instead of /dev/null mounts avoids Docker errors when parent directories
// don't exist in the container filesystem.
const credentialDirs = [
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The PR description/title talk about conditionally adding /dev/null mounts based on parent directory existence and logging skipped paths, but the implementation here switches to tmpfs-based directory hiding and doesn’t perform existence checks or log skipped credential paths. Either update the PR description/title to match the new approach or adjust the code to implement the described parent-dir checks + skip logging.

Copilot uses AI. Check for mistakes.
// 1. MCP server logs at /tmp/gh-aw/mcp-logs
// 2. Credential files/directories (when not using --allow-full-filesystem-access)
tmpfs: (() => {
const tmpfsMounts = [];
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

const tmpfsMounts = [] is inferred as any[] under TypeScript, which weakens type-safety in a strict project. Declare this as string[] so accidental non-string entries (or later refactors) are caught at compile time.

Suggested change
const tmpfsMounts = [];
const tmpfsMounts: string[] = [];

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants