fix(agents): preserve sandbox scope in container names (#115571)

This commit is contained in:
Vincent Koc
2026-07-29 12:59:38 +08:00
committed by GitHub
parent 75ee9b81ba
commit 233d16b76f
4 changed files with 48 additions and 6 deletions
+2 -3
View File
@@ -53,7 +53,7 @@ import {
issueNoVncObserverToken,
} from "./novnc-auth.js";
import { readBrowserRegistry, updateBrowserRegistry } from "./registry.js";
import { resolveSandboxAgentId, slugifySessionKey } from "./shared.js";
import { buildSandboxContainerName, resolveSandboxAgentId, slugifySessionKey } from "./shared.js";
import { isToolAllowed } from "./tool-policy.js";
import type { SandboxBrowserContext, SandboxConfig } from "./types.js";
import { validateNetworkMode } from "./validate-sandbox-security.js";
@@ -242,8 +242,7 @@ export async function ensureSandboxBrowser(params: {
}
const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(params.scopeKey);
const name = `${params.cfg.browser.containerPrefix}${slug}`;
const containerName = name.slice(0, 63);
const containerName = buildSandboxContainerName(params.cfg.browser.containerPrefix, slug);
let existing = BROWSER_BRIDGES.get(params.scopeKey);
const stopExistingForContainer = async () => {
await stopCachedBrowserBridgesForContainer(containerName);
+2 -3
View File
@@ -95,7 +95,7 @@ import {
} from "./constants.js";
import { handleHotSandboxConfigMismatch } from "./current-config.js";
import { readRegistryEntry, updateRegistry } from "./registry.js";
import { resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
import { buildSandboxContainerName, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
import type { SandboxConfig, SandboxDockerConfig, SandboxWorkspaceAccess } from "./types.js";
import { validateSandboxSecurity } from "./validate-sandbox-security.js";
import {
@@ -503,8 +503,7 @@ export async function ensureSandboxContainer(params: {
}) {
const scopeKey = resolveSandboxScopeKey(params.cfg.scope, params.sessionKey);
const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(scopeKey);
const name = `${params.cfg.docker.containerPrefix}${slug}`;
const containerName = name.slice(0, 63);
const containerName = buildSandboxContainerName(params.cfg.docker.containerPrefix, slug);
const readOnlyWorkspaceSkillMounts = resolveReadOnlyWorkspaceSkillMounts({
workspaceDir: params.workspaceDir,
agentWorkspaceDir: params.agentWorkspaceDir,
+33
View File
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { buildSandboxContainerName, slugifySessionKey } from "./shared.js";
describe("buildSandboxContainerName", () => {
it("preserves scope identity when a custom prefix exceeds the Docker name limit", () => {
const commonPrefix = "custom-prefix-".repeat(6);
const first = buildSandboxContainerName(
`${commonPrefix}first`,
slugifySessionKey("session:first"),
);
const second = buildSandboxContainerName(
`${commonPrefix}second`,
slugifySessionKey("session:second"),
);
const sameScopeDifferentPrefix = buildSandboxContainerName(
`${commonPrefix}second`,
slugifySessionKey("session:first"),
);
const oversizedSlug = buildSandboxContainerName(
commonPrefix,
slugifySessionKey("session:".concat("x".repeat(200))),
);
expect(first).not.toBe(second);
expect(first).not.toBe(sameScopeDifferentPrefix);
expect(first).toHaveLength(63);
expect(second).toHaveLength(63);
expect(oversizedSlug).toHaveLength(63);
expect(first).toMatch(/-[0-9a-f]{12}$/);
expect(second).toMatch(/-[0-9a-f]{12}$/);
expect(oversizedSlug).toMatch(/-[0-9a-f]{12}$/);
});
});
+11
View File
@@ -25,6 +25,17 @@ export function slugifySessionKey(value: string) {
return `${base}-${hash}`;
}
/** Builds a bounded Docker name without truncating the scope-identity slug. */
export function buildSandboxContainerName(prefix: string, slug: string): string {
const maxLength = 63;
const fullName = `${prefix}${slug}`;
if (fullName.length <= maxLength) {
return fullName;
}
const identitySuffix = `-${hashTextSha256(fullName).slice(0, 12)}`;
return `${fullName.slice(0, maxLength - identitySuffix.length)}${identitySuffix}`;
}
/** Resolves the per-session sandbox workspace directory under the configured sandbox root. */
function resolveSandboxWorkspaceDir(root: string, sessionKey: string) {
const resolvedRoot = resolveUserPath(root);