paperclipai/paperclip · critical
Daytona syncOut refusing tarball link whose target escapes t
Error message
Daytona syncOut refusing tarball link whose target escapes the extraction dir: ${name} -> ${linkTarget} What it means
Thrown by assertTarballEntriesConfined when a symlink or hardlink member's target, resolved relative to its parent directory, is absolute or escapes the extraction dir. This prevents a follow-up tar member from being written through the link to an arbitrary host path. The guard runs before host-side extraction on the untrusted sandbox-authored archive.
Source
Thrown at packages/plugins/sandbox-providers/daytona/src/file-sync.ts:226
if (typeFlag === "l") {
const idx = name.indexOf(" -> ");
if (idx === -1) throw new Error(`Daytona syncOut refusing unparseable symlink entry: ${line}`);
linkTarget = name.slice(idx + " -> ".length);
name = name.slice(0, idx);
} else if (typeFlag === "h") {
const idx = name.indexOf(" link to ");
if (idx === -1) throw new Error(`Daytona syncOut refusing unparseable hardlink entry: ${line}`);
linkTarget = name.slice(idx + " link to ".length);
name = name.slice(0, idx);
}
const cleanName = name.replace(/\/+$/, "");
if (cleanName.length > 0 && posixPathEscapes(cleanName)) {
throw new Error(`Daytona syncOut refusing tarball member that escapes the extraction dir: ${name}`);
}
if (linkTarget !== null) {
const resolved = path.posix.join(path.posix.dirname(cleanName), linkTarget);
if (path.posix.isAbsolute(linkTarget) || posixPathEscapes(resolved)) {
throw new Error(
`Daytona syncOut refusing tarball link whose target escapes the extraction dir: ${name} -> ${linkTarget}`,
);
}
}
}
}
async function extractHostTarball(input: { archivePath: string; localDir: string }): Promise<void> {
// The archive is sandbox-authored and untrusted: validate every member (and
// link target) is confined before letting host-side tar write a single byte.
await assertTarballEntriesConfined(input.archivePath);
await fs.mkdir(input.localDir, { recursive: true });
await execFileAsync("tar", ["-xf", input.archivePath, "-C", input.localDir], {
env: { ...process.env, COPYFILE_DISABLE: "1" },
maxBuffer: 32 * 1024 * 1024,
});
}
View on GitHub (pinned to 67001ec6eb)
Solutions
- Inspect the `name -> linkTarget` in the error to identify the offending link.
- Remove or rewrite the sandbox-side symlink/hardlink so its target resolves inside the extraction dir.
- If the link is legitimate, restructure it to be a relative in-tree target.
- Re-run syncOut once the link target is confined.
Example fix
// before: symlink target escapes (ln -s /etc/passwd ./escape) // after: in-tree relative link (ln -s ./nested/data.txt ./shortcut)
Defensive patterns
Strategy: validation
Validate before calling
function linkTargetIsConfined(memberName: string, linkTarget: string): boolean {
if (path.posix.isAbsolute(linkTarget)) return false;
const resolved = path.posix.join(path.posix.dirname(memberName.replace(/\/+$/, '')), linkTarget);
const norm = path.posix.normalize(resolved);
return !(norm === '..' || norm.startsWith('../'));
} Try / catch
try {
await extractHostTarball({ archivePath, localDir });
} catch (e) {
if (e instanceof Error && e.message.includes('link whose target escapes')) {
// reject the archive; link target would allow out-of-tree writes
}
throw e;
} Prevention
- Never disable the tarball link-target confinement check; it prevents symlink-based escapes.
- Ensure sandbox symlinks/hardlinks use relative in-tree targets only.
- Reject archives with absolute or traversing link targets at the source.
When it happens
Trigger: A tarball link entry (typeFlag 'l' or 'h') whose linkTarget is an absolute path, or whose resolved path (dirname(member) joined with linkTarget) normalizes to '..' or '../…'.
Common situations: A sandbox plants a symlink pointing to /etc/passwd or ../../etc so a later member write escapes the tree; a legitimate relative link that resolves outside the archive root; misconfigured build artifacts producing links to system paths.
Related errors
- Daytona syncOut refusing tarball member that escapes the ext
- Daytona syncOut refusing unparseable symlink entry: ${line}
- Daytona syncOut refusing unparseable hardlink entry: ${line}
- Daytona sync ${label} path is not a confined absolute path:
- Daytona sync ${label} path escapes the workspace remote dir:
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/36faf051b8904ca8.
Report an issue: GitHub.