paperclipai/paperclip · error
CreateOS transfer requires a confined absolute sandbox path.
Error message
CreateOS transfer requires a confined absolute sandbox path.
What it means
assertRemotePath validates every remote path used in CreateOS file transfers. A path must be POSIX-absolute, contain no NUL byte, and no '..' segment; otherwise it cannot be confined and the transfer is refused. This is the first-line containment check before path normalization.
Solutions
- Convert the remote path to an absolute POSIX path rooted at /paperclip-workspace before calling the transfer API.
- Normalize the path with path.posix.normalize and strip '..' segments yourself before submission.
- Never pass raw user input as the remote path; map it under ROOT with path.posix.join(ROOT, relativePart).
- On Windows hosts, replace backslashes with forward slashes and drop drive letters before use.
Example fix
// before
await syncFiles(lease, [{ local: file, remote: "output/result.json" }]);
// after
const remote = path.posix.join("/paperclip-workspace", "output/result.json");
await syncFiles(lease, [{ local: file, remote }]); Defensive patterns
Strategy: validation
Validate before calling
function toSafeRemotePath(p) {
const posix = String(p).replaceAll("\\", "/");
const normalized = path.posix.normalize(posix);
if (!path.posix.isAbsolute(normalized) || normalized.includes("\0")) throw new Error("remote path must be absolute");
return path.posix.join("/paperclip-workspace", path.posix.relative("/paperclip-workspace", normalized) || "");
} Type guard
function isConfinedRemotePath(p) {
const n = path.posix.normalize(p);
return path.posix.isAbsolute(n) && !n.includes("\0") && (n === "/paperclip-workspace" || n.startsWith("/paperclip-workspace/"));
} Try / catch
try {
await syncFiles(lease, transfers);
} catch (e) {
if (e.message.includes("confined absolute sandbox path")) {
log.error("bad remote path", { transfers });
}
throw e;
} Prevention
- Always build remote paths with path.posix.join('/paperclip-workspace', rel).
- Never pass raw user input or host-relative paths as remote paths.
- Convert Windows paths (backslashes, drive letters) before transfer calls.
When it happens
Trigger: Calling syncFiles/run/remoteGuard with a relative path (e.g. 'foo/bar'), a Windows-style path ('C:\\foo'), a path containing a NUL character, or a segment equal to '..' (e.g. '/paperclip-workspace/../etc').
Common situations: Joining local relative paths and passing them as remote paths by mistake; user-supplied destination paths not normalized; interpolating OS-specific paths on a Windows host into a POSIX sandbox.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- CreateOS transfer path escapes the workspace.
- ACPX provider node_modules resolves outside the selected…
- ACPX provider package manifest resolves outside the…
- Codex working directory cannot contain the host HOME
- CreateOS archive contains unsafe entries or exceeds the…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/5138b06be28e74b4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/file-sync.ts:16
import path from "node:path";
import os from "node:os";
import { randomUUID } from "node:crypto";
import { createReadStream, createWriteStream, promises as fs } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import * as tar from "tar";
import type { PluginEnvironmentSyncInParams, PluginEnvironmentSyncResult } from "@paperclipai/plugin-sdk";
import { CreateosClient, identifier } from "./client.js";
import { execute, shellQuote } from "./execute.js";
const ROOT = "/paperclip-workspace";
export function assertRemotePath(value: string): void {
if (!path.posix.isAbsolute(value) || value.includes("\0") || value.split("/").includes("..")) {
throw new Error("CreateOS transfer requires a confined absolute sandbox path.");
}
const normalized = path.posix.normalize(value);
if (normalized !== ROOT && !normalized.startsWith(`${ROOT}/`)) throw new Error("CreateOS transfer path escapes the workspace.");
}
function remoteGuard(candidate: string): string {
assertRemotePath(candidate);
// Re-check symlinks inside the sandbox immediately before use. A missing
// canonicalizer fails the command rather than weakening containment.
return `root=$(realpath -- ${shellQuote(ROOT)}) && test "$root" = ${shellQuote(ROOT)} && ` +
`resolved=$(realpath -m -- ${shellQuote(candidate)}) && ` +
`case "$resolved" in "$root"|"$root"/*) ;; *) exit 1 ;; esac`;
}
export async function validateArchive(file: string): Promise<number> {
let invalid = false;
let bytes = 0;
let files = 0;View on GitHub (pinned to 3f1d897a7c)