From 517bf8889f0901e33e86ca5bd368b39f760076c7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 09:33:44 +0000 Subject: [PATCH 1/3] test: improve config-writer.ts branch coverage with edge case tests Add comprehensive edge case tests for config-writer.ts to improve branch coverage from 69.09% to target >85%: - ensureDirectory error handling (non-directory path) - Chroot home directory creation paths (new vs existing) - Home subdirectory creation (.copilot, .cache, etc.) - Conditional .gemini directory creation based on geminiApiKey - Audit directory creation when missing - URL pattern parsing when allowedUrls is provided - API proxy configuration conditional inclusion - Multiple path validation scenarios These tests focus on security-critical configuration paths including: - Directory permission and ownership setup - Conditional feature enablement (API proxy, URL filtering) - File system edge cases that could affect container security All tests use mocked dependencies (fs, ssl-bump, squid-config) for fast, deterministic execution without Docker requirements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/config-writer.test.ts | 299 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/src/config-writer.test.ts b/src/config-writer.test.ts index 9372bd3d..16c25f7e 100644 --- a/src/config-writer.test.ts +++ b/src/config-writer.test.ts @@ -189,5 +189,304 @@ describe('writeConfigs', () => { const mcpLogsDirMode = fs.statSync(mcpLogsDir).mode & 0o777; expect(mcpLogsDirMode).toBe(0o777); }); + + it('throws when workDir path exists but is not a directory', async () => { + const filePath = path.join(tempDir, 'not-a-directory'); + fs.writeFileSync(filePath, 'content'); + + await expect( + writeConfigs({ + workDir: filePath, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }) + ).rejects.toThrow(`Expected directory but found non-directory path: ${filePath}`); + }); + + it('creates chroot home directory when it does not exist', async () => { + const emptyHomeDir = `${tempDir}-chroot-home`; + expect(fs.existsSync(emptyHomeDir)).toBe(false); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }); + + expect(fs.existsSync(emptyHomeDir)).toBe(true); + expect(fs.statSync(emptyHomeDir).isDirectory()).toBe(true); + }); + + it('uses existing chroot home directory if already present', async () => { + const emptyHomeDir = `${tempDir}-chroot-home`; + fs.mkdirSync(emptyHomeDir, { recursive: true }); + const statBefore = fs.statSync(emptyHomeDir); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }); + + const statAfter = fs.statSync(emptyHomeDir); + expect(statAfter.ino).toBe(statBefore.ino); // Same directory + }); + + it('creates missing home subdirectories with correct ownership', async () => { + const homeDir = tempDir; + (getRealUserHome as jest.Mock).mockReturnValue(homeDir); + + // Delete .copilot if it exists + const copilotDir = path.join(homeDir, '.copilot'); + if (fs.existsSync(copilotDir)) { + fs.rmSync(copilotDir, { recursive: true, force: true }); + } + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }); + + expect(fs.existsSync(copilotDir)).toBe(true); + expect(fs.chownSync).toHaveBeenCalledWith(copilotDir, 1000, 1000); + }); + + it('creates .gemini directory when geminiApiKey is provided', async () => { + const homeDir = tempDir; + (getRealUserHome as jest.Mock).mockReturnValue(homeDir); + + const geminiDir = path.join(homeDir, '.gemini'); + if (fs.existsSync(geminiDir)) { + fs.rmSync(geminiDir, { recursive: true, force: true }); + } + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + geminiApiKey: 'test-key', + }); + + expect(fs.existsSync(geminiDir)).toBe(true); + }); + + it('does not create .gemini directory when geminiApiKey is not provided', async () => { + const homeDir = tempDir; + (getRealUserHome as jest.Mock).mockReturnValue(homeDir); + + const geminiDir = path.join(homeDir, '.gemini'); + if (fs.existsSync(geminiDir)) { + fs.rmSync(geminiDir, { recursive: true, force: true }); + } + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }); + + expect(fs.existsSync(geminiDir)).toBe(false); + }); + + it('creates audit directory when it does not exist', async () => { + const auditDir = path.join(tempDir, 'custom-audit'); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + auditDir, + }); + + expect(fs.existsSync(auditDir)).toBe(true); + expect(fs.existsSync(path.join(auditDir, 'squid.conf'))).toBe(true); + expect(fs.existsSync(path.join(auditDir, 'docker-compose.redacted.yml'))).toBe(true); + expect(fs.existsSync(path.join(auditDir, 'policy-manifest.json'))).toBe(true); + }); + }); + + describe('seccomp profile', () => { + it('throws error when seccomp profile is not found', async () => { + // Mock __dirname to point to a location where seccomp profile doesn't exist + const originalDirname = __dirname; + Object.defineProperty(global, '__dirname', { + value: '/nonexistent/path', + writable: true, + configurable: true, + }); + + await expect( + writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }) + ).rejects.toThrow(/Seccomp profile not found/); + + Object.defineProperty(global, '__dirname', { + value: originalDirname, + writable: true, + configurable: true, + }); + }); + }); + + describe('URL patterns and API proxy', () => { + beforeEach(() => { + const { parseUrlPatterns } = jest.requireMock('./ssl-bump'); + parseUrlPatterns.mockReturnValue(['https://example\\.com/.*']); + }); + + it('parses URL patterns when allowedUrls is provided', async () => { + const { parseUrlPatterns } = jest.requireMock('./ssl-bump'); + const { generateSquidConfig } = jest.requireMock('./squid-config'); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: ['example.com'], + allowedUrls: ['https://example.com/api/*'], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }); + + expect(parseUrlPatterns).toHaveBeenCalledWith(['https://example.com/api/*']); + expect(generateSquidConfig).toHaveBeenCalledWith( + expect.objectContaining({ + urlPatterns: ['https://example\\.com/.*'], + }) + ); + }); + + it('does not parse URL patterns when allowedUrls is empty', async () => { + const { parseUrlPatterns } = jest.requireMock('./ssl-bump'); + const { generateSquidConfig } = jest.requireMock('./squid-config'); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: ['example.com'], + allowedUrls: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }); + + expect(parseUrlPatterns).not.toHaveBeenCalled(); + expect(generateSquidConfig).toHaveBeenCalledWith( + expect.objectContaining({ + urlPatterns: undefined, + }) + ); + }); + + it('includes API proxy configuration when enableApiProxy is true', async () => { + const { generateSquidConfig } = jest.requireMock('./squid-config'); + const { generatePolicyManifest } = jest.requireMock('./squid-config'); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: ['example.com'], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + enableApiProxy: true, + }); + + expect(generateSquidConfig).toHaveBeenCalledWith( + expect.objectContaining({ + apiProxyIp: '172.30.0.30', + apiProxyPorts: expect.arrayContaining([10000, 10001, 10002, 10003]), + }) + ); + expect(generatePolicyManifest).toHaveBeenCalledWith( + expect.objectContaining({ + apiProxyIp: '172.30.0.30', + }) + ); + }); + + it('does not include API proxy configuration when enableApiProxy is false', async () => { + const { generateSquidConfig } = jest.requireMock('./squid-config'); + + await writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: ['example.com'], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + enableApiProxy: false, + }); + + expect(generateSquidConfig).toHaveBeenCalledWith( + expect.not.objectContaining({ + apiProxyIp: expect.anything(), + }) + ); + }); }); }); From 9fbcddbb0b567395fbc5e85998165151ff89fb91 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 25 May 2026 07:19:31 -0700 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/config-writer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-writer.test.ts b/src/config-writer.test.ts index 16c25f7e..c19e4e4b 100644 --- a/src/config-writer.test.ts +++ b/src/config-writer.test.ts @@ -206,7 +206,7 @@ describe('writeConfigs', () => { imageRegistry: 'ghcr.io/github/gh-aw-firewall', imageTag: 'latest', }) - ).rejects.toThrow(`Expected directory but found non-directory path: ${filePath}`); + ).rejects.toThrow(/EEXIST|ENOTDIR/); }); it('creates chroot home directory when it does not exist', async () => { From 97ef2cdace23388d963e58d05abfb22955c31f5e Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 25 May 2026 07:19:42 -0700 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/config-writer.test.ts | 55 +++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/config-writer.test.ts b/src/config-writer.test.ts index c19e4e4b..7e08552f 100644 --- a/src/config-writer.test.ts +++ b/src/config-writer.test.ts @@ -350,33 +350,38 @@ describe('writeConfigs', () => { describe('seccomp profile', () => { it('throws error when seccomp profile is not found', async () => { - // Mock __dirname to point to a location where seccomp profile doesn't exist - const originalDirname = __dirname; - Object.defineProperty(global, '__dirname', { - value: '/nonexistent/path', - writable: true, - configurable: true, - }); - - await expect( - writeConfigs({ - workDir: tempDir, - sslBump: false, - allowedDomains: [], - agentCommand: 'echo test', - logLevel: 'info', - keepContainers: false, - buildLocal: false, - imageRegistry: 'ghcr.io/github/gh-aw-firewall', - imageTag: 'latest', - }) - ).rejects.toThrow(/Seccomp profile not found/); + const originalExistsSync = fs.existsSync; + const existsSyncSpy = jest.spyOn(fs, 'existsSync').mockImplementation((filePath: fs.PathLike) => { + const normalizedPath = + typeof filePath === 'string' ? filePath : filePath.toString(); + + if ( + normalizedPath === 'seccomp-profile.json' || + normalizedPath.endsWith(`${path.sep}seccomp-profile.json`) + ) { + return false; + } - Object.defineProperty(global, '__dirname', { - value: originalDirname, - writable: true, - configurable: true, + return originalExistsSync(filePath); }); + + try { + await expect( + writeConfigs({ + workDir: tempDir, + sslBump: false, + allowedDomains: [], + agentCommand: 'echo test', + logLevel: 'info', + keepContainers: false, + buildLocal: false, + imageRegistry: 'ghcr.io/github/gh-aw-firewall', + imageTag: 'latest', + }) + ).rejects.toThrow(/Seccomp profile not found/); + } finally { + existsSyncSpy.mockRestore(); + } }); });