{"record":{"id":"af80e4340ae3b0fa","repo":"abhigyanpatwari/GitNexus","slug":"refusing-symlinked-auto-sync-quarantine-root-qu","errorCode":null,"errorMessage":"Refusing symlinked auto-sync quarantine root: ${quarantineRoot}","messagePattern":"Refusing symlinked auto-sync quarantine root: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/auto-sync/path-security.ts","lineNumber":158,"sourceCode":"      'GitNexus auto-sync isolated a partial or unsafe clone result.',\n      `Created at: ${new Date().toISOString()}`,\n      `Original path: ${targetDir}`,\n      `Retention: keep for ${QUARANTINE_RETENTION_DAYS} days unless an operator reviews and removes it earlier.`,\n      'Cleanup: verify the original path and remote before manual deletion.',\n      '',\n    ].join('\\n'),\n    'utf-8',\n  );\n  return destination;\n}\n\nasync function pruneQuarantineEntries(quarantineRoot: string): Promise<void> {\n  const cutoff = Date.now() - QUARANTINE_RETENTION_DAYS * 24 * 60 * 60 * 1_000;\n  // readdir and stat both resolve through a link, so a symlinked quarantine\n  // root would age-sweep and delete entries somewhere else entirely.\n  const rootStat = await fs.lstat(quarantineRoot).catch(() => undefined);\n  if (rootStat?.isSymbolicLink()) {\n    throw new Error(`Refusing symlinked auto-sync quarantine root: ${quarantineRoot}`);\n  }\n  let entries;\n  try {\n    entries = await fs.readdir(quarantineRoot);\n  } catch (err: unknown) {\n    if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n    throw err;\n  }\n  const survivors = (\n    await Promise.all(\n      entries\n        .filter((entry) => entry.startsWith('auto-sync-'))\n        .map(async (entry) => {\n          const entryPath = path.join(quarantineRoot, entry);\n          const stat = await fs.stat(entryPath).catch(() => undefined);\n          if (stat && stat.mtimeMs < cutoff) {\n            await fs.rm(entryPath, { recursive: true, force: true });\n            return undefined;","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/core/auto-sync/path-security.ts#L140-L176","documentation":"pruneQuarantineEntries refuses to run when the quarantine root itself is a symlink (checked with lstat, which does not follow links). Because readdir/stat resolve through links, a symlinked root would cause the age-based prune to delete entries in some other directory entirely; this guard turns that silent data-loss risk into an explicit error naming the offending path.","triggerScenarios":"resolveConfiguredCloneRoot triggers quarantine pruning while the configured quarantine directory is a symbolic link, e.g. /var/lib/gitnexus/quarantine -> /mnt/other-disk/quarantine.","commonSituations":"Admins symlinking the quarantine dir onto another volume, container images replacing the directory with a link, or a previous migration leaving a link behind.","solutions":["Replace the symlink with a real directory (move the data and remove the link) at the configured quarantine path.","Update the auto-sync config to point local_path/quarantine settings at the real directory location.","Verify with `ls -ld <path>` that the target is not a symlink (first character not 'l'), then re-run."],"exampleFix":"// shell: before\nln -s /mnt/other/quarantine /var/lib/gitnexus/quarantine\n// shell: after\nmv /mnt/other/quarantine/* /var/lib/gitnexus/quarantine/ && rm /var/lib/gitnexus/quarantine-link; mkdir -p /var/lib/gitnexus/quarantine","handlingStrategy":"try-catch","validationCode":"const st = await fs.lstat(quarantinePath).catch(() => null);\nif (st?.isSymbolicLink()) throw new Error('quarantine path must be a real directory, not a symlink');","typeGuard":"function isRealDirStat(st: fs.Stats): boolean {\n  return st.isDirectory() && !st.isSymbolicLink();\n}","tryCatchPattern":"try {\n  await resolveConfiguredCloneRoot(cfg);\n} catch (err) {\n  if (String(err.message).startsWith('Refusing symlinked auto-sync quarantine root')) {\n    console.error('Replace the symlink with a real directory at ' + err.message.split(': ')[1]);\n  } else throw err;\n}","preventionTips":["Deploy real directories, not symlinks, at configured quarantine paths.","Check `ls -ld` output after migrations or volume mounts that may substitute links.","If offloading to another disk is needed, reconfigure the path in config instead of linking."],"tags":["security","symlink","filesystem","auto-sync"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-09-08T00:40:44.970Z","contentChangedAt":"2026-09-08T00:40:44.970Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}