From fd824402c281d4d93ff4cfce4e180eb8f7ed5c70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:52:14 +0000 Subject: [PATCH 1/3] Initial plan From 04a9c744bbe755682baf760545ef8aeda3d9eea6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:00:08 +0000 Subject: [PATCH 2/3] fix: repair rootless firewall artifact permissions --- .../api-proxy/blocked-request-diagnostics.js | 2 +- containers/api-proxy/otel-exporters.js | 2 +- containers/api-proxy/token-persistence.js | 4 +- containers/cli-proxy/server.js | 2 +- src/artifact-preservation-errors.test.ts | 50 ++++++++++ src/artifact-preservation.ts | 95 ++++++++++++++++++- src/commands/main-action.test.ts | 5 +- src/commands/main-action.ts | 11 ++- src/container-cleanup.ts | 12 ++- src/services/api-proxy-service-config.test.ts | 4 +- src/services/api-proxy-service-config.ts | 2 + src/services/cli-proxy-service.test.ts | 4 +- src/services/cli-proxy-service.ts | 2 + 13 files changed, 182 insertions(+), 13 deletions(-) diff --git a/containers/api-proxy/blocked-request-diagnostics.js b/containers/api-proxy/blocked-request-diagnostics.js index aa6aaa4ba..8d7e2fff9 100644 --- a/containers/api-proxy/blocked-request-diagnostics.js +++ b/containers/api-proxy/blocked-request-diagnostics.js @@ -284,7 +284,7 @@ function getDiagStream() { if (diagStream) return diagStream; try { fs.mkdirSync(TOKEN_LOG_DIR, { recursive: true }); - diagStream = fs.createWriteStream(DIAG_FILE, { flags: 'a' }); + diagStream = fs.createWriteStream(DIAG_FILE, { flags: 'a', mode: 0o644 }); diagStream.on('error', () => { diagStream = null; }); return diagStream; } catch { diff --git a/containers/api-proxy/otel-exporters.js b/containers/api-proxy/otel-exporters.js index 31a0cb9c7..12bed86ec 100644 --- a/containers/api-proxy/otel-exporters.js +++ b/containers/api-proxy/otel-exporters.js @@ -126,7 +126,7 @@ class FileSpanExporter { _getStream() { if (this._stream) return this._stream; try { - this._stream = fs.createWriteStream(this._filePath, { flags: 'a' }); + this._stream = fs.createWriteStream(this._filePath, { flags: 'a', mode: 0o644 }); this._stream.on('error', () => { this._stream = null; }); } catch { return null; } return this._stream; diff --git a/containers/api-proxy/token-persistence.js b/containers/api-proxy/token-persistence.js index 608408ab2..26da25ca9 100644 --- a/containers/api-proxy/token-persistence.js +++ b/containers/api-proxy/token-persistence.js @@ -63,7 +63,7 @@ function diag(msg, data) { try { if (!diagStream) { fs.mkdirSync(TOKEN_LOG_DIR, { recursive: true }); - diagStream = fs.createWriteStream(DIAG_LOG_FILE, { flags: 'a' }); + diagStream = fs.createWriteStream(DIAG_LOG_FILE, { flags: 'a', mode: 0o644 }); diagStream.on('error', () => { diagStream = null; }); } const record = buildTokenDiagRecord(msg, data); @@ -81,7 +81,7 @@ function getLogStream() { try { // Ensure directory exists fs.mkdirSync(TOKEN_LOG_DIR, { recursive: true }); - logStream = fs.createWriteStream(TOKEN_LOG_FILE, { flags: 'a' }); + logStream = fs.createWriteStream(TOKEN_LOG_FILE, { flags: 'a', mode: 0o644 }); logStream.on('error', (err) => { logRequest('warn', 'token_log_error', { error: err.message }); logStream = null; diff --git a/containers/cli-proxy/server.js b/containers/cli-proxy/server.js index 8f09b136d..97ef0b225 100644 --- a/containers/cli-proxy/server.js +++ b/containers/cli-proxy/server.js @@ -56,7 +56,7 @@ const CLI_PROXY_ACCESS_SCHEMA = `cli-proxy-access/v${AWF_VERSION}`; let logStream = null; try { if (fs.existsSync(LOG_DIR)) { - logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' }); + logStream = fs.createWriteStream(LOG_FILE, { flags: 'a', mode: 0o644 }); } } catch { // Non-fatal: logging to file is best-effort diff --git a/src/artifact-preservation-errors.test.ts b/src/artifact-preservation-errors.test.ts index 9a2acbca2..4969d5169 100644 --- a/src/artifact-preservation-errors.test.ts +++ b/src/artifact-preservation-errors.test.ts @@ -87,6 +87,13 @@ describe('artifact-preservation – error paths', () => { // ─── preserveCleanupArtifacts ─────────────────────────────────────────── describe('preserveCleanupArtifacts', () => { + let getuidSpy: jest.SpyInstance | undefined; + + afterEach(() => { + getuidSpy?.mockRestore(); + getuidSpy = undefined; + }); + it('does not throw when agent-logs renameSync fails (line 101)', () => { const workDir = makeTempDir(); try { @@ -160,6 +167,49 @@ describe('artifact-preservation – error paths', () => { } }); + it('runs rootless permission repair with translated mount paths', () => { + const auditDir = makeTempDir('awf-audit-'); + const workDir = makeTempDir(); + try { + getuidSpy = jest.spyOn(process, 'getuid').mockReturnValue(1001); + expect(() => preserveCleanupArtifacts(workDir, { + auditDir, + dockerHostPathPrefix: '/host', + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + })).not.toThrow(); + + expect(mockExecaSync).toHaveBeenCalledWith( + 'docker', + expect.arrayContaining([ + 'run', + '--pull', + 'never', + '-v', + `/host${auditDir}:/fix:rw`, + 'ghcr.io/github/gh-aw-firewall/agent:latest', + ]), + expect.objectContaining({ reject: false }), + ); + } finally { + realFs.rmSync(auditDir, { recursive: true, force: true }); + realFs.rmSync(workDir, { recursive: true, force: true }); + } + }); + + it('skips rootless permission repair when running as root', () => { + const auditDir = makeTempDir('awf-audit-'); + const workDir = makeTempDir(); + try { + getuidSpy = jest.spyOn(process, 'getuid').mockReturnValue(0); + expect(() => preserveCleanupArtifacts(workDir, { auditDir })).not.toThrow(); + expect(mockExecaSync.mock.calls.some(call => call[0] === 'docker')).toBe(false); + } finally { + realFs.rmSync(auditDir, { recursive: true, force: true }); + realFs.rmSync(workDir, { recursive: true, force: true }); + } + }); + it('preserves default audit dir to /tmp when no auditDir arg is provided (lines 170-173)', () => { const workDir = makeTempDir(); const timestamp = path.basename(workDir).replace('awf-', ''); diff --git a/src/artifact-preservation.ts b/src/artifact-preservation.ts index 01e9269a9..7e14ccf24 100644 --- a/src/artifact-preservation.ts +++ b/src/artifact-preservation.ts @@ -2,7 +2,11 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import execa from 'execa'; +import { getSafeHostGid, getSafeHostUid } from './host-identity'; +import { buildRuntimeImageRef, parseImageTag } from './image-tag'; import { logger } from './logger'; +import { applyHostPathPrefixToVolumes } from './services/host-path-prefix'; +import { getLocalDockerEnv } from './docker-host'; /** * Copies the iptables audit dump from the init-signal volume to the audit directory. @@ -59,7 +63,7 @@ function preserveDirectory({ execa.sync('chmod', ['-R', 'a+rX', targetDir]); logger.info(`${availableLabel} available at: ${targetDir}`); } catch (error) { - logger.debug(permissionErrorMessage, error); + logger.warn(permissionErrorMessage, error); } } return; @@ -84,11 +88,89 @@ type PreserveCleanupArtifactsOptions = { proxyLogsDir?: string; auditDir?: string; sessionStateDir?: string; + dockerHostPathPrefix?: string; + imageRegistry?: string; + imageTag?: string; }; +function resolvePermFixerImageRef(imageRegistry?: string, imageTag?: string): string { + try { + const registry = imageRegistry || 'ghcr.io/github/gh-aw-firewall'; + const parsedImageTag = parseImageTag(imageTag || 'latest'); + return buildRuntimeImageRef(registry, 'agent', parsedImageTag); + } catch { + return 'ghcr.io/github/gh-aw-firewall/agent:latest'; + } +} + +function fixArtifactPermissionsForRootless( + dirs: Array, + dockerHostPathPrefix: string | undefined, + imageRegistry: string | undefined, + imageTag: string | undefined, +): void { + const currentUid = process.getuid?.(); + if (currentUid === undefined || currentUid === 0) { + return; + } + + const existingDirs = dirs.filter( + (dir): dir is string => typeof dir === 'string' && dir.length > 0 && fs.existsSync(dir), + ); + if (existingDirs.length === 0) { + return; + } + + const uid = getSafeHostUid(); + const gid = getSafeHostGid(); + const imageRef = resolvePermFixerImageRef(imageRegistry, imageTag); + + for (const dir of existingDirs) { + const mount = applyHostPathPrefixToVolumes([`${dir}:/fix:rw`], dockerHostPathPrefix)[0]; + try { + const result = execa.sync( + 'docker', + [ + 'run', + '--rm', + '--pull', + 'never', + '--network', + 'none', + '--cap-drop', + 'ALL', + '--cap-add', + 'CHOWN', + '--cap-add', + 'DAC_OVERRIDE', + '--cap-add', + 'FOWNER', + '-e', + `TUID=${uid}`, + '-e', + `TGID=${gid}`, + '-v', + mount, + imageRef, + 'sh', + '-c', + 'chown -R "$TUID:$TGID" /fix && chmod -R a+rX /fix', + ], + { env: getLocalDockerEnv(), reject: false }, + ); + + if (typeof result.exitCode === 'number' && result.exitCode !== 0) { + logger.warn(`Rootless artifact permission repair failed for ${dir} (exit ${result.exitCode})`); + } + } catch (error) { + logger.warn(`Rootless artifact permission repair failed for ${dir}:`, error); + } + } +} + export function preserveCleanupArtifacts( workDir: string, - { proxyLogsDir, auditDir, sessionStateDir }: PreserveCleanupArtifactsOptions = {}, + { proxyLogsDir, auditDir, sessionStateDir, dockerHostPathPrefix, imageRegistry, imageTag }: PreserveCleanupArtifactsOptions = {}, ): void { const timestamp = path.basename(workDir).replace('awf-', ''); const agentLogsDestination = path.join(os.tmpdir(), `awf-agent-logs-${timestamp}`); @@ -160,7 +242,7 @@ export function preserveCleanupArtifacts( execa.sync('chmod', ['-R', 'a+rX', auditDir]); logger.info(`Audit artifacts available at: ${auditDir}`); } catch (error) { - logger.debug('Could not fix audit dir permissions:', error); + logger.warn('Could not fix audit dir permissions as non-root user; rootless repair will be attempted:', error); } } } else { @@ -205,6 +287,13 @@ export function preserveCleanupArtifacts( } } } + + fixArtifactPermissionsForRootless( + [proxyLogsDir, auditDir, sessionStateDir], + dockerHostPathPrefix, + imageRegistry, + imageTag, + ); } export function removeWorkDirectories(workDir: string): void { diff --git a/src/commands/main-action.test.ts b/src/commands/main-action.test.ts index ee0185d2c..41844853b 100644 --- a/src/commands/main-action.test.ts +++ b/src/commands/main-action.test.ts @@ -247,7 +247,10 @@ describe('createMainAction', () => { false, STUB_CONFIG.proxyLogsDir, STUB_CONFIG.auditDir, - STUB_CONFIG.sessionStateDir + STUB_CONFIG.sessionStateDir, + STUB_CONFIG.dockerHostPathPrefix, + STUB_CONFIG.imageRegistry, + STUB_CONFIG.imageTag, ); expect(mockedHostIptables.cleanupHostIptables).not.toHaveBeenCalled(); expect(processExitSpy).toHaveBeenCalledWith(1); diff --git a/src/commands/main-action.ts b/src/commands/main-action.ts index 65a4f950a..480d2a071 100644 --- a/src/commands/main-action.ts +++ b/src/commands/main-action.ts @@ -162,7 +162,16 @@ export function createMainAction(getOptionValueSource: OptionSourceResolver) { } if (!config.keepContainers) { - await cleanup(config.workDir, false, config.proxyLogsDir, config.auditDir, config.sessionStateDir); + await cleanup( + config.workDir, + false, + config.proxyLogsDir, + config.auditDir, + config.sessionStateDir, + config.dockerHostPathPrefix, + config.imageRegistry, + config.imageTag, + ); // Note: We don't remove the firewall network here since it can be reused // across multiple runs. Cleanup script will handle removal if needed. } else { diff --git a/src/container-cleanup.ts b/src/container-cleanup.ts index 9ed835032..92451a823 100644 --- a/src/container-cleanup.ts +++ b/src/container-cleanup.ts @@ -17,6 +17,9 @@ export async function cleanup( proxyLogsDir?: string, auditDir?: string, sessionStateDir?: string, + dockerHostPathPrefix?: string, + imageRegistry?: string, + imageTag?: string, ): Promise { if (keepFiles) { logger.debug(`Keeping temporary files in: ${workDir}`); @@ -29,7 +32,14 @@ export async function cleanup( return; } - preserveCleanupArtifacts(workDir, { proxyLogsDir, auditDir, sessionStateDir }); + preserveCleanupArtifacts(workDir, { + proxyLogsDir, + auditDir, + sessionStateDir, + dockerHostPathPrefix, + imageRegistry, + imageTag, + }); cleanupSslKeyMaterial(workDir); diff --git a/src/services/api-proxy-service-config.test.ts b/src/services/api-proxy-service-config.test.ts index b754a53fc..4311f21d8 100644 --- a/src/services/api-proxy-service-config.test.ts +++ b/src/services/api-proxy-service-config.test.ts @@ -1,5 +1,6 @@ import { generateDockerCompose, WrapperConfig, baseConfig, mockNetworkConfig, useTempWorkDir } from './service-test-setup.test-utils'; import { mockNetworkConfigWithProxy } from './api-proxy-service.test-utils'; +import { getSafeHostGid, getSafeHostUid } from '../host-identity'; // Create mock functions (must remain per-file — jest.mock() is hoisted before imports) @@ -33,8 +34,9 @@ describe('API proxy sidecar: service configuration', () => { const configWithProxy = { ...mockConfig, enableApiProxy: true, openaiApiKey: 'sk-test-openai-key' }; const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy); expect(result.services['api-proxy']).toBeDefined(); - const proxy = result.services['api-proxy']; + const proxy = result.services['api-proxy'] as any; expect(proxy.container_name).toBe('awf-api-proxy'); + expect(proxy.user).toBe(`${getSafeHostUid()}:${getSafeHostGid()}`); expect((proxy.networks as any)['awf-net'].ipv4_address).toBe('172.30.0.30'); }); diff --git a/src/services/api-proxy-service-config.ts b/src/services/api-proxy-service-config.ts index 81e29fbe7..061088299 100644 --- a/src/services/api-proxy-service-config.ts +++ b/src/services/api-proxy-service-config.ts @@ -3,6 +3,7 @@ import { } from '../constants'; import { assignImageSource } from '../image-tag'; import { WrapperConfig } from '../types'; +import { getSafeHostGid, getSafeHostUid } from '../host-identity'; import { NetworkConfig, ImageBuildConfig } from './squid-service'; import { applyHostPathPrefixToVolumes } from './host-path-prefix'; import { buildContainerSecurityHardening } from './service-security'; @@ -25,6 +26,7 @@ export function buildApiProxyServiceConfig(params: ApiProxyServiceConfigParams): const proxyService: any = { container_name: API_PROXY_CONTAINER_NAME, + user: `${getSafeHostUid()}:${getSafeHostGid()}`, ...buildApiProxyLifecycleConfig(networkConfig), volumes: applyHostPathPrefixToVolumes( [ diff --git a/src/services/cli-proxy-service.test.ts b/src/services/cli-proxy-service.test.ts index 8ad7e02d2..48187f10f 100644 --- a/src/services/cli-proxy-service.test.ts +++ b/src/services/cli-proxy-service.test.ts @@ -1,4 +1,5 @@ import { generateDockerCompose, WrapperConfig, baseConfig, mockNetworkConfig, useTempWorkDir } from './service-test-setup.test-utils'; +import { getSafeHostGid, getSafeHostUid } from '../host-identity'; // Create mock functions (must remain per-file — jest.mock() is hoisted before imports) @@ -37,8 +38,9 @@ describe('CLI proxy sidecar (external DIFC proxy)', () => { const configWithCliProxy = { ...mockConfig, difcProxyHost: 'host.docker.internal:18443' }; const result = generateDockerCompose(configWithCliProxy, mockNetworkConfigWithCliProxy); expect(result.services['cli-proxy']).toBeDefined(); - const proxy = result.services['cli-proxy']; + const proxy = result.services['cli-proxy'] as any; expect(proxy.container_name).toBe('awf-cli-proxy'); + expect(proxy.user).toBe(`${getSafeHostUid()}:${getSafeHostGid()}`); // cli-proxy gets its own IP on awf-net (no shared network namespace) expect((proxy.networks as any)['awf-net'].ipv4_address).toBe('172.30.0.50'); expect(proxy.network_mode).toBeUndefined(); diff --git a/src/services/cli-proxy-service.ts b/src/services/cli-proxy-service.ts index ee3458594..dce7dec17 100644 --- a/src/services/cli-proxy-service.ts +++ b/src/services/cli-proxy-service.ts @@ -1,5 +1,6 @@ import { CLI_PROXY_CONTAINER_NAME } from '../constants'; import { parseDifcProxyHost } from '../host-env'; +import { getSafeHostGid, getSafeHostUid } from '../host-identity'; import { assignImageSource } from '../image-tag'; import { logger } from '../logger'; import { WrapperConfig, CLI_PROXY_PORT } from '../types'; @@ -47,6 +48,7 @@ export function buildCliProxyService(params: CliProxyServiceParams): CliProxyBui // so that gh CLI's GH_HOST=localhost:${difcProxyPort} matches the cert's SAN. const cliProxyService: any = { container_name: CLI_PROXY_CONTAINER_NAME, + user: `${getSafeHostUid()}:${getSafeHostGid()}`, networks: { 'awf-net': { ipv4_address: cliProxyIp, From 2083efc2690db5f43a2d3db106716f824bba1193 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:16:57 +0000 Subject: [PATCH 3/3] fix: address reviewer feedback on rootless permission repair - resolvePermFixerImageRef: use agent-act image when agentImage is 'act' so --pull never succeeds when the act preset was used at runtime - fixArtifactPermissionsForRootless: resolve dirs to absolute paths via path.resolve() before constructing docker -v mount strings (relative paths would be treated as named volumes by Docker) - cli-proxy service: remove user override (host UID:GID) which breaks the entrypoint's write to /tmp/proxy-tls/combined-ca.crt owned by the cliproxy image user - Plumb agentImage through cleanup() -> preserveCleanupArtifacts() chain - Update affected tests --- src/artifact-preservation-errors.test.ts | 27 +++++++++++++++++++++++- src/artifact-preservation.ts | 14 +++++++----- src/commands/main-action.ts | 1 + src/container-cleanup.ts | 2 ++ src/services/cli-proxy-service.test.ts | 3 +-- src/services/cli-proxy-service.ts | 2 -- 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/artifact-preservation-errors.test.ts b/src/artifact-preservation-errors.test.ts index 4969d5169..194144183 100644 --- a/src/artifact-preservation-errors.test.ts +++ b/src/artifact-preservation-errors.test.ts @@ -186,7 +186,7 @@ describe('artifact-preservation – error paths', () => { '--pull', 'never', '-v', - `/host${auditDir}:/fix:rw`, + `/host${path.resolve(auditDir)}:/fix:rw`, 'ghcr.io/github/gh-aw-firewall/agent:latest', ]), expect.objectContaining({ reject: false }), @@ -197,6 +197,31 @@ describe('artifact-preservation – error paths', () => { } }); + it('uses agent-act image when agentImage is act', () => { + const auditDir = makeTempDir('awf-audit-'); + const workDir = makeTempDir(); + try { + getuidSpy = jest.spyOn(process, 'getuid').mockReturnValue(1001); + expect(() => preserveCleanupArtifacts(workDir, { + auditDir, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + agentImage: 'act', + })).not.toThrow(); + + expect(mockExecaSync).toHaveBeenCalledWith( + 'docker', + expect.arrayContaining([ + 'ghcr.io/github/gh-aw-firewall/agent-act:latest', + ]), + expect.objectContaining({ reject: false }), + ); + } finally { + realFs.rmSync(auditDir, { recursive: true, force: true }); + realFs.rmSync(workDir, { recursive: true, force: true }); + } + }); + it('skips rootless permission repair when running as root', () => { const auditDir = makeTempDir('awf-audit-'); const workDir = makeTempDir(); diff --git a/src/artifact-preservation.ts b/src/artifact-preservation.ts index 7e14ccf24..9af5bbe81 100644 --- a/src/artifact-preservation.ts +++ b/src/artifact-preservation.ts @@ -91,13 +91,15 @@ type PreserveCleanupArtifactsOptions = { dockerHostPathPrefix?: string; imageRegistry?: string; imageTag?: string; + agentImage?: string; }; -function resolvePermFixerImageRef(imageRegistry?: string, imageTag?: string): string { +function resolvePermFixerImageRef(imageRegistry?: string, imageTag?: string, agentImage?: string): string { try { const registry = imageRegistry || 'ghcr.io/github/gh-aw-firewall'; const parsedImageTag = parseImageTag(imageTag || 'latest'); - return buildRuntimeImageRef(registry, 'agent', parsedImageTag); + const imageName = agentImage === 'act' ? 'agent-act' : 'agent'; + return buildRuntimeImageRef(registry, imageName, parsedImageTag); } catch { return 'ghcr.io/github/gh-aw-firewall/agent:latest'; } @@ -108,6 +110,7 @@ function fixArtifactPermissionsForRootless( dockerHostPathPrefix: string | undefined, imageRegistry: string | undefined, imageTag: string | undefined, + agentImage: string | undefined, ): void { const currentUid = process.getuid?.(); if (currentUid === undefined || currentUid === 0) { @@ -123,10 +126,10 @@ function fixArtifactPermissionsForRootless( const uid = getSafeHostUid(); const gid = getSafeHostGid(); - const imageRef = resolvePermFixerImageRef(imageRegistry, imageTag); + const imageRef = resolvePermFixerImageRef(imageRegistry, imageTag, agentImage); for (const dir of existingDirs) { - const mount = applyHostPathPrefixToVolumes([`${dir}:/fix:rw`], dockerHostPathPrefix)[0]; + const mount = applyHostPathPrefixToVolumes([`${path.resolve(dir)}:/fix:rw`], dockerHostPathPrefix)[0]; try { const result = execa.sync( 'docker', @@ -170,7 +173,7 @@ function fixArtifactPermissionsForRootless( export function preserveCleanupArtifacts( workDir: string, - { proxyLogsDir, auditDir, sessionStateDir, dockerHostPathPrefix, imageRegistry, imageTag }: PreserveCleanupArtifactsOptions = {}, + { proxyLogsDir, auditDir, sessionStateDir, dockerHostPathPrefix, imageRegistry, imageTag, agentImage }: PreserveCleanupArtifactsOptions = {}, ): void { const timestamp = path.basename(workDir).replace('awf-', ''); const agentLogsDestination = path.join(os.tmpdir(), `awf-agent-logs-${timestamp}`); @@ -293,6 +296,7 @@ export function preserveCleanupArtifacts( dockerHostPathPrefix, imageRegistry, imageTag, + agentImage, ); } diff --git a/src/commands/main-action.ts b/src/commands/main-action.ts index 480d2a071..eb8405054 100644 --- a/src/commands/main-action.ts +++ b/src/commands/main-action.ts @@ -171,6 +171,7 @@ export function createMainAction(getOptionValueSource: OptionSourceResolver) { config.dockerHostPathPrefix, config.imageRegistry, config.imageTag, + config.agentImage, ); // Note: We don't remove the firewall network here since it can be reused // across multiple runs. Cleanup script will handle removal if needed. diff --git a/src/container-cleanup.ts b/src/container-cleanup.ts index 92451a823..35453d249 100644 --- a/src/container-cleanup.ts +++ b/src/container-cleanup.ts @@ -20,6 +20,7 @@ export async function cleanup( dockerHostPathPrefix?: string, imageRegistry?: string, imageTag?: string, + agentImage?: string, ): Promise { if (keepFiles) { logger.debug(`Keeping temporary files in: ${workDir}`); @@ -39,6 +40,7 @@ export async function cleanup( dockerHostPathPrefix, imageRegistry, imageTag, + agentImage, }); cleanupSslKeyMaterial(workDir); diff --git a/src/services/cli-proxy-service.test.ts b/src/services/cli-proxy-service.test.ts index 48187f10f..9dd01ef36 100644 --- a/src/services/cli-proxy-service.test.ts +++ b/src/services/cli-proxy-service.test.ts @@ -1,5 +1,4 @@ import { generateDockerCompose, WrapperConfig, baseConfig, mockNetworkConfig, useTempWorkDir } from './service-test-setup.test-utils'; -import { getSafeHostGid, getSafeHostUid } from '../host-identity'; // Create mock functions (must remain per-file — jest.mock() is hoisted before imports) @@ -40,7 +39,7 @@ describe('CLI proxy sidecar (external DIFC proxy)', () => { expect(result.services['cli-proxy']).toBeDefined(); const proxy = result.services['cli-proxy'] as any; expect(proxy.container_name).toBe('awf-cli-proxy'); - expect(proxy.user).toBe(`${getSafeHostUid()}:${getSafeHostGid()}`); + expect(proxy.user).toBeUndefined(); // cli-proxy gets its own IP on awf-net (no shared network namespace) expect((proxy.networks as any)['awf-net'].ipv4_address).toBe('172.30.0.50'); expect(proxy.network_mode).toBeUndefined(); diff --git a/src/services/cli-proxy-service.ts b/src/services/cli-proxy-service.ts index dce7dec17..ee3458594 100644 --- a/src/services/cli-proxy-service.ts +++ b/src/services/cli-proxy-service.ts @@ -1,6 +1,5 @@ import { CLI_PROXY_CONTAINER_NAME } from '../constants'; import { parseDifcProxyHost } from '../host-env'; -import { getSafeHostGid, getSafeHostUid } from '../host-identity'; import { assignImageSource } from '../image-tag'; import { logger } from '../logger'; import { WrapperConfig, CLI_PROXY_PORT } from '../types'; @@ -48,7 +47,6 @@ export function buildCliProxyService(params: CliProxyServiceParams): CliProxyBui // so that gh CLI's GH_HOST=localhost:${difcProxyPort} matches the cert's SAN. const cliProxyService: any = { container_name: CLI_PROXY_CONTAINER_NAME, - user: `${getSafeHostUid()}:${getSafeHostGid()}`, networks: { 'awf-net': { ipv4_address: cliProxyIp,