From e3683ab745adde9654e1e60550b98cc6a89eba58 Mon Sep 17 00:00:00 2001 From: "Jiaxiao (mossaka) Zhou" Date: Thu, 12 Feb 2026 05:24:48 +0000 Subject: [PATCH] fix: expand credential hiding list for runner tokens and MCP config Add missing credential files to the /dev/null mount list in both normal and chroot modes: - GitHub Actions runner .credentials and .credentials_rsaparams - GitHub Actions runner cached credentials - MCP config file (.copilot/mcp-config.json) containing auth tokens Also exclude GH_AW_MCP_CONFIG from --env-all passthrough to prevent trivial discovery of the MCP config path. Closes #66, closes #179, closes #182, closes #195, closes #202, closes #208, closes #217, closes #218 Co-Authored-By: Claude Opus 4.6 --- src/docker-manager.test.ts | 70 ++++++++++++++++++++++++++++++++++++++ src/docker-manager.ts | 15 ++++++++ 2 files changed, 85 insertions(+) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 9ef80eff0..0a8d86acd 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -509,6 +509,76 @@ describe('docker-manager', () => { expect(volumes.some((v: string) => v.includes('/dev/null') && v.includes('.docker/config.json'))).toBe(true); }); + it('should hide GitHub Actions runner credentials in normal mode', () => { + const result = generateDockerCompose(mockConfig, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + const home = process.env.HOME || '/root'; + + // Runner credential files + expect(volumes).toContain(`/dev/null:${home}/actions-runner/.credentials:ro`); + expect(volumes).toContain(`/dev/null:${home}/actions-runner/.credentials_rsaparams:ro`); + expect(volumes).toContain(`/dev/null:${home}/actions-runner/cached/.credentials:ro`); + expect(volumes).toContain(`/dev/null:${home}/actions-runner/cached/.credentials_rsaparams:ro`); + }); + + it('should hide MCP config in normal mode', () => { + const result = generateDockerCompose(mockConfig, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + const home = process.env.HOME || '/root'; + + expect(volumes).toContain(`/dev/null:${home}/.copilot/mcp-config.json:ro`); + }); + + it('should hide GitHub Actions runner credentials in chroot mode', () => { + const configWithChroot = { + ...mockConfig, + enableChroot: true, + }; + const result = generateDockerCompose(configWithChroot, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + const home = process.env.HOME || '/root'; + + expect(volumes).toContain(`/dev/null:/host${home}/actions-runner/.credentials:ro`); + expect(volumes).toContain(`/dev/null:/host${home}/actions-runner/.credentials_rsaparams:ro`); + expect(volumes).toContain(`/dev/null:/host${home}/actions-runner/cached/.credentials:ro`); + expect(volumes).toContain(`/dev/null:/host${home}/actions-runner/cached/.credentials_rsaparams:ro`); + }); + + it('should hide MCP config in chroot mode', () => { + const configWithChroot = { + ...mockConfig, + enableChroot: true, + }; + const result = generateDockerCompose(configWithChroot, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + const home = process.env.HOME || '/root'; + + expect(volumes).toContain(`/dev/null:/host${home}/.copilot/mcp-config.json:ro`); + }); + + it('should exclude GH_AW_MCP_CONFIG from env-all passthrough', () => { + const originalVal = process.env.GH_AW_MCP_CONFIG; + process.env.GH_AW_MCP_CONFIG = '/home/runner/.copilot/mcp-config.json'; + + try { + const configWithEnvAll = { ...mockConfig, envAll: true }; + const result = generateDockerCompose(configWithEnvAll, mockNetworkConfig); + const env = result.services.agent.environment as Record; + + expect(env.GH_AW_MCP_CONFIG).toBeUndefined(); + } finally { + if (originalVal !== undefined) { + process.env.GH_AW_MCP_CONFIG = originalVal; + } else { + delete process.env.GH_AW_MCP_CONFIG; + } + } + }); + it('should use custom volume mounts when specified', () => { const configWithMounts = { ...mockConfig, diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 22f517ea1..10e85d59c 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -318,6 +318,7 @@ export function generateDockerCompose( 'SUDO_USER', // Sudo metadata 'SUDO_UID', // Sudo metadata 'SUDO_GID', // Sudo metadata + 'GH_AW_MCP_CONFIG', // Points to MCP config path containing auth tokens ]); // Start with required/overridden environment variables @@ -666,6 +667,13 @@ export function generateDockerCompose( `${effectiveHome}/.kube/config`, `${effectiveHome}/.azure/credentials`, `${effectiveHome}/.config/gcloud/credentials.db`, + // GitHub Actions runner credentials (JWT tokens, RSA keys) + `${effectiveHome}/actions-runner/.credentials`, + `${effectiveHome}/actions-runner/.credentials_rsaparams`, + `${effectiveHome}/actions-runner/cached/.credentials`, + `${effectiveHome}/actions-runner/cached/.credentials_rsaparams`, + // MCP configuration (contains authorization tokens) + `${effectiveHome}/.copilot/mcp-config.json`, ]; credentialFiles.forEach(credFile => { @@ -697,6 +705,13 @@ export function generateDockerCompose( `/dev/null:/host${userHome}/.kube/config:ro`, `/dev/null:/host${userHome}/.azure/credentials:ro`, `/dev/null:/host${userHome}/.config/gcloud/credentials.db:ro`, + // GitHub Actions runner credentials (JWT tokens, RSA keys) + `/dev/null:/host${userHome}/actions-runner/.credentials:ro`, + `/dev/null:/host${userHome}/actions-runner/.credentials_rsaparams:ro`, + `/dev/null:/host${userHome}/actions-runner/cached/.credentials:ro`, + `/dev/null:/host${userHome}/actions-runner/cached/.credentials_rsaparams:ro`, + // MCP configuration (contains authorization tokens) + `/dev/null:/host${userHome}/.copilot/mcp-config.json:ro`, ]; chrootCredentialFiles.forEach(mount => {