From 6934d9261c06f146b435acb89e9997a772cbb6c0 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 6 Feb 2026 15:22:12 -0600 Subject: [PATCH] fix(SEC-ORCH-30): Add unique suffix to container names Add crypto.randomBytes(4) hex suffix to container name generation to prevent name collisions when multiple agents spawn simultaneously within the same millisecond. Container names now include both a timestamp and 8 random hex characters for guaranteed uniqueness. Co-Authored-By: Claude Opus 4.6 --- .../spawner/docker-sandbox.service.spec.ts | 36 +++++++++++++++++++ .../src/spawner/docker-sandbox.service.ts | 7 ++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts b/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts index bd68184..8e1593e 100644 --- a/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts +++ b/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts @@ -162,6 +162,42 @@ describe("DockerSandboxService", () => { ); }); + it("should include a random suffix in container name for uniqueness", async () => { + const agentId = "agent-123"; + const taskId = "task-456"; + const workspacePath = "/workspace/agent-123"; + + await service.createContainer(agentId, taskId, workspacePath); + + const callArgs = (mockDocker.createContainer as ReturnType).mock + .calls[0][0] as Docker.ContainerCreateOptions; + const containerName = callArgs.name as string; + + // Name format: mosaic-agent-{agentId}-{timestamp}-{8 hex chars} + expect(containerName).toMatch(/^mosaic-agent-agent-123-\d+-[0-9a-f]{8}$/); + }); + + it("should generate unique container names across rapid successive calls", async () => { + const agentId = "agent-123"; + const taskId = "task-456"; + const workspacePath = "/workspace/agent-123"; + const containerNames = new Set(); + + // Spawn multiple containers rapidly to test for collisions + for (let i = 0; i < 20; i++) { + await service.createContainer(agentId, taskId, workspacePath); + } + + const calls = (mockDocker.createContainer as ReturnType).mock.calls; + for (const call of calls) { + const args = call[0] as Docker.ContainerCreateOptions; + containerNames.add(args.name as string); + } + + // All 20 names must be unique (no collisions) + expect(containerNames.size).toBe(20); + }); + it("should throw error if container creation fails", async () => { const agentId = "agent-123"; const taskId = "task-456"; diff --git a/apps/orchestrator/src/spawner/docker-sandbox.service.ts b/apps/orchestrator/src/spawner/docker-sandbox.service.ts index d90cbde..37c1922 100644 --- a/apps/orchestrator/src/spawner/docker-sandbox.service.ts +++ b/apps/orchestrator/src/spawner/docker-sandbox.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; +import { randomBytes } from "crypto"; import Docker from "dockerode"; import { DockerSandboxOptions, @@ -248,8 +249,10 @@ export class DockerSandboxService { } } - // Container name with timestamp to ensure uniqueness - const containerName = `mosaic-agent-${agentId}-${Date.now().toString()}`; + // Container name with timestamp and random suffix to guarantee uniqueness + // even when multiple agents are spawned simultaneously within the same millisecond + const uniqueSuffix = randomBytes(4).toString("hex"); + const containerName = `mosaic-agent-${agentId}-${Date.now().toString()}-${uniqueSuffix}`; this.logger.log( `Creating container for agent ${agentId} (image: ${image}, memory: ${memoryMB.toString()}MB, cpu: ${cpuLimit.toString()})`