dsh-pentester 0.0.1 → 1.0.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/README.md +39 -5
- package/agents/impact/profile.yml +19 -0
- package/agents/recon/profile.yml +23 -0
- package/agents/reporting/REPORT_TEMPLATE.md +333 -0
- package/agents/reporting/profile.yml +45 -0
- package/agents/threat-model/profile.yml +19 -0
- package/agents/validation/profile.yml +20 -0
- package/agents/vulnerability/profile.yml +19 -0
- package/agents/web/profile.yml +19 -0
- package/cordis.dev.patch.yml +16 -0
- package/cordis.patch.yml +2 -0
- package/docker/README.md +60 -0
- package/docker/kali/Dockerfile +881 -0
- package/docker/kali/README.md +104 -0
- package/docker/kali/REPORT_TEMPLATE.md +333 -0
- package/docker/kali/TOOL_PROMPT.md +362 -0
- package/docker/kali/bin/clone-kb +39 -0
- package/docker/kali/bin/entrypoint.sh +9 -0
- package/docker/kali/bin/gen-tools-json.sh +246 -0
- package/docker/kali/bin/record-traffic.sh +32 -0
- package/docker/kali/bin/tool-info +31 -0
- package/docker/kali/bin/tool-list +17 -0
- package/lib/catalog-BmeOyr6n.js +231 -0
- package/lib/catalog-BmeOyr6n.js.map +1 -0
- package/lib/catalog-DhE_r18k.js +231 -0
- package/lib/catalog-DhE_r18k.js.map +1 -0
- package/lib/client.js +15234 -0
- package/lib/client.js.map +7 -0
- package/lib/container-listing-BI6Xj8l2.js +56 -0
- package/lib/container-listing-BI6Xj8l2.js.map +1 -0
- package/lib/container-listing-BWZoBnN_.js +56 -0
- package/lib/container-listing-BWZoBnN_.js.map +1 -0
- package/lib/container-listing-CE5t-Y_H.js +56 -0
- package/lib/container-listing-CE5t-Y_H.js.map +1 -0
- package/lib/container-listing-DZPBvrLD.js +56 -0
- package/lib/container-listing-DZPBvrLD.js.map +1 -0
- package/lib/docker-tar-_wf8UrSj.js +66 -0
- package/lib/docker-tar-_wf8UrSj.js.map +1 -0
- package/lib/engagement-container-store-B9D8g0wq.js +641 -0
- package/lib/engagement-container-store-B9D8g0wq.js.map +1 -0
- package/lib/engagement-container-store-Cf-h0B4F.js +641 -0
- package/lib/engagement-container-store-Cf-h0B4F.js.map +1 -0
- package/lib/engagement-container-store-Cu1wuur1.js +639 -0
- package/lib/engagement-container-store-Cu1wuur1.js.map +1 -0
- package/lib/engagement-container-store-L_Gms-zi.js +632 -0
- package/lib/engagement-container-store-L_Gms-zi.js.map +1 -0
- package/lib/index.d.ts +41 -0
- package/lib/index.js +3200 -0
- package/lib/index.js.map +1 -0
- package/lib/model-CZoiogVs.js +149 -0
- package/lib/model-CZoiogVs.js.map +1 -0
- package/lib/model-DKZ5Rjik.js +145 -0
- package/lib/model-DKZ5Rjik.js.map +1 -0
- package/lib/worker-events-jFvaBp9x.js +351 -0
- package/lib/worker-events-jFvaBp9x.js.map +1 -0
- package/lib/worker-events-juSf0EDW.js +347 -0
- package/lib/worker-events-juSf0EDW.js.map +1 -0
- package/package.json +89 -8
- package/presets/pentester/agent.cordis.yml +194 -0
- package/presets/pentester/preset.yml +2 -0
- package/presets/pentester/run-state.mjs +210 -0
- package/index.js +0 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engagement-container-store-B9D8g0wq.js","names":["httpRequest"],"sources":["../src/domain/tools.ts","../src/tools/errors.ts","../src/tools/persist.ts","../src/tools/output-capture.ts","../src/tools/docker-engine.ts","../src/tools/engagement-container-store.ts"],"sourcesContent":["import type { AgentRunId } from './ids.js'\nimport type { EngagementId } from './ids.js'\nimport type { McpServerId } from './ids.js'\nimport type { TaskId } from './ids.js'\nimport type { ToolCallId } from './ids.js'\nimport type { ToolId } from './ids.js'\nimport type { ToolboxId } from './ids.js'\nimport type { ContextResourceRef } from './context.js'\n\nexport const TOOL_CALL_STATUSES = [\n 'requested',\n 'pending_approval',\n 'queued',\n 'running',\n 'succeeded',\n 'failed',\n 'timed_out',\n 'cancelled',\n 'interrupted',\n 'denied',\n 'capability_required',\n] as const\n\nexport type ToolCallStatus = (typeof TOOL_CALL_STATUSES)[number]\n\nexport const TOOL_RISK_DECISIONS = ['AUTO', 'APPROVAL', 'DENY'] as const\nexport type ToolRiskDecision = (typeof TOOL_RISK_DECISIONS)[number]\n\nexport const TOOL_OPERATION_CLASSES = [\n 'sandbox_setup',\n 'recon',\n 'read_only_validation',\n 'target_state_change',\n 'high_risk',\n 'escape',\n] as const\nexport type ToolOperationClass = (typeof TOOL_OPERATION_CLASSES)[number]\n\nexport const TOOL_FAILURE_KINDS = [\n 'timeout',\n 'transport',\n 'execution',\n 'backend_unavailable',\n 'toolbox_image_unavailable',\n 'cancelled',\n 'invalid_result',\n 'unknown',\n] as const\nexport type ToolFailureKind = (typeof TOOL_FAILURE_KINDS)[number]\n\nexport const TOOLBOX_LIFECYCLES = ['agent_run', 'engagement'] as const\nexport type ToolboxLifecycle = (typeof TOOLBOX_LIFECYCLES)[number]\n\nexport const MCP_LIFECYCLES = ['agent_run', 'engagement'] as const\nexport type McpLifecycle = (typeof MCP_LIFECYCLES)[number]\n\nexport const DOCKER_RUNTIME_STATUSES = ['running', 'stopped', 'missing', 'unhealthy', 'unknown'] as const\nexport type DockerRuntimeStatus = (typeof DOCKER_RUNTIME_STATUSES)[number]\n\nexport const MCP_RUNTIME_STATUSES = ['connected', 'reconnectable', 'stopped', 'unavailable', 'unknown'] as const\nexport type McpRuntimeStatus = (typeof MCP_RUNTIME_STATUSES)[number]\n\nexport const IMAGE_RESOLVE_SOURCES = ['existing', 'built', 'pulled'] as const\nexport type ImageResolveSource = (typeof IMAGE_RESOLVE_SOURCES)[number]\n\nexport const TOOL_RETRY_HINTS = ['transient', 'permanent', 'unknown'] as const\nexport type ToolRetryHint = (typeof TOOL_RETRY_HINTS)[number]\n\nexport const TOOL_TERMINATIONS = ['confirmed', 'requested', 'unknown'] as const\nexport type ToolTermination = (typeof TOOL_TERMINATIONS)[number]\n\nexport type ToolBackendBinding =\n | { readonly kind: 'docker'; readonly executor: string }\n | { readonly kind: 'mcp'; readonly serverId: McpServerId; readonly remoteToolName: string }\n\nexport interface ToolTimeoutSpec {\n readonly defaultMs: number\n readonly maxMs: number\n}\n\nexport interface ToolScopeTarget {\n readonly value: string\n}\n\nexport interface ToolCallRequest {\n readonly engagementId: EngagementId\n readonly taskId: TaskId\n readonly agentRunId: AgentRunId\n readonly toolId: ToolId\n readonly arguments: unknown\n readonly timeoutMs?: number\n readonly retryOf?: ToolCallId\n}\n\nexport interface ToolDefinition {\n readonly id: ToolId\n readonly name: string\n readonly description: string\n readonly toolboxId: ToolboxId\n readonly backend: ToolBackendBinding\n readonly risk: ToolRiskDecision\n readonly timeout: ToolTimeoutSpec\n readonly requiredArgumentKeys?: readonly string[]\n readonly scopeTargets?: (args: Record<string, unknown>) => readonly ToolScopeTarget[]\n}\n\nexport interface ToolboxProvisioning {\n readonly dockerfile?: {\n readonly context: string\n readonly dockerfile: string\n }\n readonly pull?: {\n readonly image: string\n }\n}\n\nexport interface ToolboxDefinition {\n readonly id: ToolboxId\n readonly name: string\n readonly description: string\n readonly toolIds: readonly ToolId[]\n readonly image: string\n readonly lifecycle: ToolboxLifecycle\n readonly provisioning?: ToolboxProvisioning\n readonly env?: Readonly<Record<string, string>>\n readonly secrets?: Readonly<Record<string, string>>\n}\n\nexport type ContainerExecSpec =\n | {\n readonly mode: 'argv'\n readonly toolboxId: ToolboxId\n readonly executable: string\n readonly args?: readonly string[]\n readonly cwd?: string\n readonly env?: Readonly<Record<string, string>>\n readonly timeoutMs?: number\n readonly operationClass?: ToolOperationClass\n readonly declaredTargets?: readonly ToolScopeTarget[]\n }\n | {\n readonly mode: 'shell'\n readonly toolboxId: ToolboxId\n readonly command: string\n readonly cwd?: string\n readonly env?: Readonly<Record<string, string>>\n readonly timeoutMs?: number\n readonly operationClass?: ToolOperationClass\n readonly declaredTargets?: readonly ToolScopeTarget[]\n }\n\n/** @deprecated Prefer ContainerExecSpec; kept as the parsed argv-shaped view. */\nexport interface ContainerExecArguments {\n readonly toolboxId: ToolboxId\n readonly mode: 'argv' | 'shell'\n readonly command: string\n readonly executable?: string\n readonly args?: readonly string[]\n readonly cwd?: string\n readonly env?: Readonly<Record<string, string>>\n readonly timeoutMs?: number\n readonly operationClass?: ToolOperationClass\n readonly declaredTargets?: readonly ToolScopeTarget[]\n}\n\nexport interface ToolOutputLimits {\n readonly maxInlineOutputBytes: number\n readonly maxTotalCapturedBytes: number\n}\n\nexport const DEFAULT_TOOL_OUTPUT_LIMITS: ToolOutputLimits = {\n maxInlineOutputBytes: 8_192,\n maxTotalCapturedBytes: 1_048_576,\n}\n\nexport interface DockerToolboxBinding {\n readonly engagementId: EngagementId\n readonly agentRunId?: AgentRunId\n readonly toolboxId: ToolboxId\n readonly lifecycle: ToolboxLifecycle\n readonly containerId: string\n readonly image: string\n readonly imageId?: string\n readonly imageSource?: ImageResolveSource\n}\n\nexport interface McpTransportDefinition {\n readonly kind: 'fixture' | 'stdio' | 'http'\n readonly command?: string\n readonly args?: readonly string[]\n readonly url?: string\n}\n\nexport interface McpServerConfig {\n readonly env?: Readonly<Record<string, string>>\n readonly secrets?: Readonly<Record<string, string>>\n}\n\nexport interface McpServerDefinition {\n readonly id: McpServerId\n readonly name: string\n readonly lifecycle: McpLifecycle\n readonly transport: McpTransportDefinition\n readonly config: McpServerConfig\n}\n\nexport interface McpRuntimeBinding {\n readonly engagementId: EngagementId\n readonly agentRunId?: AgentRunId\n readonly serverId: McpServerId\n readonly lifecycle: McpLifecycle\n readonly runtimeId?: string\n}\n\nexport interface ToolOutputResourceRef extends ContextResourceRef {}\n\nexport interface ToolCallRecord {\n readonly toolCallId: ToolCallId\n readonly request: ToolCallRequest\n readonly status: ToolCallStatus\n readonly createdAt: string\n readonly updatedAt: string\n readonly retryOf?: ToolCallId\n readonly reason?: string\n readonly outputRefs?: readonly ToolOutputResourceRef[]\n readonly retryHint?: ToolRetryHint\n readonly failureKind?: ToolFailureKind\n readonly termination?: ToolTermination\n readonly policySnapshot?: {\n readonly risk?: ToolRiskDecision\n readonly scope?: 'allowed' | 'denied'\n }\n}\n\nexport interface PendingToolCall {\n readonly toolCallId: ToolCallId\n readonly request: ToolCallRequest\n readonly status: 'pending_approval'\n readonly requestedAt: string\n readonly policySnapshot: {\n readonly risk: 'APPROVAL'\n readonly scope: 'allowed'\n readonly toolboxId: ToolboxId\n }\n}\n\nexport interface ToolBrokerConcurrencyLimits {\n readonly maxPerEngagement: number\n readonly maxPerAgentRun: number\n}\n\nexport const DEFAULT_TOOL_BROKER_LIMITS: ToolBrokerConcurrencyLimits = {\n maxPerEngagement: 4,\n maxPerAgentRun: 2,\n}","export type ToolBrokerErrorCode =\n | 'unknown_tool'\n | 'invalid_arguments'\n | 'invalid_timeout'\n | 'task_not_found'\n | 'agent_run_not_found'\n | 'task_run_mismatch'\n | 'out_of_scope'\n | 'capability_required'\n | 'denied'\n | 'not_pending'\n | 'call_not_found'\n | 'backend_failure'\n | 'toolbox_image_unavailable'\n | 'mcp_unauthorized'\n | 'persist_failed'\n\nexport class ToolBrokerError extends Error {\n readonly code: ToolBrokerErrorCode\n\n constructor(code: ToolBrokerErrorCode, message: string) {\n super(message)\n this.name = 'ToolBrokerError'\n this.code = code\n }\n}","import { randomUUID } from 'node:crypto'\nimport { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'\nimport { dirname } from 'node:path'\nimport { ToolBrokerError } from './errors.js'\n\nexport function toolsRuntimeDir(engagementDir: string): string {\n return `${engagementDir}/runtime/tools`\n}\n\nexport async function writeJsonAtomic(target: string, value: unknown): Promise<void> {\n await mkdir(dirname(target), { recursive: true })\n const tmp = `${target}.tmp-${randomUUID()}`\n try {\n await writeFile(tmp, `${JSON.stringify(value, null, 2)}\\n`, 'utf8')\n await rename(tmp, target)\n } catch (error) {\n try {\n await unlink(tmp)\n } catch {\n // tmp may not exist\n }\n throw new ToolBrokerError(\n 'persist_failed',\n error instanceof Error ? error.message : `Failed to write ${target}`,\n )\n }\n}\n\nexport async function readJsonFile(path: string): Promise<unknown | null> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as unknown\n } catch (error) {\n const code = (error as { code?: string }).code\n if (code === 'ENOENT') {\n return null\n }\n if (error instanceof SyntaxError) {\n return null\n }\n throw error\n }\n}","import type { ToolOutputLimits } from '../domain/tools.js'\n\nexport interface CapturedOutputs {\n readonly files: Readonly<Record<string, string>>\n readonly binaryFiles: Readonly<Record<string, Buffer>>\n readonly inline: { stdout?: string; stderr?: string }\n readonly truncated: boolean\n readonly captureLimitReached: boolean\n readonly binary: boolean\n}\n\nexport class BoundedCapture {\n truncated = false\n captureLimitReached = false\n buffered = 0\n private remaining: number\n private readonly stdoutParts: Buffer[] = []\n private readonly stderrParts: Buffer[] = []\n\n constructor(maxTotalCapturedBytes: number) {\n this.remaining = maxTotalCapturedBytes\n }\n\n get full(): boolean {\n return this.remaining <= 0\n }\n\n accept(stream: 'stdout' | 'stderr', chunk: Buffer): void {\n if (chunk.length === 0) {\n return\n }\n if (this.remaining <= 0) {\n this.captureLimitReached = true\n this.truncated = true\n return\n }\n let take = chunk\n if (chunk.length > this.remaining) {\n take = chunk.subarray(0, this.remaining)\n this.captureLimitReached = true\n this.truncated = true\n }\n this.remaining -= take.length\n this.buffered += take.length\n if (stream === 'stderr') {\n this.stderrParts.push(take)\n } else {\n this.stdoutParts.push(take)\n }\n }\n\n stdout(): Buffer {\n return Buffer.concat(this.stdoutParts)\n }\n\n stderr(): Buffer {\n return Buffer.concat(this.stderrParts)\n }\n}\n\nexport class DockerMuxCapture {\n readonly inner: BoundedCapture\n private leftover = Buffer.alloc(0)\n\n constructor(maxTotalCapturedBytes: number) {\n this.inner = new BoundedCapture(maxTotalCapturedBytes)\n }\n\n pushMuxed(chunk: Buffer): void {\n this.leftover = Buffer.concat([this.leftover, chunk])\n while (this.leftover.length >= 8) {\n const type = this.leftover[0]\n if (type !== 1 && type !== 2 && type !== 0) {\n this.inner.accept('stdout', this.leftover)\n this.leftover = Buffer.alloc(0)\n return\n }\n const size = this.leftover.readUInt32BE(4)\n if (this.leftover.length < 8 + size) {\n return\n }\n const payload = this.leftover.subarray(8, 8 + size)\n this.leftover = this.leftover.subarray(8 + size)\n this.inner.accept(type === 2 ? 'stderr' : 'stdout', payload)\n }\n }\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n if (buf.includes(0)) {\n return true\n }\n const sample = buf.subarray(0, Math.min(buf.length, 4096))\n try {\n const decoded = new TextDecoder('utf8', { fatal: true }).decode(sample)\n return decoded.includes('\\uFFFD')\n } catch {\n return true\n }\n}\n\nexport function applyCaptureLimits(\n streams: { readonly stdout: Buffer; readonly stderr: Buffer },\n limits: ToolOutputLimits,\n): CapturedOutputs {\n let truncated = false\n let captureLimitReached = false\n let remaining = limits.maxTotalCapturedBytes\n const take = (buf: Buffer, name: string): Buffer => {\n if (remaining <= 0) {\n captureLimitReached = true\n truncated = true\n return Buffer.alloc(0)\n }\n if (buf.length > remaining) {\n captureLimitReached = true\n truncated = true\n const slice = buf.subarray(0, remaining)\n remaining = 0\n void name\n return slice\n }\n remaining -= buf.length\n return buf\n }\n const stdout = take(streams.stdout, 'stdout')\n const stderr = take(streams.stderr, 'stderr')\n const stdoutBinary = isBinaryBuffer(stdout) && stdout.length > 0\n const stderrBinary = isBinaryBuffer(stderr) && stderr.length > 0\n const files: Record<string, string> = {}\n const binaryFiles: Record<string, Buffer> = {}\n const inline: { stdout?: string; stderr?: string } = {}\n const put = (name: 'stdout' | 'stderr', buf: Buffer, binary: boolean) => {\n if (buf.length === 0) {\n return\n }\n if (binary) {\n binaryFiles[`${name}.bin`] = buf\n return\n }\n const text = buf.toString('utf8')\n files[`${name}.txt`] = text\n if (buf.length <= limits.maxInlineOutputBytes && !truncated) {\n inline[name] = text\n }\n }\n put('stdout', stdout, stdoutBinary)\n put('stderr', stderr, stderrBinary)\n return {\n files,\n binaryFiles,\n inline,\n truncated,\n captureLimitReached,\n binary: stdoutBinary || stderrBinary,\n }\n}\n","import { request as httpRequest } from 'node:http'\nimport type { ToolCallId } from '../domain/ids.js'\nimport { DEFAULT_TOOL_OUTPUT_LIMITS } from '../domain/tools.js'\nimport { BoundedCapture, DockerMuxCapture } from './output-capture.js'\n\nexport interface DockerImageInspect {\n readonly id: string\n readonly digest?: string\n}\n\nexport interface DockerContainerInspect {\n readonly id: string\n readonly running: boolean\n readonly image: string\n}\n\n/** Minimal container row for listing (P0-B GC scans by label/name/image). */\nexport interface DockerContainerListItem {\n readonly id: string\n readonly name: string\n readonly image: string\n readonly running: boolean\n readonly labels?: Readonly<Record<string, string>>\n}\n\nexport interface DockerListContainersInput {\n readonly all?: boolean\n /** Docker label filter: `key=value`. */\n readonly label?: string\n}\n\nexport interface DockerHostConfig {\n readonly privileged?: boolean\n readonly networkMode?: string\n readonly pidMode?: string\n readonly ipcMode?: string\n readonly capAdd?: readonly string[]\n readonly binds?: readonly string[]\n}\n\nexport interface DockerCreateContainerInput {\n readonly image: string\n readonly name?: string\n readonly cmd?: readonly string[]\n readonly env?: readonly string[]\n readonly labels?: Readonly<Record<string, string>>\n readonly hostConfig: DockerHostConfig\n}\n\nexport interface DockerExecInput {\n readonly containerId: string\n readonly argv: readonly string[]\n readonly cwd?: string\n readonly env?: readonly string[]\n readonly timeoutMs?: number\n readonly abort?: AbortSignal\n readonly maxTotalCapturedBytes?: number\n readonly onExecCreated?: (execId: string) => void\n}\n\nexport interface DockerExecResult {\n readonly execId: string\n readonly exitCode: number\n readonly stdout: Buffer\n readonly stderr: Buffer\n readonly truncated: boolean\n readonly captureLimitReached: boolean\n}\n\nexport interface DockerEngineClient {\n inspectImage(ref: string): Promise<DockerImageInspect | undefined>\n buildImage(input: { contextDir: string; dockerfile: string; tag: string }): Promise<DockerImageInspect>\n pullImage(image: string): Promise<DockerImageInspect>\n createContainer(input: DockerCreateContainerInput): Promise<{ containerId: string }>\n inspectContainer(containerId: string): Promise<DockerContainerInspect | undefined>\n startContainer(containerId: string): Promise<void>\n stopContainer(containerId: string): Promise<void>\n removeContainer(containerId: string): Promise<void>\n /** List containers (P0-B GC). `label` filters on `dsh.pentester.*` labels. */\n listContainers(input?: DockerListContainersInput): Promise<readonly DockerContainerListItem[]>\n exec(input: DockerExecInput): Promise<DockerExecResult>\n cancelExec(execId: string): Promise<'confirmed' | 'requested' | 'unknown'>\n}\n\n/**\n * The only extra capabilities a Toolbox container may receive. nmap's file\n * capabilities (`cap_net_bind_service,cap_net_admin,cap_net_raw=eip`) require\n * these to be present in the container's capability set, or exec fails with\n * `Operation not permitted`. This stays a strict whitelist: arbitrary capAdd\n * (e.g. SYS_ADMIN, DAC_OVERRIDE) remains forbidden.\n */\nexport const ALLOWED_CONTAINER_CAP_ADD: readonly string[] = ['NET_ADMIN', 'NET_RAW', 'NET_BIND_SERVICE']\n\nexport function assertSafeHostConfig(config: DockerHostConfig): void {\n if (config.privileged === true) {\n throw new Error('privileged containers are not allowed')\n }\n if (config.networkMode === 'host') {\n throw new Error('host network is not allowed')\n }\n if (config.pidMode === 'host') {\n throw new Error('host pid namespace is not allowed')\n }\n if (config.ipcMode === 'host') {\n throw new Error('host ipc namespace is not allowed')\n }\n for (const cap of config.capAdd ?? []) {\n if (!ALLOWED_CONTAINER_CAP_ADD.includes(cap)) {\n throw new Error(`capability ${cap} is not allowed`)\n }\n }\n for (const bind of config.binds ?? []) {\n if (/docker\\.sock/i.test(bind) || bind.includes(':/') && bind.startsWith('/:') ) {\n throw new Error('forbidden bind')\n }\n if (bind === '/' || bind.startsWith('/:') || /(^|:)\\/:/.test(bind)) {\n throw new Error('forbidden bind')\n }\n }\n}\n\nexport const SAFE_CREATE_HOST_CONFIG: DockerHostConfig = {\n privileged: false,\n networkMode: 'bridge',\n pidMode: '',\n ipcMode: '',\n capAdd: [...ALLOWED_CONTAINER_CAP_ADD],\n}\n\ninterface InjectedContainer {\n id: string\n name: string\n image: string\n running: boolean\n hostConfig: DockerHostConfig\n cmd: readonly string[]\n env: readonly string[]\n labels?: Readonly<Record<string, string>>\n}\n\ninterface InjectedExec {\n id: string\n containerId: string\n argv: readonly string[]\n running: boolean\n hang?: Promise<void>\n}\n\nexport class InjectedDockerEngine implements DockerEngineClient {\n inspectImageCalls = 0\n buildCalls = 0\n pullCalls = 0\n createCalls = 0\n startCalls = 0\n execCalls = 0\n cancelExecCalls = 0\n firewallObjectsCreated = 0\n lastCreate?: DockerCreateContainerInput\n lastExecArgv?: readonly string[]\n lastExecCwd?: string\n lastExecEnv?: readonly string[]\n hangNext?: Promise<void>\n nextStdout?: Buffer\n nextStderr?: Buffer\n binaryNext = false\n oversizedNext = 0\n lastCancelledExecId?: string\n maxBufferedBytes = 0\n readonly images = new Map<string, DockerImageInspect>()\n readonly containers = new Map<string, InjectedContainer>()\n readonly execs = new Map<string, InjectedExec>()\n private readonly cancelled = new Set<string>()\n private seq = 0\n\n addImage(ref: string, inspect: DockerImageInspect = { id: `sha256:${ref.replace(/[^a-z0-9]/gi, '')}` }): void {\n this.images.set(ref, inspect)\n }\n\n async inspectImage(ref: string): Promise<DockerImageInspect | undefined> {\n this.inspectImageCalls += 1\n return this.images.get(ref)\n }\n\n async buildImage(input: { contextDir: string; dockerfile: string; tag: string }): Promise<DockerImageInspect> {\n this.buildCalls += 1\n const inspect = { id: `sha256:built-${input.tag.replace(/[^a-z0-9]/gi, '')}`, digest: 'sha256:built' }\n this.images.set(input.tag, inspect)\n return inspect\n }\n\n async pullImage(image: string): Promise<DockerImageInspect> {\n this.pullCalls += 1\n const inspect = { id: `sha256:pulled-${image.replace(/[^a-z0-9]/gi, '')}`, digest: 'sha256:pulled' }\n this.images.set(image, inspect)\n return inspect\n }\n\n async createContainer(input: DockerCreateContainerInput): Promise<{ containerId: string }> {\n assertSafeHostConfig(input.hostConfig)\n if (!this.images.has(input.image)) {\n throw new Error(`image not found: ${input.image}`)\n }\n this.createCalls += 1\n this.lastCreate = input\n this.seq += 1\n const containerId = `ctr-${this.seq}`\n this.containers.set(containerId, {\n id: containerId,\n name: input.name ?? `ctr-${this.seq}`,\n image: input.image,\n running: false,\n hostConfig: input.hostConfig,\n cmd: input.cmd ?? ['sleep', 'infinity'],\n env: input.env ?? [],\n ...(input.labels === undefined ? {} : { labels: input.labels }),\n })\n return { containerId }\n }\n\n async inspectContainer(containerId: string): Promise<DockerContainerInspect | undefined> {\n const container = this.containers.get(containerId)\n if (container === undefined) {\n return undefined\n }\n return { id: container.id, running: container.running, image: container.image }\n }\n\n async startContainer(containerId: string): Promise<void> {\n const container = this.containers.get(containerId)\n if (container === undefined) {\n throw new Error(`container missing: ${containerId}`)\n }\n this.startCalls += 1\n container.running = true\n }\n\n async stopContainer(containerId: string): Promise<void> {\n const container = this.containers.get(containerId)\n if (container !== undefined) {\n container.running = false\n }\n }\n\n async removeContainer(containerId: string): Promise<void> {\n this.containers.delete(containerId)\n }\n\n async listContainers(input?: DockerListContainersInput): Promise<readonly DockerContainerListItem[]> {\n const items: DockerContainerListItem[] = []\n for (const container of this.containers.values()) {\n if (input?.label !== undefined) {\n const [key, value] = input.label.split('=')\n if (container.labels?.[key] !== value) {\n continue\n }\n }\n items.push({\n id: container.id,\n name: container.name,\n image: container.image,\n running: container.running,\n labels: container.labels,\n })\n }\n return items\n }\n\n async exec(input: DockerExecInput): Promise<DockerExecResult> {\n const container = this.containers.get(input.containerId)\n if (container === undefined || !container.running) {\n throw new Error(`container not running: ${input.containerId}`)\n }\n this.execCalls += 1\n this.lastExecArgv = input.argv\n this.lastExecCwd = input.cwd\n this.lastExecEnv = input.env\n this.seq += 1\n const execId = `exec-${this.seq}`\n input.onExecCreated?.(execId)\n const record: InjectedExec = {\n id: execId,\n containerId: input.containerId,\n argv: input.argv,\n running: true,\n hang: this.hangNext,\n }\n this.execs.set(execId, record)\n this.hangNext = undefined\n if (record.hang !== undefined) {\n const hang = record.hang\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n record.running = false\n this.cancelled.add(execId)\n resolve()\n }\n input.abort?.addEventListener('abort', onAbort)\n void hang.then(() => {\n input.abort?.removeEventListener('abort', onAbort)\n resolve()\n }, reject)\n })\n }\n record.running = false\n if (this.cancelled.has(execId) || input.abort?.aborted) {\n return {\n execId,\n exitCode: 137,\n stdout: Buffer.alloc(0),\n stderr: Buffer.from('cancelled'),\n truncated: false,\n captureLimitReached: false,\n }\n }\n if (this.oversizedNext > 0) {\n const produced = this.oversizedNext\n this.oversizedNext = 0\n const limit = input.maxTotalCapturedBytes ?? DEFAULT_TOOL_OUTPUT_LIMITS.maxTotalCapturedBytes\n const capture = new BoundedCapture(limit)\n const chunk = Buffer.alloc(4096, 0x61)\n let remaining = produced\n while (remaining > 0 && !capture.full) {\n const n = Math.min(chunk.length, remaining)\n capture.accept('stdout', chunk.subarray(0, n))\n remaining -= n\n }\n this.maxBufferedBytes = Math.max(this.maxBufferedBytes, capture.buffered)\n return {\n execId,\n exitCode: 0,\n stdout: capture.stdout(),\n stderr: capture.stderr(),\n truncated: capture.truncated,\n captureLimitReached: capture.captureLimitReached,\n }\n }\n if (this.binaryNext) {\n this.binaryNext = false\n return {\n execId,\n exitCode: 0,\n stdout: Buffer.from([0x00, 0xff, 0xfe, 0x01]),\n stderr: Buffer.alloc(0),\n truncated: false,\n captureLimitReached: false,\n }\n }\n const stdout =\n this.nextStdout ?? Buffer.from(input.argv.includes('echo') ? input.argv.slice(1).join(' ') : input.argv.join(' '))\n this.nextStdout = undefined\n const stderr = this.nextStderr ?? Buffer.alloc(0)\n this.nextStderr = undefined\n return { execId, exitCode: 0, stdout, stderr, truncated: false, captureLimitReached: false }\n }\n\n async cancelExec(execId: string): Promise<'confirmed' | 'requested' | 'unknown'> {\n this.cancelExecCalls += 1\n this.lastCancelledExecId = execId\n this.cancelled.add(execId)\n const current = this.execs.get(execId)\n if (current !== undefined) {\n current.running = false\n return 'requested'\n }\n return 'unknown'\n }\n}\n\nexport function dockerSocketPath(): string {\n return process.env.DOCKER_HOST?.startsWith('unix://')\n ? process.env.DOCKER_HOST.slice('unix://'.length)\n : '/var/run/docker.sock'\n}\n\nexport class UnixDockerEngine implements DockerEngineClient {\n readonly kind = 'unix' as const\n firewallObjectsCreated = 0\n killExecStarts = 0\n lastKillPid?: number\n private readonly socketPath: string\n private readonly api: string\n private readonly inflight = new Map<string, { abort: AbortController; containerId: string }>()\n\n constructor(socketPath = dockerSocketPath(), api = 'v1.41') {\n this.socketPath = socketPath\n this.api = api\n }\n\n async inspectImage(ref: string): Promise<DockerImageInspect | undefined> {\n const encoded = encodeURIComponent(ref)\n const res = await this.json('GET', `/images/${encoded}/json`)\n if (res.status === 404) {\n return undefined\n }\n if (res.status >= 400) {\n throw new Error(`inspect image failed: ${res.status}`)\n }\n const body = res.body as { Id?: string; RepoDigests?: string[] }\n return { id: body.Id ?? ref, digest: body.RepoDigests?.[0] }\n }\n\n async buildImage(input: { contextDir: string; dockerfile: string; tag: string }): Promise<DockerImageInspect> {\n const { packDockerContext } = await import('./docker-tar.js')\n const tar = await packDockerContext(input.contextDir, input.dockerfile)\n const res = await this.raw(\n 'POST',\n `/build?t=${encodeURIComponent(input.tag)}&dockerfile=${encodeURIComponent(input.dockerfile)}`,\n tar,\n 'application/x-tar',\n )\n if (res.status >= 400) {\n throw new Error(`build failed: ${res.status} ${res.body.toString('utf8').slice(0, 200)}`)\n }\n const inspect = await this.inspectImage(input.tag)\n if (inspect === undefined) {\n throw new Error('build completed but image missing')\n }\n return inspect\n }\n\n async pullImage(image: string): Promise<DockerImageInspect> {\n const [name, tag] = image.includes(':') ? image.split(/:(?=[^:]+$)/) : [image, 'latest']\n const res = await this.raw(\n 'POST',\n `/images/create?fromImage=${encodeURIComponent(name ?? image)}&tag=${encodeURIComponent(tag ?? 'latest')}`,\n )\n if (res.status >= 400) {\n throw new Error(`pull failed: ${res.status}`)\n }\n const inspect = await this.inspectImage(image)\n if (inspect === undefined) {\n throw new Error('pull completed but image missing')\n }\n return inspect\n }\n\n async createContainer(input: DockerCreateContainerInput): Promise<{ containerId: string }> {\n assertSafeHostConfig(input.hostConfig)\n const res = await this.json('POST', `/containers/create${input.name !== undefined ? `?name=${encodeURIComponent(input.name)}` : ''}`, {\n Image: input.image,\n Cmd: input.cmd ?? ['sleep', 'infinity'],\n Env: input.env ?? [],\n Labels: input.labels ?? {},\n HostConfig: {\n Privileged: false,\n NetworkMode: input.hostConfig.networkMode ?? 'bridge',\n PidMode: input.hostConfig.pidMode ?? '',\n IpcMode: input.hostConfig.ipcMode ?? '',\n CapAdd: [...(input.hostConfig.capAdd ?? [])],\n Binds: input.hostConfig.binds ?? [],\n SecurityOpt: ['no-new-privileges'],\n },\n })\n if (res.status >= 400) {\n throw new Error(`create container failed: ${res.status} ${JSON.stringify(res.body)}`)\n }\n return { containerId: (res.body as { Id: string }).Id }\n }\n\n async inspectContainer(containerId: string): Promise<DockerContainerInspect | undefined> {\n const res = await this.json('GET', `/containers/${encodeURIComponent(containerId)}/json`)\n if (res.status === 404) {\n return undefined\n }\n if (res.status >= 400) {\n throw new Error(`inspect container failed: ${res.status}`)\n }\n const body = res.body as { Id: string; State?: { Running?: boolean }; Config?: { Image?: string }; Image?: string }\n return {\n id: body.Id,\n running: body.State?.Running === true,\n image: body.Config?.Image ?? body.Image ?? '',\n }\n }\n\n async startContainer(containerId: string): Promise<void> {\n const res = await this.raw('POST', `/containers/${encodeURIComponent(containerId)}/start`)\n if (res.status >= 400 && res.status !== 304) {\n throw new Error(`start container failed: ${res.status}`)\n }\n }\n\n async stopContainer(containerId: string): Promise<void> {\n const res = await this.raw('POST', `/containers/${encodeURIComponent(containerId)}/stop`)\n if (res.status >= 400 && res.status !== 304 && res.status !== 404) {\n throw new Error(`stop container failed: ${res.status}`)\n }\n }\n\n async removeContainer(containerId: string): Promise<void> {\n const res = await this.raw('DELETE', `/containers/${encodeURIComponent(containerId)}?force=true`)\n if (res.status >= 400 && res.status !== 404) {\n throw new Error(`remove container failed: ${res.status}`)\n }\n }\n\n async listContainers(input?: DockerListContainersInput): Promise<readonly DockerContainerListItem[]> {\n const query = new URLSearchParams()\n if (input?.all === true) {\n query.set('all', '1')\n }\n if (input?.label !== undefined) {\n query.set('filters', JSON.stringify({ label: [input.label] }))\n }\n const suffix = query.toString().length > 0 ? `?${query.toString()}` : ''\n const res = await this.json('GET', `/containers/json${suffix}`)\n if (res.status >= 400) {\n throw new Error(`list containers failed: ${res.status}`)\n }\n const rows = res.body as Array<{\n Id?: string\n Names?: string[]\n Image?: string\n State?: string\n Labels?: Record<string, string>\n }>\n return rows.map((row) => ({\n id: row.Id ?? '',\n name: row.Names?.[0]?.replace(/^\\//, '') ?? '',\n image: row.Image ?? '',\n running: row.State === 'running',\n ...(row.Labels === undefined ? {} : { labels: row.Labels }),\n })).filter((item) => item.id.length > 0)\n }\n\n async exec(input: DockerExecInput): Promise<DockerExecResult> {\n const created = await this.json('POST', `/containers/${encodeURIComponent(input.containerId)}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n Cmd: input.argv,\n WorkingDir: input.cwd,\n Env: input.env,\n })\n if (created.status >= 400) {\n throw new Error(`exec create failed: ${created.status}`)\n }\n const execId = (created.body as { Id: string }).Id\n input.onExecCreated?.(execId)\n const abort = new AbortController()\n this.inflight.set(execId, { abort, containerId: input.containerId })\n if (input.abort !== undefined) {\n input.abort.addEventListener('abort', () => abort.abort())\n }\n const limit = input.maxTotalCapturedBytes ?? DEFAULT_TOOL_OUTPUT_LIMITS.maxTotalCapturedBytes\n try {\n const started = await this.streamExecStart(execId, abort.signal, limit)\n if (started.status >= 400) {\n throw new Error(`exec start failed: ${started.status}`)\n }\n const inspected = await this.json('GET', `/exec/${encodeURIComponent(execId)}/json`)\n const exitCode = (inspected.body as { ExitCode?: number }).ExitCode ?? 0\n return {\n execId,\n exitCode,\n stdout: started.stdout,\n stderr: started.stderr,\n truncated: started.truncated,\n captureLimitReached: started.captureLimitReached,\n }\n } finally {\n this.inflight.delete(execId)\n }\n }\n\n async cancelExec(execId: string): Promise<'confirmed' | 'requested' | 'unknown'> {\n const current = this.inflight.get(execId)\n const inspected = await this.json('GET', `/exec/${encodeURIComponent(execId)}/json`)\n const pid = (inspected.body as { Pid?: number }).Pid\n if (typeof pid === 'number' && pid > 0 && current !== undefined) {\n this.lastKillPid = pid\n const kill = await this.json('POST', `/containers/${encodeURIComponent(current.containerId)}/exec`, {\n AttachStdout: false,\n AttachStderr: false,\n Cmd: ['kill', '-TERM', String(pid)],\n })\n if (kill.status < 400) {\n const killId = (kill.body as { Id?: string }).Id\n if (typeof killId === 'string' && killId.length > 0) {\n await this.raw(\n 'POST',\n `/exec/${encodeURIComponent(killId)}/start`,\n Buffer.from(JSON.stringify({ Detach: true, Tty: false })),\n 'application/json',\n )\n this.killExecStarts += 1\n }\n }\n current.abort.abort()\n return 'requested'\n }\n current?.abort.abort()\n return current !== undefined ? 'requested' : 'unknown'\n }\n\n private streamExecStart(\n execId: string,\n signal: AbortSignal,\n maxTotalCapturedBytes: number,\n ): Promise<{\n status: number\n stdout: Buffer\n stderr: Buffer\n truncated: boolean\n captureLimitReached: boolean\n }> {\n return new Promise((resolve, reject) => {\n const capture = new DockerMuxCapture(maxTotalCapturedBytes)\n const body = Buffer.from(JSON.stringify({ Detach: false, Tty: false }))\n const req = httpRequest({\n socketPath: this.socketPath,\n path: `/${this.api}/exec/${encodeURIComponent(execId)}/start`,\n method: 'POST',\n headers: {\n Host: 'localhost',\n 'Content-Type': 'application/json',\n 'Content-Length': body.length,\n },\n }, (res) => {\n res.on('data', (chunk) => {\n capture.pushMuxed(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))\n })\n res.on('end', () => {\n resolve({\n status: res.statusCode ?? 0,\n stdout: capture.inner.stdout(),\n stderr: capture.inner.stderr(),\n truncated: capture.inner.truncated,\n captureLimitReached: capture.inner.captureLimitReached,\n })\n })\n })\n req.on('error', reject)\n signal.addEventListener('abort', () => {\n req.destroy(new Error('aborted'))\n })\n req.write(body)\n req.end()\n })\n }\n\n private json(method: string, path: string, body?: unknown): Promise<{ status: number; body: unknown }> {\n const payload = body === undefined ? undefined : Buffer.from(JSON.stringify(body))\n return this.raw(method, path, payload, 'application/json').then((res) => ({\n status: res.status,\n body: res.body.length === 0 ? {} : JSON.parse(res.body.toString('utf8')) as unknown,\n }))\n }\n\n private raw(\n method: string,\n path: string,\n body?: Buffer,\n contentType?: string,\n signal?: AbortSignal,\n ): Promise<{ status: number; body: Buffer }> {\n return new Promise((resolve, reject) => {\n const req = httpRequest({\n socketPath: this.socketPath,\n path: `/${this.api}${path}`,\n method,\n headers: {\n Host: 'localhost',\n ...(contentType !== undefined ? { 'Content-Type': contentType } : {}),\n ...(body !== undefined ? { 'Content-Length': body.length } : {}),\n },\n }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (chunk) => {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))\n })\n res.on('end', () => {\n resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks) })\n })\n })\n req.on('error', reject)\n signal?.addEventListener('abort', () => {\n req.destroy(new Error('aborted'))\n })\n if (body !== undefined) {\n req.write(body)\n }\n req.end()\n })\n }\n}\n\nexport function toolCallAbortKey(toolCallId: ToolCallId): string {\n return toolCallId\n}\n","import type { EngagementId } from '../domain/ids.js'\nimport type { ToolboxId } from '../domain/ids.js'\nimport type { DockerEngineClient, DockerImageInspect } from './docker-engine.js'\nimport { SAFE_CREATE_HOST_CONFIG } from './docker-engine.js'\nimport { readJsonFile, writeJsonAtomic } from './persist.js'\n\n/**\n * P0-B: ONE persistent execution container per Engagement × runtime-profile.\n *\n * Container ownership changed: `agentRunId`/`taskId` NEVER participate in the\n * container identity. The key is `engagementId + runtimeProfile` (the factory\n * Kali image is the single runtime profile in the MVP), so every Worker /\n * Task / Stage of an Engagement reuses the SAME container. Provenance\n * (taskId / agentRunId / toolCallId) stays in the audit log only.\n *\n * Lifecycle:\n * - lazy create on first execution (ensure)\n * - Task completed / AgentRun completed / Session ended → KEEP\n * - Engagement CLOSED → stop + remove\n * - explicit reset → stop + remove + recreate\n * - plugin restart → recover/reuse (DockerBindingStore read + engine inspect)\n * - orphan/stale managed containers (no live Engagement) → GC removes\n *\n * Concurrency: `ensure` is singleflighted per key — N concurrent first-time\n * callers create exactly ONE container and all receive the same id.\n */\n\nexport const PENTESTER_MANAGED_LABEL = 'dsh.pentester.managed'\nexport const PENTESTER_ENGAGEMENT_LABEL = 'dsh.pentester.engagement'\nexport const PENTESTER_RUNTIME_PROFILE_LABEL = 'dsh.pentester.runtime-profile'\nexport const PENTESTER_WORKSPACE_LABEL = 'dsh.pentester.workspace'\nexport const PENTESTER_MANAGED_TRUE = 'true'\n\n/** MVP runtime profile: the single factory Kali image. */\nexport const RUNTIME_PROFILE_KALI_DEFAULT = 'kali-default'\n\nexport interface EngagementRuntimeProfile {\n readonly engagementId: EngagementId\n /** Runtime profile id (MVP: kali-default). */\n readonly profile: string\n /** Image ref backing this profile (factory Kali image). */\n readonly image: string\n /** Trusted host workspace path mounted to /workspace (never model-supplied). */\n readonly workspacePath?: string\n /** Engagement workspace path mounted to /artifacts. */\n readonly artifactsPath?: string\n /** Stable workspace identity label value (hashed, never the raw path). */\n readonly workspaceLabel?: string\n /** P1-4: human-readable slug for the container name (never a UUID). */\n readonly nameSlug?: string\n}\n\nexport interface EngagementContainerBinding {\n readonly engagementId: EngagementId\n readonly profile: string\n readonly image: string\n readonly containerId: string\n readonly imageId?: string\n readonly imageSource?: 'existing' | 'built' | 'pulled'\n readonly createdAt: string\n}\n\nexport interface EnsureEngagementContainerInput {\n readonly profile: EngagementRuntimeProfile\n readonly engine: DockerEngineClient\n readonly resolvedImage: DockerImageInspect\n /** Image resolve source (existing/built/pulled) for audit. */\n readonly imageSource: 'existing' | 'built' | 'pulled'\n}\n\nexport class EngagementContainerStore {\n private readonly path: string\n private bindings: Record<string, EngagementContainerBinding>\n /** singleflight: key → in-flight ensure promise. */\n private readonly inflight = new Map<string, Promise<string>>()\n\n private constructor(path: string, bindings: Record<string, EngagementContainerBinding>) {\n this.path = path\n this.bindings = bindings\n }\n\n static keyOf(engagementId: EngagementId, profile: string): string {\n return `${engagementId}::${profile}`\n }\n\n static async open(engagementDir: string): Promise<EngagementContainerStore> {\n const path = `${engagementDir}/runtime/engagement-containers.json`\n const raw = await readJsonFile(path)\n const bindings: Record<string, EngagementContainerBinding> =\n raw !== null && typeof raw === 'object' && !Array.isArray(raw) && 'bindings' in raw\n ? ((raw as { bindings: Record<string, EngagementContainerBinding> }).bindings ?? {})\n : {}\n return new EngagementContainerStore(path, bindings)\n }\n\n list(): readonly EngagementContainerBinding[] {\n return Object.values(this.bindings)\n }\n\n get(key: string): EngagementContainerBinding | undefined {\n return this.bindings[key]\n }\n\n /** Remove a binding record (after the container itself is removed). */\n async remove(key: string): Promise<void> {\n if (this.bindings[key] === undefined) {\n return\n }\n const next = { ...this.bindings }\n delete next[key]\n this.bindings = next\n await writeJsonAtomic(this.path, { bindings: this.bindings })\n }\n\n async put(binding: EngagementContainerBinding): Promise<void> {\n const key = EngagementContainerStore.keyOf(binding.engagementId, binding.profile)\n this.bindings = { ...this.bindings, [key]: binding }\n await writeJsonAtomic(this.path, { bindings: this.bindings })\n }\n\n /**\n * Ensure exactly ONE running container exists for the profile. Concurrency\n * safe: concurrent first-time callers singleflight to a single create.\n * Returns the container id.\n */\n ensure(input: EnsureEngagementContainerInput): Promise<string> {\n const key = EngagementContainerStore.keyOf(input.profile.engagementId, input.profile.profile)\n const inflight = this.inflight.get(key)\n if (inflight !== undefined) {\n return inflight\n }\n const run = this.ensureInner(key, input)\n this.inflight.set(key, run)\n void run.finally(() => {\n this.inflight.delete(key)\n })\n return run\n }\n\n private async ensureInner(\n key: string,\n input: EnsureEngagementContainerInput,\n ): Promise<string> {\n const existing = this.bindings[key]\n if (existing !== undefined) {\n const inspect = await input.engine.inspectContainer(existing.containerId)\n if (inspect === undefined) {\n // Container was removed out-of-band → recreate.\n return this.createAndRecord(key, input)\n }\n if (!inspect.running) {\n await input.engine.startContainer(existing.containerId)\n }\n return existing.containerId\n }\n return this.createAndRecord(key, input)\n }\n\n private async createAndRecord(\n key: string,\n input: EnsureEngagementContainerInput,\n ): Promise<string> {\n const profile = input.profile\n const binds: string[] = []\n if (profile.workspacePath !== undefined && profile.workspacePath.length > 0) {\n const { mkdir } = await import('node:fs/promises')\n await mkdir(profile.workspacePath, { recursive: true })\n binds.push(`${profile.workspacePath}:/workspace`)\n if (profile.artifactsPath !== undefined && profile.artifactsPath.length > 0) {\n await mkdir(profile.artifactsPath, { recursive: true })\n binds.push(`${profile.artifactsPath}:/artifacts`)\n }\n } else if (profile.artifactsPath !== undefined && profile.artifactsPath.length > 0) {\n // Legacy fallback: no trusted workspace → engagement workspace is /workspace.\n const { mkdir } = await import('node:fs/promises')\n await mkdir(profile.artifactsPath, { recursive: true })\n binds.push(`${profile.artifactsPath}:/workspace`)\n }\n const created = await input.engine.createContainer({\n image: profile.image,\n name: engagementContainerName(profile.engagementId, profile.profile, profile.nameSlug),\n cmd: ['sleep', 'infinity'],\n env: [],\n labels: {\n [PENTESTER_MANAGED_LABEL]: PENTESTER_MANAGED_TRUE,\n [PENTESTER_ENGAGEMENT_LABEL]: profile.engagementId,\n [PENTESTER_RUNTIME_PROFILE_LABEL]: profile.profile,\n ...(profile.workspaceLabel === undefined ? {} : { [PENTESTER_WORKSPACE_LABEL]: profile.workspaceLabel }),\n },\n hostConfig: {\n ...SAFE_CREATE_HOST_CONFIG,\n binds,\n },\n })\n await input.engine.startContainer(created.containerId)\n const binding: EngagementContainerBinding = {\n engagementId: profile.engagementId,\n profile: profile.profile,\n image: profile.image,\n containerId: created.containerId,\n imageId: input.resolvedImage.id,\n ...(input.resolvedImage.digest === undefined ? {} : { imageSource: input.imageSource }),\n createdAt: new Date().toISOString(),\n }\n // In the (rare) race where a concurrent ensure already recorded a binding\n // whose container still EXISTS after our create, prefer the recorded one\n // and remove our duplicate. A stale recorded binding (container removed\n // out-of-band) is NOT preferred — we keep the freshly created one.\n const raced = this.bindings[key]\n if (raced !== undefined && raced.containerId !== created.containerId) {\n const racedInspect = await input.engine.inspectContainer(raced.containerId)\n if (racedInspect !== undefined) {\n try {\n await input.engine.removeContainer(created.containerId)\n } catch {\n // best-effort\n }\n return raced.containerId\n }\n }\n await this.put(binding)\n return created.containerId\n }\n\n /** Remove + recreate the container for a profile (explicit reset). */\n async reset(\n key: string,\n input: EnsureEngagementContainerInput,\n ): Promise<string> {\n const existing = this.bindings[key]\n if (existing !== undefined) {\n try {\n await input.engine.stopContainer(existing.containerId)\n } catch {\n // best-effort\n }\n try {\n await input.engine.removeContainer(existing.containerId)\n } catch {\n // best-effort\n }\n await this.remove(key)\n }\n return this.ensure(input)\n }\n\n /**\n * Engagement CLOSED: stop + remove the managed container and forget the\n * binding. Returns the removed container id, if any.\n */\n async closeEngagement(engagementId: EngagementId, engine: DockerEngineClient): Promise<string | undefined> {\n const keys = Object.keys(this.bindings).filter((key) => key.startsWith(`${engagementId}::`))\n let removed: string | undefined\n for (const key of keys) {\n const binding = this.bindings[key]\n if (binding === undefined) continue\n try {\n await engine.stopContainer(binding.containerId)\n } catch {\n // best-effort\n }\n try {\n await engine.removeContainer(binding.containerId)\n } catch {\n // best-effort\n }\n removed = binding.containerId\n await this.remove(key)\n }\n return removed\n }\n}\n\n/** Deterministic container name: readable slug + profile (never agentRun/taskId,\n * never a UUID). E.g. `pentest-192-168-107-5-kali`. Falls back to a short\n * engagement hash only when no readable slug is available. */\nexport function engagementContainerName(\n engagementId: EngagementId,\n profile: string,\n slug?: string,\n): string {\n const readable = slug !== undefined && slug.length > 0 ? safeName(slug) : `eng-${safeName(engagementId).slice(0, 12)}`\n const profileTail = profile === 'kali-default' ? 'kali' : safeName(profile)\n return `pentest-${readable}-${profileTail}`\n}\n\nfunction safeName(value: string): string {\n return value.replace(/[^a-zA-Z0-9_.-]/g, '').slice(0, 40) || 'x'\n}\n\nexport type { ToolboxId }\n"],"mappings":"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"}
|