paperclipai/paperclip · error · Error
sandbox runtime asset key is not a simple path segment: ${ke
Error message
sandbox runtime asset key is not a simple path segment: ${key} What it means
Thrown by assertRuntimeAssetKeyIsSafe (called from the sandbox managed runtime before building any paths, sandbox-managed-runtime.ts:774) when a runtime asset key is empty or contains "/", "\\", or "..". The key becomes a remote directory, an archive name, and a host temp file, so a separator or traversal sequence could escape those roots; the runtime fails closed instead of building the path.
Source
Thrown at packages/adapter-utils/src/sandbox-managed-runtime.ts:433
}
// The workspace stages under `<runtimeRootDir>/workspace-upload.tar` and, for a
// git-backed workspace, under `<runtimeRootDir>/git-workspace-upload.tar`. Each
// asset stages under `<runtimeRootDir>/<key>-upload.tar`, so an asset key equal to
// one of these stems resolves to the same remote archive path. Reserve the stems.
const RESERVED_RUNTIME_ASSET_KEYS = new Set(["workspace", "git-workspace"]);
// Reject an asset key before any path is built from it. An asset key becomes a
// remote directory (`<runtimeRootDir>/<key>`), a remote archive name
// (`<key>-upload.tar`), and a host temp file (`<key>.tar`). A path separator or
// `..` in the key escapes those roots. A reserved stem makes the asset archive
// share a path with the workspace archive; under concurrent sync the asset task
// and the workspace task then write or upload the same archive at the same time,
// which fails extraction nondeterministically or puts asset bytes in the
// workspace. Fail closed on both cases.
function assertRuntimeAssetKeyIsSafe(key: string): void {
if (key.length === 0 || key.includes("/") || key.includes("\\") || key.includes("..")) {
throw new Error(`sandbox runtime asset key is not a simple path segment: ${key}`);
}
if (RESERVED_RUNTIME_ASSET_KEYS.has(key)) {
throw new Error(`sandbox runtime asset key collides with a reserved runtime archive name: ${key}`);
}
}
export function parseSandboxRemoteExecutionSpec(value: unknown): SandboxRemoteExecutionSpec | null {
const parsed = asObject(value);
const transport = asString(parsed.transport).trim();
const provider = asString(parsed.provider).trim();
const sandboxId = asString(parsed.sandboxId).trim();
const remoteCwd = asString(parsed.remoteCwd).trim();
const timeoutMs = asNumber(parsed.timeoutMs);
if (
transport !== "sandbox" ||
provider.length === 0 ||
sandboxId.length === 0 ||View on GitHub (pinned to a7e689b3c3)
Solutions
- Use a flat, single-segment key: letters, digits, dot, dash, underscore (e.g. "build-cache", "node_modules-cache").
- When deriving a key from a path, take path.basename() and strip anything outside a safe alphabet.
- Reject empty keys and any key containing "/", "\\", or ".." at the boundary where the asset list enters your system.
- Keep multi-level layouts inside the asset archive, not in the key.
Example fix
// before
const key = userInputPath; // "tools/../workspace"
// after
const key = sanitizeAssetKey(userInputPath);
function sanitizeAssetKey(raw: string): string {
const key = path.basename(raw.trim());
if (!key || key.includes("..") || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(key)) {
throw new Error(`invalid runtime asset key: ${JSON.stringify(raw)}`);
}
return key;
} Defensive patterns
Strategy: validation
Validate before calling
const RUNTIME_ASSET_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
function isSafeRuntimeAssetKey(key: string): boolean {
return key.length > 0 && !key.includes("/") && !key.includes("\\") &&
!key.includes("..") && RUNTIME_ASSET_KEY_RE.test(key);
}
const assets = manifest.assets.filter((a) => isSafeRuntimeAssetKey(a.key)); Type guard
function isSafeRuntimeAssetKey(key: unknown): key is string {
return typeof key === "string" && key.length > 0 &&
!key.includes("/") && !key.includes("\\") && !key.includes("..");
} Try / catch
try {
buildRuntimeAssets(assets);
} catch (error) {
if (error instanceof Error && error.message.includes("not a simple path segment")) {
return rejectManifest(error); // surface which key is bad; do not sanitize silently
}
throw error;
} Prevention
- Generate keys from path.basename() plus an allowlist alphabet.
- Validate asset manifests at ingestion (API/config boundary), not at sync time.
- Never accept raw filesystem paths or user strings as asset keys.
When it happens
Trigger: A SandboxRemoteExecutionSpec's runtimeAssets entry uses a key derived from a file path ("cache/node_modules"), a Windows path ("assets\\bin"), a traversal ("../secret"), or an empty string; path building is rejected before any sync starts.
Common situations: Generating keys from user input or workspace-relative paths without normalizing; accepting asset manifests from config files or APIs where callers paste paths instead of names; Windows-developed manifests leaking backslash separators.
Related errors
- sandbox runtime asset key collides with a reserved runtime a
- Access denied
- UI parser path escapes package directory — skipping
- ${prefix}: "sandboxTransport" must be one of ${ADAPTER_LOGIN
- Sandbox duplex channel needs a plugin id, a provider key, an
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/3cf4948d42f9e766.
Report an issue: GitHub.