paperclipai/paperclip · critical
sync operation ${label} path escapes its confinement root: $
Error message
sync operation ${label} path escapes its confinement root: ${candidate} What it means
The second confinement check in assertSyncOperationsConfined: the path is a clean absolute POSIX path but does not fall under any of the allowed roots supplied for that label (source or target). It fails closed to prevent a sync operation from reading or writing outside orchestrator-owned directories, blocking absolute-path escapes that are technically valid but unauthorized.
Source
Thrown at packages/adapter-utils/src/sandbox-managed-runtime.ts:281
* Absolute escapes and `..` traversal are rejected fail-closed. Sandbox and host
* paths on the server are POSIX.
*/
export function assertSyncOperationsConfined(
operations: SandboxSyncOperation[],
roots: { sourceRoots: string[]; targetRoots: string[] },
): void {
const confine = (candidate: string, allowed: string[], label: string): void => {
const normalized = path.posix.normalize(candidate);
if (!path.posix.isAbsolute(normalized) || normalized === ".." || normalized.includes("/../") || normalized.endsWith("/..")) {
throw new Error(`sync operation ${label} path is not a confined absolute path: ${candidate}`);
}
const within = allowed.some((root) => {
const normalizedRoot = path.posix.normalize(root);
const prefix = normalizedRoot.endsWith("/") ? normalizedRoot : `${normalizedRoot}/`;
return normalized === normalizedRoot || normalized.startsWith(prefix);
});
if (!within) {
throw new Error(`sync operation ${label} path escapes its confinement root: ${candidate}`);
}
};
for (const operation of operations) {
for (const mapping of operation.files) {
confine(mapping.sourcePath, roots.sourceRoots, "source");
confine(mapping.targetPath, roots.targetRoots, "target");
}
}
}
export interface PreparedSandboxManagedRuntime {
spec: SandboxRemoteExecutionSpec;
workspaceLocalDir: string;
workspaceRemoteDir: string;
runtimeRootDir: string;
assetDirs: Record<string, string>;
/**
* Remote directory of each additional (referenced) project that stagedView on GitHub (pinned to 67001ec6eb)
Solutions
- Compare the offending candidate (logged in the message) against roots.sourceRoots/targetRoots and add the missing owning root.
- Canonicalize paths with fs.realpath before submitting so symlinks resolve to their true root before the check.
- Confirm runtimeRootDir/tempDir used to build targetPaths are included in the targetRoots you pass.
- Restrict caller-supplied paths to known prefixes at the API boundary so they never escape.
Defensive patterns
Strategy: validation
Validate before calling
function assertWithinRoots(candidate: string, roots: string[]): void {
const n = path.posix.normalize(candidate);
const ok = roots.some((r) => {
const nr = path.posix.normalize(r);
const prefix = nr.endsWith('/') ? nr : nr + '/';
return n === nr || n.startsWith(prefix);
});
if (!ok) throw new Error(`path escapes confinement roots: ${candidate}`);
} Prevention
- Keep roots.sourceRoots/targetRoots in sync with every directory you stage from/into.
- Resolve symlinks (fs.realpath) before the confinement check so real paths are evaluated.
- Document which roots each staging step declares.
When it happens
Trigger: Calling assertSyncOperationsConfined where a mapping path is absolute and traversal-free but starts with a root not present in roots.sourceRoots (for source) or roots.targetRoots (for target). E.g. targetPath under /etc while targetRoots only lists the runtime root.
Common situations: roots arrays were not updated when a new staging directory was introduced; a symlink-resolved real path points outside the declared root; misconfigured runtimeRootDir or tempDir; caller passed an asset dir outside the asset root.
Related errors
- sync operation ${label} path is not a confined absolute path
- provision stageFile.name must be a simple basename, got: ${s
- Daytona sync ${label} path is not a confined absolute path:
- Daytona sync ${label} path escapes the workspace remote dir:
- Refusing to write export file outside output directory: ${re
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/db2232eb93b38f3a.
Report an issue: GitHub.