paperclipai/paperclip · error · Error
sandbox runtime asset key is not a simple path segment
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.
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
- CreateOS transfer path escapes the workspace.
- Invalid canonical workspace path
- escapes its declared root
- sandbox runtime asset key collides with a reserved runtime…
- Unsafe protocol eval campaign ID
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-21).
Data as JSON: /api/errors/3cf4948d42f9e766.
Report an issue: GitHub.
Appendix: source
Thrown at packages/adapter-utils/src/sandbox-managed-runtime.ts:442
* `access` is not `"rw"`.
*/
writablePath?: string;
}
/**
* A control command run against the sandbox after a sync operation's files have
* landed. Mirrors the plugin SDK `PluginPostUploadCommand`; kept as a local
* structural type so `adapter-utils` does not depend on the plugin SDK. Ordered
* within {@link SandboxSyncOperation.postUploadCommands} and executed in array
* order, fail-fast (first non-zero exit or timeout aborts the operation).
*
* SECURITY — command origin (Stage-1 design review, condition C1). `command` is
* a **Paperclip/adapter-authored control operation**: it may be supplied ONLY by
* core/adapter code. No server route, issue/comment content, project/workspace
* file content, provider-plugin callback, or arbitrary adapter config may supply
* a raw `command` string; any path embedded in it MUST be built by adapter/core
* helpers from already-confined paths and shell-quoted (C3). Providers treat the
* command as **opaque** — execute or reject, never rewrite/concatenate/append.
*/
export interface SandboxPostUploadCommand {
/** The opaque, adapter-authored shell command to run after upload. */
command: string;
/**
* Working directory for the command. When present, MUST be an absolute POSIX
* path confined under the operation's allowed sandbox target root (C2). When
* absent, defaults to the runtime's stable command cwd — never a process
* default cwd.
*/
cwd?: string;
/** Optional per-command timeout in milliseconds. */
timeoutMs?: number;
}
/**
* An ordered, opaque unit of work handed to the native sync transport. The
* `operationId` is an opaque, non-sensitive token authored by the orchestratorView on GitHub (pinned to 3f1d897a7c)