From 341bab91f9e5ef2e866f172dafd20a08f6084554 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:45:42 +0000 Subject: [PATCH 1/2] Initial plan From b755afeaa69e5d0558855f41a78be62a20ccfc44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:48:46 +0000 Subject: [PATCH 2/2] refactor: internalize sbx-manager test-only helpers --- src/container-runtime.test.ts | 4 ++- src/sbx-manager.test.ts | 6 ++-- src/sbx-manager.ts | 54 +++++++++++++++++++---------------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/container-runtime.test.ts b/src/container-runtime.test.ts index e7aded4e4..c0d77f6a9 100644 --- a/src/container-runtime.test.ts +++ b/src/container-runtime.test.ts @@ -1,5 +1,7 @@ import { resolveDockerRuntime, runtimeNeedsStaticDns, runtimeUsesComposeAgent, runtimeUsesIptables, isGvisorRuntime } from './container-runtime'; -import { sanitizeEnvForSbx } from './sbx-manager'; +import { testHelpers } from './sbx-manager'; + +const { sanitizeEnvForSbx } = testHelpers; describe('container-runtime', () => { describe('resolveDockerRuntime', () => { diff --git a/src/sbx-manager.test.ts b/src/sbx-manager.test.ts index a8ad39917..3d4a0888c 100644 --- a/src/sbx-manager.test.ts +++ b/src/sbx-manager.test.ts @@ -3,15 +3,15 @@ import { execInSandbox, isSbxAvailable, removeSandbox, - restoreHomeCredentials, - sanitizeEnvForSbx, - withLocalBinOnPath, SBX_DEFAULT_NAME, + testHelpers, } from './sbx-manager'; import * as fs from 'fs'; import { mockExecaFn } from './test-helpers/mock-execa.test-utils'; import { logger } from './logger'; +const { restoreHomeCredentials, sanitizeEnvForSbx, withLocalBinOnPath } = testHelpers; + // eslint-disable-next-line @typescript-eslint/no-require-imports jest.mock('execa', () => require('./test-helpers/mock-execa.test-utils').execaMockFactory()); // eslint-disable-next-line @typescript-eslint/no-require-imports diff --git a/src/sbx-manager.ts b/src/sbx-manager.ts index b80d427f8..f356dcda7 100644 --- a/src/sbx-manager.ts +++ b/src/sbx-manager.ts @@ -52,32 +52,12 @@ const SECRET_ENV_PATTERNS = [ /** Default sandbox name (single-sandbox-per-run model). */ export const SBX_DEFAULT_NAME = `${SBX_NAME_PREFIX}-${process.pid}`; -export interface SbxConfig { - /** Sandbox name (defaults to `awf-agent-`). */ - name?: string; - /** Workspace directory to mount into the sandbox. */ - workspaceDir: string; - /** Squid proxy IP for DOCKER_SANDBOXES_PROXY. */ - squidIp: string; - /** Squid proxy port (default 3128). */ - squidPort?: number; - /** Additional workspace mounts (read-only paths). */ - extraMounts?: string[]; -} - -export interface SbxExecOptions { - timeoutMinutes?: number; - workDir?: string; - environment?: Record; - tty?: boolean; -} - /** * Strips secret-bearing env vars from process.env so they never reach * the sbx CLI or the sandbox interior. Returns a shallow copy with * only non-secret entries plus any explicit overrides. */ -export function sanitizeEnvForSbx( +function sanitizeEnvForSbx( overrides: Record = {}, ): Record { const clean: Record = {}; @@ -157,7 +137,7 @@ function scrubHomeCredentials(homePath: string): void { * MUST run only after the sandbox is removed, because the home dirs are live * mounts — restoring while the VM is running would re-expose the secrets. */ -export function restoreHomeCredentials(): void { +function restoreHomeCredentials(): void { for (const { original, backup } of scrubbedCredentials) { try { if (fs.existsSync(backup)) { @@ -186,7 +166,18 @@ export function restoreHomeCredentials(): void { * Creates a Docker sbx sandbox with workspace mounts. * Sets `DOCKER_SANDBOXES_PROXY` to chain all egress through AWF's Squid. */ -export async function createSandbox(config: SbxConfig): Promise { +export async function createSandbox(config: { + /** Sandbox name (defaults to `awf-agent-`). */ + name?: string; + /** Workspace directory to mount into the sandbox. */ + workspaceDir: string; + /** Squid proxy IP for DOCKER_SANDBOXES_PROXY. */ + squidIp: string; + /** Squid proxy port (default 3128). */ + squidPort?: number; + /** Additional workspace mounts (read-only paths). */ + extraMounts?: string[]; +}): Promise { const name = config.name || SBX_DEFAULT_NAME; const squidPort = config.squidPort || 3128; const proxyUrl = `http://${config.squidIp}:${squidPort}`; @@ -356,10 +347,18 @@ export async function createSandbox(config: SbxConfig): Promise { * resolvable by name. `$HOME` resolves to the injected HOME (getRealUserHome), * which matches the wholesale-mounted home tool dirs. */ -export function withLocalBinOnPath(command: string): string { +function withLocalBinOnPath(command: string): string { return `export PATH="$HOME/.local/bin\${PATH:+:$PATH}"; ${command}`; } +/** @internal Exposed for unit tests only. */ +// ts-prune-ignore-next +export const testHelpers = { + sanitizeEnvForSbx, + restoreHomeCredentials, + withLocalBinOnPath, +}; + /** * Executes a command inside the sandbox, streaming stdout/stderr. * Returns the exit code of the command. @@ -367,7 +366,12 @@ export function withLocalBinOnPath(command: string): string { export async function execInSandbox( name: string, command: string, - options?: SbxExecOptions, + options?: { + timeoutMinutes?: number; + workDir?: string; + environment?: Record; + tty?: boolean; + }, ): Promise<{ exitCode: number }> { logger.info(`Executing in sandbox "${name}": ${command}`);