@agentforge-ai/sandbox 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +197 -0
- package/README.md +108 -0
- package/dist/container-pool.d.ts +89 -0
- package/dist/container-pool.js +539 -0
- package/dist/container-pool.js.map +1 -0
- package/dist/docker-sandbox.d.ts +92 -0
- package/dist/docker-sandbox.js +343 -0
- package/dist/docker-sandbox.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +701 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox-manager.d.ts +77 -0
- package/dist/sandbox-manager.js +498 -0
- package/dist/sandbox-manager.js.map +1 -0
- package/dist/security.d.ts +59 -0
- package/dist/security.js +89 -0
- package/dist/security.js.map +1 -0
- package/dist/types.d.ts +179 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sandbox-manager.ts","../src/docker-sandbox.ts","../src/security.ts"],"sourcesContent":["/**\n * @module sandbox-manager\n *\n * SandboxManager — factory + registry for sandbox instances.\n *\n * Responsibilities:\n * - Create the correct sandbox type (Docker | E2B) from a unified config.\n * - Apply the `agentforge-{scope}-{id}` naming convention.\n * - Track active sandboxes and clean them up on process exit.\n * - Verify Docker availability on startup; print a friendly message if absent.\n */\n\nimport Dockerode from 'dockerode';\nimport { randomUUID } from 'node:crypto';\nimport { DockerSandbox } from './docker-sandbox.js';\nimport type {\n DockerSandboxConfig,\n ExecOptions,\n ExecResult,\n SandboxManagerConfig,\n SandboxProvider,\n} from './types.js';\n\n/**\n * A thin E2B stub that satisfies {@link SandboxProvider} but throws at runtime.\n * The real E2B implementation lives in @agentforge-ai/core.\n * This stub lets the manager compile and be tested without the E2B dependency.\n */\nclass E2BProviderStub implements SandboxProvider {\n async start(): Promise<void> {\n throw new Error(\n 'SandboxManager: E2B provider is not bundled in @agentforge-ai/sandbox. ' +\n 'Use the SandboxManager from @agentforge-ai/core instead.',\n );\n }\n async stop(): Promise<void> { /* noop until started */ }\n async destroy(): Promise<void> { /* noop */ }\n async exec(_cmd: string, _opts?: ExecOptions): Promise<ExecResult> {\n throw new Error('E2BProviderStub: not implemented');\n }\n async readFile(_path: string): Promise<string> {\n throw new Error('E2BProviderStub: not implemented');\n }\n async writeFile(_path: string, _content: string): Promise<void> {\n throw new Error('E2BProviderStub: not implemented');\n }\n async isRunning(): Promise<boolean> { return false; }\n getContainerId(): string | null { return null; }\n}\n\n/**\n * Checks whether the Docker daemon is reachable.\n *\n * @param docker - Dockerode instance to ping.\n * @returns `true` if Docker is available, `false` otherwise.\n */\nexport async function isDockerAvailable(docker: Dockerode): Promise<boolean> {\n try {\n await docker.ping();\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Central factory and lifecycle manager for sandbox instances.\n *\n * @example\n * ```ts\n * const manager = new SandboxManager({ provider: 'docker' });\n * await manager.initialize();\n *\n * const sb = await manager.create({ scope: 'agent', workspaceAccess: 'none' });\n * const result = await sb.exec('echo hello');\n * await manager.destroy(sb);\n *\n * await manager.shutdown();\n * ```\n */\nexport class SandboxManager {\n private readonly config: SandboxManagerConfig;\n private readonly docker: Dockerode;\n private readonly active = new Map<string, SandboxProvider>();\n private shutdownRegistered = false;\n\n constructor(config: SandboxManagerConfig = {}) {\n this.config = { provider: 'docker', ...config };\n\n const dockerHostCfg = config.dockerHost;\n if (dockerHostCfg?.host) {\n this.docker = new Dockerode({\n host: dockerHostCfg.host,\n port: dockerHostCfg.port ?? 2376,\n protocol: dockerHostCfg.protocol ?? 'http',\n });\n } else {\n this.docker = new Dockerode({\n socketPath: dockerHostCfg?.socketPath ?? '/var/run/docker.sock',\n });\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /**\n * Initialize the manager. For the Docker provider this verifies that the\n * Docker daemon is reachable. Call this once at application startup.\n *\n * @throws Error if the Docker daemon cannot be reached (Docker provider only).\n */\n async initialize(): Promise<void> {\n if (this.config.provider === 'docker') {\n const available = await isDockerAvailable(this.docker);\n if (!available) {\n console.warn(\n '[SandboxManager] Docker daemon is not reachable. ' +\n 'Agent tool execution will fail until Docker is started. ' +\n 'Install Docker: https://docs.docker.com/get-docker/',\n );\n }\n }\n\n this._registerShutdownHandlers();\n }\n\n /**\n * Create and start a new sandbox.\n *\n * The sandbox is registered internally; call {@link SandboxManager.destroy}\n * or {@link SandboxManager.shutdown} to release it.\n *\n * @param overrides - Per-sandbox config overrides merged with manager defaults.\n */\n async create(\n overrides: Pick<DockerSandboxConfig, 'scope' | 'workspaceAccess'> &\n Partial<DockerSandboxConfig>,\n ): Promise<SandboxProvider> {\n if (this.config.provider === 'e2b') {\n const stub = new E2BProviderStub();\n const id = this._generateId(overrides.scope);\n this.active.set(id, stub);\n return stub;\n }\n\n const mergedConfig: DockerSandboxConfig = {\n image: 'node:22-slim',\n ...this.config.dockerConfig,\n ...overrides,\n };\n\n const sandbox = new DockerSandbox(mergedConfig, this.docker);\n await sandbox.start();\n\n const id = this._generateId(overrides.scope);\n this.active.set(id, sandbox);\n return sandbox;\n }\n\n /**\n * Destroy a specific sandbox and remove it from the active registry.\n */\n async destroy(sandbox: SandboxProvider): Promise<void> {\n await sandbox.destroy();\n for (const [key, value] of this.active) {\n if (value === sandbox) {\n this.active.delete(key);\n break;\n }\n }\n }\n\n /**\n * Destroy all active sandboxes and shut the manager down.\n * Called automatically on SIGTERM / SIGINT when registered.\n */\n async shutdown(): Promise<void> {\n const destroyAll = Array.from(this.active.values()).map((sb) =>\n sb.destroy().catch((err: unknown) => {\n console.error('[SandboxManager] Error destroying sandbox during shutdown:', err);\n }),\n );\n await Promise.all(destroyAll);\n this.active.clear();\n }\n\n /**\n * Returns the number of currently active sandboxes.\n */\n get activeCount(): number {\n return this.active.size;\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _generateId(scope: string): string {\n return `agentforge-${scope}-${randomUUID().slice(0, 8)}`;\n }\n\n private _registerShutdownHandlers(): void {\n if (this.shutdownRegistered) return;\n this.shutdownRegistered = true;\n\n const handler = () => {\n void this.shutdown().finally(() => process.exit(0));\n };\n\n process.once('SIGTERM', handler);\n process.once('SIGINT', handler);\n process.once('beforeExit', () => void this.shutdown());\n }\n}\n","/**\n * @module docker-sandbox\n *\n * DockerSandbox — a container-backed {@link SandboxProvider} for AgentForge.\n *\n * Each instance manages exactly one Docker container. The container is\n * created lazily on the first call to {@link DockerSandbox.start}, executed\n * via the Docker exec API, and destroyed via {@link DockerSandbox.destroy}.\n *\n * @example\n * ```ts\n * const sandbox = new DockerSandbox({\n * scope: 'agent',\n * workspaceAccess: 'ro',\n * workspacePath: '/home/user/project',\n * resourceLimits: { memoryMb: 512, cpuShares: 512 },\n * });\n * await sandbox.start();\n * const result = await sandbox.exec('echo hello');\n * console.log(result.stdout); // \"hello\\n\"\n * await sandbox.destroy();\n * ```\n */\n\nimport Dockerode from 'dockerode';\nimport { randomUUID } from 'node:crypto';\nimport type { DockerSandboxConfig, ExecOptions, ExecResult, SandboxProvider } from './types.js';\nimport { DEFAULT_CAP_DROP, SecurityError, validateBinds, validateCommand, validateImageName } from './security.js';\n\n/** Default Docker image used when none is specified. */\nconst DEFAULT_IMAGE = 'node:22-slim';\n\n/** Default per-exec timeout in milliseconds. */\nconst DEFAULT_EXEC_TIMEOUT_MS = 30_000;\n\n/** Default container workspace mount point. */\nconst DEFAULT_CONTAINER_WORKSPACE = '/workspace';\n\n// ---------------------------------------------------------------------------\n// Stream demuxing\n// ---------------------------------------------------------------------------\n\n/**\n * Decodes the multiplexed stream that Docker returns for exec output.\n *\n * Docker prefixes every chunk with an 8-byte header:\n * [stream_type(1)] [0(3)] [size(4 BE)]\n * where stream_type is 1 = stdout, 2 = stderr.\n */\nfunction demuxDockerStream(buffer: Buffer): { stdout: string; stderr: string } {\n let stdout = '';\n let stderr = '';\n let offset = 0;\n\n while (offset + 8 <= buffer.length) {\n const streamType = buffer[offset];\n const frameSize = buffer.readUInt32BE(offset + 4);\n offset += 8;\n\n if (offset + frameSize > buffer.length) break;\n\n const chunk = buffer.slice(offset, offset + frameSize).toString('utf8');\n offset += frameSize;\n\n if (streamType === 1) {\n stdout += chunk;\n } else if (streamType === 2) {\n stderr += chunk;\n }\n }\n\n return { stdout, stderr };\n}\n\n// ---------------------------------------------------------------------------\n// DockerSandbox\n// ---------------------------------------------------------------------------\n\n/**\n * Container-based sandbox using the Docker engine.\n *\n * Implements the {@link SandboxProvider} interface for full lifecycle\n * management, command execution, and file I/O within an isolated container.\n */\nexport class DockerSandbox implements SandboxProvider {\n private readonly config: Required<\n Pick<DockerSandboxConfig, 'scope' | 'workspaceAccess' | 'image' | 'containerWorkspacePath'>\n > &\n DockerSandboxConfig;\n\n private readonly docker: Dockerode;\n private container: Dockerode.Container | null = null;\n private containerId: string | null = null;\n private killTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * @param config - Sandbox configuration.\n * @param docker - Optional pre-configured Dockerode instance (useful in tests).\n */\n constructor(config: DockerSandboxConfig, docker?: Dockerode) {\n // Validate security constraints eagerly\n const image = config.image ?? DEFAULT_IMAGE;\n validateImageName(image);\n\n if (config.binds && config.binds.length > 0) {\n validateBinds(config.binds);\n }\n\n this.config = {\n ...config,\n image,\n containerWorkspacePath: config.containerWorkspacePath ?? DEFAULT_CONTAINER_WORKSPACE,\n };\n\n this.docker =\n docker ??\n new Dockerode(\n process.env['DOCKER_HOST']\n ? { host: process.env['DOCKER_HOST'] }\n : { socketPath: '/var/run/docker.sock' },\n );\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /**\n * Create and start the Docker container.\n * Idempotent — calling start() on an already-running sandbox is a no-op.\n */\n async start(): Promise<void> {\n if (this.container) return;\n\n const { image, scope, resourceLimits, binds, env, timeout, workspaceAccess, workspacePath, containerWorkspacePath } = this.config;\n\n const name = `agentforge-${scope}-${randomUUID().slice(0, 8)}`;\n\n const envArray = Object.entries(env ?? {}).map(([k, v]) => `${k}=${v}`);\n\n // Build bind mounts\n const allBinds: string[] = [...(binds ?? [])];\n\n // Mount workspace if configured\n if (workspaceAccess !== 'none' && workspacePath) {\n const mode = workspaceAccess === 'ro' ? 'ro' : 'rw';\n allBinds.push(`${workspacePath}:${containerWorkspacePath}:${mode}`);\n }\n\n const hostConfig: Dockerode.HostConfig = {\n // Resource limits\n CpuShares: resourceLimits?.cpuShares,\n Memory: resourceLimits?.memoryMb ? resourceLimits.memoryMb * 1024 * 1024 : undefined,\n PidsLimit: resourceLimits?.pidsLimit ?? 256,\n\n // Security hardening\n CapDrop: [...DEFAULT_CAP_DROP],\n SecurityOpt: ['no-new-privileges:true'],\n ReadonlyRootfs: false,\n\n // Bind mounts\n Binds: allBinds.length > 0 ? allBinds : undefined,\n };\n\n this.container = await this.docker.createContainer({\n name,\n Image: image,\n // Keep container alive — we run commands via exec\n Cmd: ['/bin/sh', '-c', 'while true; do sleep 3600; done'],\n Env: envArray,\n AttachStdin: false,\n AttachStdout: false,\n AttachStderr: false,\n Tty: false,\n NetworkDisabled: resourceLimits?.networkDisabled ?? false,\n WorkingDir: containerWorkspacePath,\n Labels: {\n 'agentforge.scope': scope,\n 'agentforge.managed': 'true',\n },\n HostConfig: hostConfig,\n });\n\n await this.container.start();\n this.containerId = this.container.id;\n\n // Auto-kill after configured timeout\n if (timeout && timeout > 0) {\n this.killTimer = setTimeout(() => {\n void this.destroy();\n }, timeout * 1000);\n }\n }\n\n /**\n * Stop the container gracefully (10 s grace period then SIGKILL).\n * The container is kept for potential restart.\n */\n async stop(): Promise<void> {\n this._clearKillTimer();\n if (!this.container) return;\n\n try {\n const info = await this.container.inspect();\n if (info.State.Running) {\n await this.container.stop({ t: 10 });\n }\n } catch {\n // Container may already be stopped — ignore\n }\n }\n\n /**\n * Destroy the container and release all resources.\n * Safe to call multiple times.\n */\n async destroy(): Promise<void> {\n this._clearKillTimer();\n const container = this.container;\n this.container = null;\n this.containerId = null;\n\n if (!container) return;\n\n try {\n await container.remove({ force: true });\n } catch {\n // Already gone — ignore\n }\n }\n\n // ---------------------------------------------------------------------------\n // Execution\n // ---------------------------------------------------------------------------\n\n /**\n * Execute a shell command inside the running container.\n *\n * @param command - Shell command string passed to `/bin/sh -c`.\n * @param options - Per-call options (timeout, cwd, env overrides).\n */\n async exec(command: string, options: ExecOptions = {}): Promise<ExecResult> {\n if (!this.container) {\n throw new Error('DockerSandbox: container is not running. Call start() first.');\n }\n\n // Defense-in-depth command validation\n validateCommand(command);\n\n const timeoutMs = options.timeout ?? DEFAULT_EXEC_TIMEOUT_MS;\n const envOverride = Object.entries(options.env ?? {}).map(([k, v]) => `${k}=${v}`);\n\n const execInstance = await this.container.exec({\n Cmd: ['/bin/sh', '-c', command],\n AttachStdout: true,\n AttachStderr: true,\n Tty: false,\n WorkingDir: options.cwd,\n Env: envOverride.length > 0 ? envOverride : undefined,\n });\n\n return new Promise<ExecResult>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`DockerSandbox: exec timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n execInstance.start({ hijack: true, stdin: false }, (err, stream) => {\n if (err) {\n clearTimeout(timer);\n reject(err);\n return;\n }\n\n if (!stream) {\n clearTimeout(timer);\n reject(new Error('DockerSandbox: no stream returned from exec'));\n return;\n }\n\n const chunks: Buffer[] = [];\n\n stream.on('data', (chunk: Buffer) => chunks.push(chunk));\n\n stream.on('end', async () => {\n clearTimeout(timer);\n try {\n const raw = Buffer.concat(chunks);\n const { stdout, stderr } = demuxDockerStream(raw);\n\n // Inspect exec to get exit code\n const inspectResult = await execInstance.inspect();\n const exitCode = inspectResult.ExitCode ?? 0;\n\n resolve({ stdout, stderr, exitCode });\n } catch (inspectErr) {\n reject(inspectErr);\n }\n });\n\n stream.on('error', (streamErr: Error) => {\n clearTimeout(timer);\n reject(streamErr);\n });\n });\n });\n }\n\n /**\n * Read a file from the container filesystem by running `cat`.\n *\n * @param path - Absolute path inside the container.\n */\n async readFile(path: string): Promise<string> {\n const result = await this.exec(`cat \"${path.replace(/\"/g, '\\\\\"')}\"`);\n if (result.exitCode !== 0) {\n throw new Error(\n `DockerSandbox.readFile: failed to read \"${path}\" (exit ${result.exitCode}): ${result.stderr}`,\n );\n }\n return result.stdout;\n }\n\n /**\n * Write content to a file inside the container using base64 encoding\n * to avoid shell quoting issues.\n *\n * @param path - Absolute path inside the container.\n * @param content - UTF-8 string content.\n */\n async writeFile(path: string, content: string): Promise<void> {\n const b64 = Buffer.from(content, 'utf8').toString('base64');\n const cmd = `printf '%s' \"${b64}\" | base64 -d > \"${path.replace(/\"/g, '\\\\\"')}\"`;\n const result = await this.exec(cmd);\n if (result.exitCode !== 0) {\n throw new Error(\n `DockerSandbox.writeFile: failed to write \"${path}\" (exit ${result.exitCode}): ${result.stderr}`,\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // Health\n // ---------------------------------------------------------------------------\n\n /**\n * Returns true if the underlying Docker container is running.\n */\n async isRunning(): Promise<boolean> {\n if (!this.container) return false;\n try {\n const info = await this.container.inspect();\n return info.State.Running === true;\n } catch {\n return false;\n }\n }\n\n /**\n * Returns the Docker container ID or null if not yet started.\n */\n getContainerId(): string | null {\n return this.containerId;\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _clearKillTimer(): void {\n if (this.killTimer !== null) {\n clearTimeout(this.killTimer);\n this.killTimer = null;\n }\n }\n}\n","/**\n * @module security\n *\n * Security helpers for the Docker sandbox implementation.\n *\n * Centralised in one module so policy changes propagate everywhere.\n * All validation functions throw {@link SecurityError} on violations.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Host-side paths that must never be bind-mounted into a sandbox container.\n * Mounting these paths would allow container-escape or privilege escalation.\n */\nexport const BLOCKED_BIND_PREFIXES: readonly string[] = [\n '/var/run/docker.sock',\n '/etc',\n '/proc',\n '/sys',\n '/dev',\n '/boot',\n '/root',\n];\n\n/**\n * Linux capabilities dropped by default for every sandbox container.\n * We start with no capabilities at all (drop \"ALL\") then add nothing back.\n */\nexport const DEFAULT_CAP_DROP: readonly string[] = ['ALL'];\n\n/**\n * Allowed image name patterns (non-arbitrary image names in production).\n * Only images that match one of these prefixes are permitted.\n * In test / dev the list can be extended via AGENTFORGE_ALLOWED_IMAGES env var.\n */\nconst BASE_ALLOWED_IMAGE_PREFIXES: readonly string[] = [\n 'node:',\n 'python:',\n 'ubuntu:',\n 'debian:',\n 'alpine:',\n 'agentforge/',\n];\n\n// ---------------------------------------------------------------------------\n// Validation functions\n// ---------------------------------------------------------------------------\n\n/**\n * Throws if the provided bind-mount spec contains a blocked host path.\n *\n * @param bind - A bind-mount spec in `host:container[:mode]` format.\n * @throws {SecurityError} when the host path is on the block-list.\n */\nexport function validateBind(bind: string): void {\n const hostPath = bind.split(':')[0];\n if (!hostPath) {\n throw new SecurityError(`Invalid bind mount spec: \"${bind}\"`);\n }\n\n for (const blocked of BLOCKED_BIND_PREFIXES) {\n if (hostPath === blocked || hostPath.startsWith(blocked + '/') || hostPath.startsWith(blocked)) {\n throw new SecurityError(\n `Bind mount \"${bind}\" is blocked. Host path \"${hostPath}\" matches blocked prefix \"${blocked}\".`,\n );\n }\n }\n}\n\n/**\n * Validate all bind mounts in the provided array.\n * @throws {SecurityError} on the first violation found.\n */\nexport function validateBinds(binds: string[]): void {\n for (const bind of binds) {\n validateBind(bind);\n }\n}\n\n/**\n * Validate that an image name is on the allow-list.\n *\n * In production (NODE_ENV === 'production') only known safe images are allowed.\n * In development/test any image name that passes format validation is accepted.\n *\n * Additional allowed prefixes can be injected via the\n * `AGENTFORGE_ALLOWED_IMAGES` env var (comma-separated prefixes).\n *\n * @param image - Docker image name, e.g. `node:22-slim`.\n * @throws {SecurityError} if the image is not permitted.\n */\nexport function validateImageName(image: string): void {\n if (!image || typeof image !== 'string') {\n throw new SecurityError('Image name must be a non-empty string.');\n }\n\n // Reject obviously dangerous patterns (shell metacharacters)\n if (/[;&|`$(){}[\\]<>]/.test(image)) {\n throw new SecurityError(`Image name \"${image}\" contains forbidden characters.`);\n }\n\n if (process.env['NODE_ENV'] !== 'production') {\n // In dev/test, just ensure the name is a plausible Docker image reference\n return;\n }\n\n // Build the full allow-list (base + env-configured extras)\n const extraPrefixes = (process.env['AGENTFORGE_ALLOWED_IMAGES'] ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\n const allowedPrefixes = [...BASE_ALLOWED_IMAGE_PREFIXES, ...extraPrefixes];\n\n const allowed = allowedPrefixes.some((prefix) => image.startsWith(prefix));\n if (!allowed) {\n throw new SecurityError(\n `Image \"${image}\" is not on the allow-list. ` +\n `Allowed prefixes: ${allowedPrefixes.join(', ')}. ` +\n `Add custom prefixes via AGENTFORGE_ALLOWED_IMAGES env var.`,\n );\n }\n}\n\n/**\n * Validate that a command does not contain obvious escape attempts.\n * This is a defense-in-depth measure — the container itself is the primary boundary.\n *\n * @param command - The shell command to validate.\n * @throws {SecurityError} if the command contains dangerous patterns.\n */\nexport function validateCommand(command: string): void {\n if (!command || typeof command !== 'string') {\n throw new SecurityError('Command must be a non-empty string.');\n }\n\n // Block attempts to access the Docker socket from within the container\n const dangerousPatterns = [\n /docker\\.sock/i,\n /nsenter\\s/i,\n /mount\\s+-t\\s+proc/i,\n ];\n\n for (const pattern of dangerousPatterns) {\n if (pattern.test(command)) {\n throw new SecurityError(\n `Command contains a potentially dangerous pattern: ${pattern.source}`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error class\n// ---------------------------------------------------------------------------\n\n/**\n * A structured error type for sandbox security violations.\n */\nexport class SecurityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SecurityError';\n }\n}\n"],"mappings":"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","names":["Dockerode","randomUUID","Dockerode","id","randomUUID"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module security
|
|
3
|
+
*
|
|
4
|
+
* Security helpers for the Docker sandbox implementation.
|
|
5
|
+
*
|
|
6
|
+
* Centralised in one module so policy changes propagate everywhere.
|
|
7
|
+
* All validation functions throw {@link SecurityError} on violations.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Host-side paths that must never be bind-mounted into a sandbox container.
|
|
11
|
+
* Mounting these paths would allow container-escape or privilege escalation.
|
|
12
|
+
*/
|
|
13
|
+
declare const BLOCKED_BIND_PREFIXES: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Linux capabilities dropped by default for every sandbox container.
|
|
16
|
+
* We start with no capabilities at all (drop "ALL") then add nothing back.
|
|
17
|
+
*/
|
|
18
|
+
declare const DEFAULT_CAP_DROP: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* Throws if the provided bind-mount spec contains a blocked host path.
|
|
21
|
+
*
|
|
22
|
+
* @param bind - A bind-mount spec in `host:container[:mode]` format.
|
|
23
|
+
* @throws {SecurityError} when the host path is on the block-list.
|
|
24
|
+
*/
|
|
25
|
+
declare function validateBind(bind: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Validate all bind mounts in the provided array.
|
|
28
|
+
* @throws {SecurityError} on the first violation found.
|
|
29
|
+
*/
|
|
30
|
+
declare function validateBinds(binds: string[]): void;
|
|
31
|
+
/**
|
|
32
|
+
* Validate that an image name is on the allow-list.
|
|
33
|
+
*
|
|
34
|
+
* In production (NODE_ENV === 'production') only known safe images are allowed.
|
|
35
|
+
* In development/test any image name that passes format validation is accepted.
|
|
36
|
+
*
|
|
37
|
+
* Additional allowed prefixes can be injected via the
|
|
38
|
+
* `AGENTFORGE_ALLOWED_IMAGES` env var (comma-separated prefixes).
|
|
39
|
+
*
|
|
40
|
+
* @param image - Docker image name, e.g. `node:22-slim`.
|
|
41
|
+
* @throws {SecurityError} if the image is not permitted.
|
|
42
|
+
*/
|
|
43
|
+
declare function validateImageName(image: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Validate that a command does not contain obvious escape attempts.
|
|
46
|
+
* This is a defense-in-depth measure — the container itself is the primary boundary.
|
|
47
|
+
*
|
|
48
|
+
* @param command - The shell command to validate.
|
|
49
|
+
* @throws {SecurityError} if the command contains dangerous patterns.
|
|
50
|
+
*/
|
|
51
|
+
declare function validateCommand(command: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* A structured error type for sandbox security violations.
|
|
54
|
+
*/
|
|
55
|
+
declare class SecurityError extends Error {
|
|
56
|
+
constructor(message: string);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { BLOCKED_BIND_PREFIXES, DEFAULT_CAP_DROP, SecurityError, validateBind, validateBinds, validateCommand, validateImageName };
|
package/dist/security.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/security.ts
|
|
2
|
+
var BLOCKED_BIND_PREFIXES = [
|
|
3
|
+
"/var/run/docker.sock",
|
|
4
|
+
"/etc",
|
|
5
|
+
"/proc",
|
|
6
|
+
"/sys",
|
|
7
|
+
"/dev",
|
|
8
|
+
"/boot",
|
|
9
|
+
"/root"
|
|
10
|
+
];
|
|
11
|
+
var DEFAULT_CAP_DROP = ["ALL"];
|
|
12
|
+
var BASE_ALLOWED_IMAGE_PREFIXES = [
|
|
13
|
+
"node:",
|
|
14
|
+
"python:",
|
|
15
|
+
"ubuntu:",
|
|
16
|
+
"debian:",
|
|
17
|
+
"alpine:",
|
|
18
|
+
"agentforge/"
|
|
19
|
+
];
|
|
20
|
+
function validateBind(bind) {
|
|
21
|
+
const hostPath = bind.split(":")[0];
|
|
22
|
+
if (!hostPath) {
|
|
23
|
+
throw new SecurityError(`Invalid bind mount spec: "${bind}"`);
|
|
24
|
+
}
|
|
25
|
+
for (const blocked of BLOCKED_BIND_PREFIXES) {
|
|
26
|
+
if (hostPath === blocked || hostPath.startsWith(blocked + "/") || hostPath.startsWith(blocked)) {
|
|
27
|
+
throw new SecurityError(
|
|
28
|
+
`Bind mount "${bind}" is blocked. Host path "${hostPath}" matches blocked prefix "${blocked}".`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function validateBinds(binds) {
|
|
34
|
+
for (const bind of binds) {
|
|
35
|
+
validateBind(bind);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function validateImageName(image) {
|
|
39
|
+
if (!image || typeof image !== "string") {
|
|
40
|
+
throw new SecurityError("Image name must be a non-empty string.");
|
|
41
|
+
}
|
|
42
|
+
if (/[;&|`$(){}[\]<>]/.test(image)) {
|
|
43
|
+
throw new SecurityError(`Image name "${image}" contains forbidden characters.`);
|
|
44
|
+
}
|
|
45
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const extraPrefixes = (process.env["AGENTFORGE_ALLOWED_IMAGES"] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
49
|
+
const allowedPrefixes = [...BASE_ALLOWED_IMAGE_PREFIXES, ...extraPrefixes];
|
|
50
|
+
const allowed = allowedPrefixes.some((prefix) => image.startsWith(prefix));
|
|
51
|
+
if (!allowed) {
|
|
52
|
+
throw new SecurityError(
|
|
53
|
+
`Image "${image}" is not on the allow-list. Allowed prefixes: ${allowedPrefixes.join(", ")}. Add custom prefixes via AGENTFORGE_ALLOWED_IMAGES env var.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function validateCommand(command) {
|
|
58
|
+
if (!command || typeof command !== "string") {
|
|
59
|
+
throw new SecurityError("Command must be a non-empty string.");
|
|
60
|
+
}
|
|
61
|
+
const dangerousPatterns = [
|
|
62
|
+
/docker\.sock/i,
|
|
63
|
+
/nsenter\s/i,
|
|
64
|
+
/mount\s+-t\s+proc/i
|
|
65
|
+
];
|
|
66
|
+
for (const pattern of dangerousPatterns) {
|
|
67
|
+
if (pattern.test(command)) {
|
|
68
|
+
throw new SecurityError(
|
|
69
|
+
`Command contains a potentially dangerous pattern: ${pattern.source}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var SecurityError = class extends Error {
|
|
75
|
+
constructor(message) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = "SecurityError";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
export {
|
|
81
|
+
BLOCKED_BIND_PREFIXES,
|
|
82
|
+
DEFAULT_CAP_DROP,
|
|
83
|
+
SecurityError,
|
|
84
|
+
validateBind,
|
|
85
|
+
validateBinds,
|
|
86
|
+
validateCommand,
|
|
87
|
+
validateImageName
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/security.ts"],"sourcesContent":["/**\n * @module security\n *\n * Security helpers for the Docker sandbox implementation.\n *\n * Centralised in one module so policy changes propagate everywhere.\n * All validation functions throw {@link SecurityError} on violations.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Host-side paths that must never be bind-mounted into a sandbox container.\n * Mounting these paths would allow container-escape or privilege escalation.\n */\nexport const BLOCKED_BIND_PREFIXES: readonly string[] = [\n '/var/run/docker.sock',\n '/etc',\n '/proc',\n '/sys',\n '/dev',\n '/boot',\n '/root',\n];\n\n/**\n * Linux capabilities dropped by default for every sandbox container.\n * We start with no capabilities at all (drop \"ALL\") then add nothing back.\n */\nexport const DEFAULT_CAP_DROP: readonly string[] = ['ALL'];\n\n/**\n * Allowed image name patterns (non-arbitrary image names in production).\n * Only images that match one of these prefixes are permitted.\n * In test / dev the list can be extended via AGENTFORGE_ALLOWED_IMAGES env var.\n */\nconst BASE_ALLOWED_IMAGE_PREFIXES: readonly string[] = [\n 'node:',\n 'python:',\n 'ubuntu:',\n 'debian:',\n 'alpine:',\n 'agentforge/',\n];\n\n// ---------------------------------------------------------------------------\n// Validation functions\n// ---------------------------------------------------------------------------\n\n/**\n * Throws if the provided bind-mount spec contains a blocked host path.\n *\n * @param bind - A bind-mount spec in `host:container[:mode]` format.\n * @throws {SecurityError} when the host path is on the block-list.\n */\nexport function validateBind(bind: string): void {\n const hostPath = bind.split(':')[0];\n if (!hostPath) {\n throw new SecurityError(`Invalid bind mount spec: \"${bind}\"`);\n }\n\n for (const blocked of BLOCKED_BIND_PREFIXES) {\n if (hostPath === blocked || hostPath.startsWith(blocked + '/') || hostPath.startsWith(blocked)) {\n throw new SecurityError(\n `Bind mount \"${bind}\" is blocked. Host path \"${hostPath}\" matches blocked prefix \"${blocked}\".`,\n );\n }\n }\n}\n\n/**\n * Validate all bind mounts in the provided array.\n * @throws {SecurityError} on the first violation found.\n */\nexport function validateBinds(binds: string[]): void {\n for (const bind of binds) {\n validateBind(bind);\n }\n}\n\n/**\n * Validate that an image name is on the allow-list.\n *\n * In production (NODE_ENV === 'production') only known safe images are allowed.\n * In development/test any image name that passes format validation is accepted.\n *\n * Additional allowed prefixes can be injected via the\n * `AGENTFORGE_ALLOWED_IMAGES` env var (comma-separated prefixes).\n *\n * @param image - Docker image name, e.g. `node:22-slim`.\n * @throws {SecurityError} if the image is not permitted.\n */\nexport function validateImageName(image: string): void {\n if (!image || typeof image !== 'string') {\n throw new SecurityError('Image name must be a non-empty string.');\n }\n\n // Reject obviously dangerous patterns (shell metacharacters)\n if (/[;&|`$(){}[\\]<>]/.test(image)) {\n throw new SecurityError(`Image name \"${image}\" contains forbidden characters.`);\n }\n\n if (process.env['NODE_ENV'] !== 'production') {\n // In dev/test, just ensure the name is a plausible Docker image reference\n return;\n }\n\n // Build the full allow-list (base + env-configured extras)\n const extraPrefixes = (process.env['AGENTFORGE_ALLOWED_IMAGES'] ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\n const allowedPrefixes = [...BASE_ALLOWED_IMAGE_PREFIXES, ...extraPrefixes];\n\n const allowed = allowedPrefixes.some((prefix) => image.startsWith(prefix));\n if (!allowed) {\n throw new SecurityError(\n `Image \"${image}\" is not on the allow-list. ` +\n `Allowed prefixes: ${allowedPrefixes.join(', ')}. ` +\n `Add custom prefixes via AGENTFORGE_ALLOWED_IMAGES env var.`,\n );\n }\n}\n\n/**\n * Validate that a command does not contain obvious escape attempts.\n * This is a defense-in-depth measure — the container itself is the primary boundary.\n *\n * @param command - The shell command to validate.\n * @throws {SecurityError} if the command contains dangerous patterns.\n */\nexport function validateCommand(command: string): void {\n if (!command || typeof command !== 'string') {\n throw new SecurityError('Command must be a non-empty string.');\n }\n\n // Block attempts to access the Docker socket from within the container\n const dangerousPatterns = [\n /docker\\.sock/i,\n /nsenter\\s/i,\n /mount\\s+-t\\s+proc/i,\n ];\n\n for (const pattern of dangerousPatterns) {\n if (pattern.test(command)) {\n throw new SecurityError(\n `Command contains a potentially dangerous pattern: ${pattern.source}`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error class\n// ---------------------------------------------------------------------------\n\n/**\n * A structured error type for sandbox security violations.\n */\nexport class SecurityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SecurityError';\n }\n}\n"],"mappings":";AAiBO,IAAM,wBAA2C;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,IAAM,mBAAsC,CAAC,KAAK;AAOzD,IAAM,8BAAiD;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAYO,SAAS,aAAa,MAAoB;AAC/C,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,CAAC;AAClC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,cAAc,6BAA6B,IAAI,GAAG;AAAA,EAC9D;AAEA,aAAW,WAAW,uBAAuB;AAC3C,QAAI,aAAa,WAAW,SAAS,WAAW,UAAU,GAAG,KAAK,SAAS,WAAW,OAAO,GAAG;AAC9F,YAAM,IAAI;AAAA,QACR,eAAe,IAAI,4BAA4B,QAAQ,6BAA6B,OAAO;AAAA,MAC7F;AAAA,IACF;AAAA,EACF;AACF;AAMO,SAAS,cAAc,OAAuB;AACnD,aAAW,QAAQ,OAAO;AACxB,iBAAa,IAAI;AAAA,EACnB;AACF;AAcO,SAAS,kBAAkB,OAAqB;AACrD,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,IAAI,cAAc,wCAAwC;AAAA,EAClE;AAGA,MAAI,mBAAmB,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,cAAc,eAAe,KAAK,kCAAkC;AAAA,EAChF;AAEA,MAAI,QAAQ,IAAI,UAAU,MAAM,cAAc;AAE5C;AAAA,EACF;AAGA,QAAM,iBAAiB,QAAQ,IAAI,2BAA2B,KAAK,IAChE,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEjB,QAAM,kBAAkB,CAAC,GAAG,6BAA6B,GAAG,aAAa;AAEzE,QAAM,UAAU,gBAAgB,KAAK,CAAC,WAAW,MAAM,WAAW,MAAM,CAAC;AACzE,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,UAAU,KAAK,iDACQ,gBAAgB,KAAK,IAAI,CAAC;AAAA,IAEnD;AAAA,EACF;AACF;AASO,SAAS,gBAAgB,SAAuB;AACrD,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,cAAc,qCAAqC;AAAA,EAC/D;AAGA,QAAM,oBAAoB;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,aAAW,WAAW,mBAAmB;AACvC,QAAI,QAAQ,KAAK,OAAO,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,qDAAqD,QAAQ,MAAM;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AACF;AASO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;","names":[]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module types
|
|
3
|
+
*
|
|
4
|
+
* Shared types for the AgentForge sandbox abstraction layer.
|
|
5
|
+
*
|
|
6
|
+
* Every sandbox provider (Docker, E2B, future providers) implements the
|
|
7
|
+
* {@link SandboxProvider} interface, ensuring a unified API for agent
|
|
8
|
+
* tool execution regardless of the underlying isolation technology.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Options passed to {@link SandboxProvider.exec} for a single command invocation.
|
|
12
|
+
*/
|
|
13
|
+
interface ExecOptions {
|
|
14
|
+
/** Timeout in milliseconds for this specific execution. */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
/** Working directory inside the container / sandbox. */
|
|
17
|
+
cwd?: string;
|
|
18
|
+
/** Additional environment variables for this execution. */
|
|
19
|
+
env?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The result of a sandbox command execution.
|
|
23
|
+
*/
|
|
24
|
+
interface ExecResult {
|
|
25
|
+
/** Combined stdout output. */
|
|
26
|
+
stdout: string;
|
|
27
|
+
/** Combined stderr output. */
|
|
28
|
+
stderr: string;
|
|
29
|
+
/** Exit code (0 = success). */
|
|
30
|
+
exitCode: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Unified interface implemented by every sandbox provider (Docker, E2B, etc.).
|
|
34
|
+
*
|
|
35
|
+
* All agent tool execution must go through a SandboxProvider to ensure
|
|
36
|
+
* isolation and prevent malicious code from affecting the host system.
|
|
37
|
+
*/
|
|
38
|
+
interface SandboxProvider {
|
|
39
|
+
/** Start / warm up the sandbox so it is ready to accept commands. */
|
|
40
|
+
start(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Stop the sandbox gracefully (may keep the container for reuse).
|
|
43
|
+
* Implementations that do not support reuse may treat this as destroy().
|
|
44
|
+
*/
|
|
45
|
+
stop(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Permanently destroy all resources associated with this sandbox.
|
|
48
|
+
* Must be idempotent — safe to call multiple times.
|
|
49
|
+
*/
|
|
50
|
+
destroy(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Execute a shell command inside the sandbox.
|
|
53
|
+
* @param command - The shell command string to run (via /bin/sh -c).
|
|
54
|
+
* @param options - Per-call options (timeout, cwd, env).
|
|
55
|
+
*/
|
|
56
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Read a file from the sandbox filesystem.
|
|
59
|
+
* @param path - Absolute path inside the sandbox.
|
|
60
|
+
*/
|
|
61
|
+
readFile(path: string): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Write content to a file inside the sandbox filesystem.
|
|
64
|
+
* @param path - Absolute path inside the sandbox.
|
|
65
|
+
* @param content - UTF-8 string to write.
|
|
66
|
+
*/
|
|
67
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
68
|
+
/** Returns true if the sandbox is currently running and accepting commands. */
|
|
69
|
+
isRunning(): Promise<boolean>;
|
|
70
|
+
/** Returns the provider-specific container/session ID, or null if not started. */
|
|
71
|
+
getContainerId(): string | null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resource limits that can be applied to a DockerSandbox container.
|
|
75
|
+
*/
|
|
76
|
+
interface ResourceLimits {
|
|
77
|
+
/** CPU shares (relative weight). Docker default: 1024. */
|
|
78
|
+
cpuShares?: number;
|
|
79
|
+
/** Memory limit in megabytes. */
|
|
80
|
+
memoryMb?: number;
|
|
81
|
+
/** Disk quota in megabytes (requires overlay2 quota support). */
|
|
82
|
+
diskMb?: number;
|
|
83
|
+
/** When true the container has no network access. */
|
|
84
|
+
networkDisabled?: boolean;
|
|
85
|
+
/** Maximum number of PIDs inside the container. */
|
|
86
|
+
pidsLimit?: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Configuration for a {@link DockerSandbox} instance.
|
|
90
|
+
*/
|
|
91
|
+
interface DockerSandboxConfig {
|
|
92
|
+
/**
|
|
93
|
+
* Lifecycle scope:
|
|
94
|
+
* - `session` — one container per user session
|
|
95
|
+
* - `agent` — one container per agent run
|
|
96
|
+
* - `shared` — long-running shared container (pool-managed)
|
|
97
|
+
*/
|
|
98
|
+
scope: 'session' | 'agent' | 'shared';
|
|
99
|
+
/**
|
|
100
|
+
* How the host workspace is mounted inside the container:
|
|
101
|
+
* - `none` — no host filesystem access
|
|
102
|
+
* - `ro` — read-only bind mount
|
|
103
|
+
* - `rw` — read-write bind mount
|
|
104
|
+
*/
|
|
105
|
+
workspaceAccess: 'none' | 'ro' | 'rw';
|
|
106
|
+
/** Docker image to use. Defaults to `node:22-slim`. */
|
|
107
|
+
image?: string;
|
|
108
|
+
/** Resource constraints applied to the container. */
|
|
109
|
+
resourceLimits?: ResourceLimits;
|
|
110
|
+
/**
|
|
111
|
+
* Additional bind mounts in `host:container[:mode]` format.
|
|
112
|
+
* Dangerous paths (/var/run/docker.sock, /etc, /proc, /sys) are blocked.
|
|
113
|
+
*/
|
|
114
|
+
binds?: string[];
|
|
115
|
+
/** Environment variables injected into the container. */
|
|
116
|
+
env?: Record<string, string>;
|
|
117
|
+
/**
|
|
118
|
+
* Automatic kill timeout in seconds.
|
|
119
|
+
* The container is force-killed after this many seconds of total uptime.
|
|
120
|
+
*/
|
|
121
|
+
timeout?: number;
|
|
122
|
+
/**
|
|
123
|
+
* Path to the workspace directory on the host.
|
|
124
|
+
* Used when `workspaceAccess` is `ro` or `rw` to mount the workspace.
|
|
125
|
+
*/
|
|
126
|
+
workspacePath?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Path inside the container where the workspace is mounted.
|
|
129
|
+
* Defaults to `/workspace`.
|
|
130
|
+
*/
|
|
131
|
+
containerWorkspacePath?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Pool configuration for {@link ContainerPool}.
|
|
135
|
+
*/
|
|
136
|
+
interface PoolConfig {
|
|
137
|
+
/** Docker image to pre-warm. */
|
|
138
|
+
image: string;
|
|
139
|
+
/** Scope used when naming / managing pooled containers. */
|
|
140
|
+
scope: 'session' | 'agent' | 'shared';
|
|
141
|
+
/** Maximum number of warm containers to keep ready. Defaults to 3. */
|
|
142
|
+
maxSize?: number;
|
|
143
|
+
/** Seconds of idle time before evicting a warm container. Defaults to 300. */
|
|
144
|
+
idleTimeoutSeconds?: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A single entry tracked by the {@link ContainerPool}.
|
|
148
|
+
*/
|
|
149
|
+
interface PoolEntry {
|
|
150
|
+
sandbox: SandboxProvider;
|
|
151
|
+
createdAt: number;
|
|
152
|
+
lastUsedAt: number;
|
|
153
|
+
inUse: boolean;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Configuration for the {@link SandboxManager} factory.
|
|
157
|
+
*/
|
|
158
|
+
interface SandboxManagerConfig {
|
|
159
|
+
/**
|
|
160
|
+
* Which provider to use when creating new sandboxes.
|
|
161
|
+
* Defaults to `docker`.
|
|
162
|
+
*/
|
|
163
|
+
provider?: 'docker' | 'e2b';
|
|
164
|
+
/**
|
|
165
|
+
* Passed through to DockerSandbox when provider === 'docker'.
|
|
166
|
+
*/
|
|
167
|
+
dockerConfig?: Omit<DockerSandboxConfig, 'scope' | 'workspaceAccess'>;
|
|
168
|
+
/**
|
|
169
|
+
* Docker host configuration. Defaults to the local Unix socket.
|
|
170
|
+
*/
|
|
171
|
+
dockerHost?: {
|
|
172
|
+
socketPath?: string;
|
|
173
|
+
host?: string;
|
|
174
|
+
port?: number;
|
|
175
|
+
protocol?: 'http' | 'https';
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type { DockerSandboxConfig, ExecOptions, ExecResult, PoolConfig, PoolEntry, ResourceLimits, SandboxManagerConfig, SandboxProvider };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentforge-ai/sandbox",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Docker-based sandbox provider for AgentForge agent tool execution isolation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./docker": {
|
|
14
|
+
"types": "./dist/docker-sandbox.d.ts",
|
|
15
|
+
"import": "./dist/docker-sandbox.js"
|
|
16
|
+
},
|
|
17
|
+
"./security": {
|
|
18
|
+
"types": "./dist/security.d.ts",
|
|
19
|
+
"import": "./dist/security.js"
|
|
20
|
+
},
|
|
21
|
+
"./pool": {
|
|
22
|
+
"types": "./dist/container-pool.d.ts",
|
|
23
|
+
"import": "./dist/container-pool.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"dockerode": "^4.0.9"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/dockerode": "^3.3.47",
|
|
35
|
+
"@types/node": "^22.10.5",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.5.0",
|
|
38
|
+
"vitest": "^2.0.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"agentforge",
|
|
42
|
+
"sandbox",
|
|
43
|
+
"docker",
|
|
44
|
+
"isolation",
|
|
45
|
+
"container",
|
|
46
|
+
"agents",
|
|
47
|
+
"security"
|
|
48
|
+
],
|
|
49
|
+
"license": "Apache-2.0",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/Agentic-Engineering-Agency/agentforge.git",
|
|
53
|
+
"directory": "packages/sandbox"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:coverage": "vitest run --coverage",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"lint": "eslint src/",
|
|
62
|
+
"clean": "rm -rf dist"
|
|
63
|
+
}
|
|
64
|
+
}
|