paperclipai/paperclip · critical
Daytona sync ${label} path is not a confined absolute path:
Error message
Daytona sync ${label} path is not a confined absolute path: ${candidate} What it means
Thrown by `assertConfinedSandboxPath` (file-sync.ts:117) — a fail-closed security guard — when a candidate sandbox path is not POSIX-absolute, or normalizes to `..`, contains `/../`, or ends with `/..`. Every inbound (sync target) and outbound (sync source) path MUST canonicalize inside the workspace remote dir before any bytes move; this is the first check rejecting non-absolute or traversing paths.
Source
Thrown at packages/plugins/sandbox-providers/daytona/src/file-sync.ts:117
}
/**
* Host-side complete-mediation guard applied as defense-in-depth below the
* orchestrator's own confinement. Every sandbox-side path (the sync target for
* inbound, the sync source for outbound) MUST canonicalize inside the workspace
* remote dir; absolute escapes and `..` traversal are rejected fail-closed before
* any bytes move. Sandbox paths on the server are POSIX.
*/
export function assertConfinedSandboxPath(remoteDir: string, candidate: string, label: string): void {
const normalizedRoot = path.posix.normalize(remoteDir);
const normalized = path.posix.normalize(candidate);
if (
!path.posix.isAbsolute(normalized) ||
normalized === ".." ||
normalized.includes("/../") ||
normalized.endsWith("/..")
) {
throw new Error(`Daytona sync ${label} path is not a confined absolute path: ${candidate}`);
}
const prefix = normalizedRoot.endsWith("/") ? normalizedRoot : `${normalizedRoot}/`;
if (normalized !== normalizedRoot && !normalized.startsWith(prefix)) {
throw new Error(`Daytona sync ${label} path escapes the workspace remote dir: ${candidate}`);
}
}
async function withHostTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T> {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-daytona-sync-"));
try {
return await fn(dir);
} finally {
await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
}
}
/**
* Build a host-side tarball of a directory, mirroring the runtime's ownView on GitHub (pinned to 67001ec6eb)
Solutions
- Ensure every sync `targetPath`/`sourcePath` and command `cwd` is POSIX-absolute (leading `/`) and resolved under the workspace `remoteDir`.
- Use `path.posix.resolve(remoteDir, relativeInput)` to absolutize, then pass the result.
- Reject `..` components in untrusted path input before constructing the mapping.
Example fix
// before
mapping: { sourcePath: localFile, targetPath: "repo/file.txt", kind: "file" }
// after
mapping: { sourcePath: localFile, targetPath: `${remoteDir}/repo/file.txt`, kind: "file" } Defensive patterns
Strategy: validation
Validate before calling
import path from "node:path";
function assertConfined(remoteDir, candidate) {
const n = path.posix.normalize(candidate);
if (!path.posix.isAbsolute(n) || n === ".." || n.includes("/../") || n.endsWith("/..")) {
throw new Error(`path not a confined absolute path: ${candidate}`);
}
return n;
}
// call assertConfined(remoteDir, targetPath) BEFORE building the sync mapping Type guard
function isConfinedAbsolutePath(remoteDir: string, candidate: string): boolean {
const n = path.posix.normalize(candidate);
if (!path.posix.isAbsolute(n) || n === ".." || n.includes("/../") || n.endsWith("/..")) return false;
const prefix = remoteDir.endsWith("/") ? remoteDir : `${remoteDir}/`;
return n === remoteDir || n.startsWith(prefix);
} Prevention
- Always build sync target/source paths as POSIX-absolute and remoteDir-rooted.
- Use path.posix (not the host path module) when constructing sandbox paths.
- Reject relative or '..'-containing input from untrusted callers before syncing.
When it happens
Trigger: A sync file mapping supplies a `targetPath`/`sourcePath` that is relative (`"foo"`), dot-relative (`"./x"`, `"../x"`), or contains `..` segments after POSIX normalization. Also fires for a post-upload command `cwd` that is not absolute. The guard runs host-side before any sandbox round trip.
Common situations: A plugin/mapping builder constructs paths with `path.join` on a Windows host (backslashes), passes a relative repo path by mistake, or accepts untrusted user input as a sync target without absolutizing it under the remote dir.
Related errors
- Daytona sync ${label} path escapes the workspace remote dir:
- sync operation ${label} path is not a confined absolute path
- sync operation ${label} path escapes its confinement root: $
- provision stageFile.name must be a simple basename, got: ${s
- Daytona syncOut refusing tarball with an unparseable entry l
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/9d38630e6db9f49b.
Report an issue: GitHub.