abhigyanpatwari/GitNexus · error · Error
Refusing symlinked auto-sync quarantine root: ${quarantineRo
Error message
Refusing symlinked auto-sync quarantine root: ${quarantineRoot} What it means
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.
Source
Thrown at gitnexus/src/core/auto-sync/path-security.ts:158
'GitNexus auto-sync isolated a partial or unsafe clone result.',
`Created at: ${new Date().toISOString()}`,
`Original path: ${targetDir}`,
`Retention: keep for ${QUARANTINE_RETENTION_DAYS} days unless an operator reviews and removes it earlier.`,
'Cleanup: verify the original path and remote before manual deletion.',
'',
].join('\n'),
'utf-8',
);
return destination;
}
async function pruneQuarantineEntries(quarantineRoot: string): Promise<void> {
const cutoff = Date.now() - QUARANTINE_RETENTION_DAYS * 24 * 60 * 60 * 1_000;
// readdir and stat both resolve through a link, so a symlinked quarantine
// root would age-sweep and delete entries somewhere else entirely.
const rootStat = await fs.lstat(quarantineRoot).catch(() => undefined);
if (rootStat?.isSymbolicLink()) {
throw new Error(`Refusing symlinked auto-sync quarantine root: ${quarantineRoot}`);
}
let entries;
try {
entries = await fs.readdir(quarantineRoot);
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;
throw err;
}
const survivors = (
await Promise.all(
entries
.filter((entry) => entry.startsWith('auto-sync-'))
.map(async (entry) => {
const entryPath = path.join(quarantineRoot, entry);
const stat = await fs.stat(entryPath).catch(() => undefined);
if (stat && stat.mtimeMs < cutoff) {
await fs.rm(entryPath, { recursive: true, force: true });
return undefined;View on GitHub (pinned to 0d1aed942f)
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.
Example fix
// shell: before ln -s /mnt/other/quarantine /var/lib/gitnexus/quarantine // shell: after mv /mnt/other/quarantine/* /var/lib/gitnexus/quarantine/ && rm /var/lib/gitnexus/quarantine-link; mkdir -p /var/lib/gitnexus/quarantine
Defensive patterns
Strategy: try-catch
Validate before calling
const st = await fs.lstat(quarantinePath).catch(() => null);
if (st?.isSymbolicLink()) throw new Error('quarantine path must be a real directory, not a symlink'); Type guard
function isRealDirStat(st: fs.Stats): boolean {
return st.isDirectory() && !st.isSymbolicLink();
} Try / catch
try {
await resolveConfiguredCloneRoot(cfg);
} catch (err) {
if (String(err.message).startsWith('Refusing symlinked auto-sync quarantine root')) {
console.error('Replace the symlink with a real directory at ' + err.message.split(': ')[1]);
} else throw err;
} Prevention
- 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.
When it happens
Trigger: resolveConfiguredCloneRoot triggers quarantine pruning while the configured quarantine directory is a symbolic link, e.g. /var/lib/gitnexus/quarantine -> /mnt/other-disk/quarantine.
Common situations: Admins symlinking the quarantine dir onto another volume, container images replacing the directory with a link, or a previous migration leaving a link behind.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Path traversal denied
- Analyzer package lock symbolic link does not resolve to a fi
- ${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic li
- ${filename} must not be a symbolic link
- ${filename} must be a regular file
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08).
Data as JSON: /api/errors/af80e4340ae3b0fa.
Report an issue: GitHub.