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

  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@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 orchestrator

View on GitHub (pinned to 3f1d897a7c)