{"record":{"id":"9ce30a7971b3aeca","repo":"paperclipai/paperclip","slug":"daytona-syncout-refusing-unparseable-symlink-entry","errorCode":null,"errorMessage":"Daytona syncOut refusing unparseable symlink entry: ${line}","messagePattern":"Daytona syncOut refusing unparseable symlink entry: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/plugins/sandbox-providers/daytona/src/file-sync.ts","lineNumber":210,"sourceCode":" */\nasync function assertTarballEntriesConfined(archivePath: string): Promise<void> {\n  const { stdout } = await execFileAsync(\"tar\", [\"-tvf\", archivePath], {\n    env: { ...process.env, COPYFILE_DISABLE: \"1\" },\n    maxBuffer: 32 * 1024 * 1024,\n  });\n  const lines = stdout.split(\"\\n\").filter((line) => line.trim().length > 0);\n  for (const line of lines) {\n    // GNU tar -tvf: \"<perms> <owner>/<group> <size> <date> <time> <name>[ -> target]\".\n    const match = line.match(/^(\\S+)\\s+\\S+\\s+\\d+\\s+\\S+\\s+\\S+\\s+(.*)$/);\n    if (!match) {\n      throw new Error(`Daytona syncOut refusing tarball with an unparseable entry listing: ${line}`);\n    }\n    const typeFlag = match[1][0];\n    let name = match[2];\n    let linkTarget: string | null = null;\n    if (typeFlag === \"l\") {\n      const idx = name.indexOf(\" -> \");\n      if (idx === -1) throw new Error(`Daytona syncOut refusing unparseable symlink entry: ${line}`);\n      linkTarget = name.slice(idx + \" -> \".length);\n      name = name.slice(0, idx);\n    } else if (typeFlag === \"h\") {\n      const idx = name.indexOf(\" link to \");\n      if (idx === -1) throw new Error(`Daytona syncOut refusing unparseable hardlink entry: ${line}`);\n      linkTarget = name.slice(idx + \" link to \".length);\n      name = name.slice(0, idx);\n    }\n    const cleanName = name.replace(/\\/+$/, \"\");\n    if (cleanName.length > 0 && posixPathEscapes(cleanName)) {\n      throw new Error(`Daytona syncOut refusing tarball member that escapes the extraction dir: ${name}`);\n    }\n    if (linkTarget !== null) {\n      const resolved = path.posix.join(path.posix.dirname(cleanName), linkTarget);\n      if (path.posix.isAbsolute(linkTarget) || posixPathEscapes(resolved)) {\n        throw new Error(\n          `Daytona syncOut refusing tarball link whose target escapes the extraction dir: ${name} -> ${linkTarget}`,\n        );","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/daytona/src/file-sync.ts#L192-L228","documentation":"Thrown by `assertTarballEntriesConfined` (file-sync.ts:210) when a tar entry whose permission type-flag starts with `l` (symlink) does not contain the `\" -> \"` separator that delimits the link target in GNU `tar -tvf` output. Without the target, the guard cannot verify the symlink stays inside the extraction dir, so it refuses the entry closed.","triggerScenarios":"An untrusted sandbox-authored tarball contains a symlink entry whose verbose listing line has a leading `l` perm flag but no `\" -> <target>\"` suffix — e.g. a truncated line, a filename literally containing newlines that split the entry, or a non-GNU tar emitting symlinks differently.","commonSituations":"BSD tar or a different tar implementation formats symlinks without the ` -> ` marker; a filename with embedded newlines breaks the per-line parse; or a deliberately crafted archive to confuse the confinement check.","solutions":["Use GNU tar in the sandbox so symlink entries render as `... name -> target`.","Avoid filenames containing newlines in synced-out trees (sanitize before tar).","Re-pack the archive with a compatible tar implementation before outbound sync."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Reject filenames containing newlines before syncOut so tar -tvf lines stay parseable.\nfunction hasNewlineInName(name) { return /[\\r\\n]/.test(name); }","typeGuard":null,"tryCatchPattern":"try {\n  await performSyncOut({ sandbox, operations, remoteDir, timeoutSeconds });\n} catch (err) {\n  if (err instanceof Error && /unparseable symlink entry/.test(err.message)) {\n    // ensure GNU tar in sandbox; strip/escape filenames with newlines; then retry\n  } else throw err;\n}","preventionTips":["Use GNU tar so symlink entries render `name -> target`.","Sanitize synced-out filenames to exclude embedded newlines.","Never weaken the symlink-target check; it blocks extraction-dir escapes."],"tags":["daytona","sandbox","security","file-sync","tar","symlink","fail-closed"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}