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 <[email protected]>
This commit is contained in:
Jason Woltje
2026-02-06 15:22:12 -06:00
co-authored by Claude Opus 4.6
parent 3880993b60
commit 6934d9261c
2 changed files with 41 additions and 2 deletions
@@ -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<typeof vi.fn>).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<string>();
// 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<typeof vi.fn>).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 () => { it("should throw error if container creation fails", async () => {
const agentId = "agent-123"; const agentId = "agent-123";
const taskId = "task-456"; const taskId = "task-456";
@@ -1,5 +1,6 @@
import { Injectable, Logger } from "@nestjs/common"; import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { randomBytes } from "crypto";
import Docker from "dockerode"; import Docker from "dockerode";
import { import {
DockerSandboxOptions, DockerSandboxOptions,
@@ -248,8 +249,10 @@ export class DockerSandboxService {
} }
} }
// Container name with timestamp to ensure uniqueness // Container name with timestamp and random suffix to guarantee uniqueness
const containerName = `mosaic-agent-${agentId}-${Date.now().toString()}`; // 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( this.logger.log(
`Creating container for agent ${agentId} (image: ${image}, memory: ${memoryMB.toString()}MB, cpu: ${cpuLimit.toString()})` `Creating container for agent ${agentId} (image: ${image}, memory: ${memoryMB.toString()}MB, cpu: ${cpuLimit.toString()})`