paperclipai/paperclip · error

Daytona syncOut refusing unparseable or ambiguous symlink…

Error message

Daytona syncOut refusing unparseable or ambiguous symlink entry: ${line}

What it means

Fail-closed check in assertTarballEntriesConfined: a tar member whose type flag is 'l' (symlink) did not split cleanly on exactly one ' -> ' separator, so its name and link target are ambiguous. Since the target cannot be verified to stay inside the extraction dir, the syncOut is refused outright.

Solutions

  1. Fix or exclude the ambiguous symlink entry in the tarball.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at packages/plugins/sandbox-providers/daytona/src/file-sync.ts:247 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-18). Data as JSON: /api/errors/cfda5e0ac93cdf50. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugins/sandbox-providers/daytona/src/file-sync.ts:251

  const first = field.indexOf(delimiter);
  if (first === -1) return null;
  if (field.indexOf(delimiter, first + delimiter.length) !== -1) return null;
  return { name: field.slice(0, first), target: field.slice(first + delimiter.length) };
}

/**
 * Reject a sandbox-authored tarball before extraction if any member would land
 * outside the extraction dir. The archive is produced by the (untrusted) sandbox,
 * so `tar -xf` on the host must never be handed an archive whose entries carry
 * absolute paths or `../` traversal, nor a symlink/hardlink member whose target
 * escapes the tree — the latter would let a follow-up member be written through
 * the link to an arbitrary host path. Legitimate in-tree relative links (targets
 * that resolve back inside the archive, e.g. `shortcut -> nested/data.txt`) are
 * preserved. Parses the `-tvf` verbose listing so both member names and link
 * targets are inspected; any unparseable line fails closed.
 */
async function assertTarballEntriesConfined(archivePath: string): Promise<void> {
  const { stdout } = await execFileAsync("tar", ["-tvf", archivePath], {
    env: { ...process.env, COPYFILE_DISABLE: "1" },
    maxBuffer: 32 * 1024 * 1024,
  });
  const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
  for (const line of lines) {
    const parsed = parseTarVerboseListingLine(line);
    if (!parsed) {
      throw new Error(`Daytona syncOut refusing tarball with an unparseable entry listing: ${line}`);
    }
    const typeFlag = parsed.typeFlag;
    let name = parsed.rest;
    let linkTarget: string | null = null;
    if (typeFlag === "l") {
      const split = splitLinkEntryOnce(name, " -> ");
      if (!split) throw new Error(`Daytona syncOut refusing unparseable or ambiguous symlink entry: ${line}`);
      name = split.name;
      linkTarget = split.target;
    } else if (typeFlag === "h") {

View on GitHub (pinned to 3f1d897a7c)