From e58861db96f01bc1fd4bbc30a2ebe8226a6183cc Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:08:21 +0000 Subject: [PATCH 1/3] Initial plan From 53410d736080a6aba731cb9bf4922427010340e7 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:12:00 +0000 Subject: [PATCH 2/3] fix: prevent .cargo volume/tmpfs mount conflict in chroot mode Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- src/docker-manager.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/docker-manager.ts | 3 ++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 067cf4b7d..a2dce6b1d 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -655,6 +655,44 @@ describe('docker-manager', () => { expect(volumes).not.toContain(`${homeDir}:/host${homeDir}:rw`); }); + it('should not mount .cargo when enableChroot is true and allowFullFilesystemAccess is false', () => { + const configWithChroot = { + ...mockConfig, + enableChroot: true, + allowFullFilesystemAccess: false + }; + const result = generateDockerCompose(configWithChroot, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + const tmpfs = agent.tmpfs as string[]; + + // Should NOT mount .cargo as volume (it's hidden via tmpfs) + const homeDir = process.env.HOME || '/root'; + const cargoVolumePattern = new RegExp(`${homeDir.replace(/\//g, '\\/')}.*\\.cargo.*:/host.*\\.cargo`); + expect(volumes.some((v: string) => cargoVolumePattern.test(v))).toBe(false); + + // Should have .cargo hidden via tmpfs + expect(tmpfs.some((t: string) => t.includes('.cargo'))).toBe(true); + }); + + it('should mount .cargo when enableChroot is true and allowFullFilesystemAccess is true', () => { + const configWithChroot = { + ...mockConfig, + enableChroot: true, + allowFullFilesystemAccess: true + }; + const result = generateDockerCompose(configWithChroot, mockNetworkConfig); + const agent = result.services.agent; + const volumes = agent.volumes as string[]; + const tmpfs = agent.tmpfs as string[]; + + // With allowFullFilesystemAccess, should have blanket mount + expect(volumes).toContain('/:/host:rw'); + + // Should NOT have credential hiding tmpfs (only MCP logs tmpfs) + expect(tmpfs.some((t: string) => t.includes('.cargo'))).toBe(false); + }); + it('should add SYS_CHROOT and SYS_ADMIN capabilities when enableChroot is true', () => { const configWithChroot = { ...mockConfig, diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 945176e3e..c815684ed 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -502,8 +502,9 @@ export function generateDockerCompose( } // Mount ~/.cargo for Rust binaries (read-only) if it exists + // SKIP if allowFullFilesystemAccess is false (credentials will be hidden via tmpfs) const hostCargoDir = path.join(userHome, '.cargo'); - if (fs.existsSync(hostCargoDir)) { + if (fs.existsSync(hostCargoDir) && config.allowFullFilesystemAccess) { agentVolumes.push(`${hostCargoDir}:/host${hostCargoDir}:ro`); } From c7ad1ac651254228e1b4323377b22446ca22cd6b Mon Sep 17 00:00:00 2001 From: Claude <242468646+Claude@users.noreply.github.com> Date: Wed, 11 Feb 2026 22:32:43 -0800 Subject: [PATCH 3/3] 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> --- src/docker-manager.ts | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/docker-manager.ts b/src/docker-manager.ts index c815684ed..0cdc409f3 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -1301,11 +1301,36 @@ export async function stopContainers(workDir: string, keepContainers: boolean): logger.info('Stopping containers...'); try { - await execa('docker', ['compose', 'down', '-v'], { - cwd: workDir, - stdio: 'inherit', - }); - logger.success('Containers stopped successfully'); + // Check if workDir and docker-compose.yml exist before using docker compose + const composeFile = path.join(workDir, 'docker-compose.yml'); + if (fs.existsSync(workDir) && fs.existsSync(composeFile)) { + // Normal path: use docker compose down + await execa('docker', ['compose', 'down', '-v'], { + cwd: workDir, + stdio: 'inherit', + }); + logger.success('Containers stopped successfully'); + } else { + // Fallback: compose file missing, stop containers by name + logger.debug('Compose file not found, stopping containers by name'); + + // Stop and remove containers by name + const containerNames = ['awf-agent', 'awf-squid']; + for (const name of containerNames) { + try { + // Check if container exists + const { stdout } = await execa('docker', ['ps', '-aq', '-f', `name=^${name}$`]); + if (stdout.trim()) { + logger.debug(`Stopping container: ${name}`); + await execa('docker', ['rm', '-f', name], { stdio: 'inherit' }); + } + } catch (err) { + logger.debug(`Could not stop container ${name}:`, err); + } + } + + logger.success('Containers stopped successfully'); + } } catch (error) { logger.error('Failed to stop containers:', error); throw error;