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

  1. Use a flat, single-segment key: letters, digits, dot, dash, underscore (e.g. "build-cache", "node_modules-cache").
  2. When deriving a key from a path, take path.basename() and strip anything outside a safe alphabet.
  3. Reject empty keys and any key containing "/", "\\", or ".." at the boundary where the asset list enters your system.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21). Data as JSON: /api/errors/3cf4948d42f9e766. Report an issue: GitHub.