{"record":{"id":"69a6c617b4f6353c","repo":"abhigyanpatwari/GitNexus","slug":"refusing-to-delete-dbpath-resolved-path-real","errorCode":null,"errorMessage":"Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory","messagePattern":"Refusing to delete (.+?): resolved path (.+?) is outside storage directory","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/lbug/lbug-adapter.ts","lineNumber":832,"sourceCode":"    conn = usable.conn;\n    currentDbReadOnly = true;\n  } else {\n    // LadybugDB stores the database as a single file (not a directory).\n    // If the path already exists, it must be a valid LadybugDB database file.\n    // Remove stale empty directories or files from older versions.\n    try {\n      const stat = await fs.lstat(dbPath);\n      if (stat.isSymbolicLink()) {\n        // Never follow symlinks — just remove the link itself\n        await fs.unlink(dbPath);\n      } else if (stat.isDirectory()) {\n        // Verify path is within expected storage directory before deleting\n        const realPath = await fs.realpath(dbPath);\n        const parentDir = path.dirname(dbPath);\n        const realParent = await fs.realpath(parentDir);\n        const safePrefix = realParent.endsWith(path.sep) ? realParent : realParent + path.sep;\n        if (!realPath.startsWith(safePrefix) && realPath !== realParent) {\n          throw new Error(\n            `Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory`,\n          );\n        }\n        // Old-style directory database or empty leftover - remove it\n        await fs.rm(dbPath, { recursive: true, force: true });\n      }\n      // If it's a file, assume it's an existing LadybugDB database - LadybugDB will open it\n    } catch (err) {\n      if (!isMissingFileError(err)) {\n        throw err;\n      }\n      // Path doesn't exist, which is what LadybugDB wants for a new database\n    }\n\n    // -------------------------------------------------------------------------\n    // Cross-process critical section: acquire init lock, clean orphan sidecars,\n    // and open the database. The lock prevents a TOCTOU race where another\n    // process could create a fresh DB between our access() check and the","sourceCodeStart":814,"sourceCodeEnd":850,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/lbug/lbug-adapter.ts#L814-L850","documentation":"A safety guard in `ensureLbugInitialized`: when the existing `dbPath` is a directory (an old-style directory database or leftover) the code resolves the real path via `realpath` and refuses to recursively delete it unless the resolved path is inside the storage (parent) directory. This prevents a symlink or bind-mount that points the db path OUTSIDE the storage directory from causing `fs.rm({recursive:true})` to wipe an unrelated directory tree. Symlinks themselves are just unlinked (never followed).","triggerScenarios":"`dbPath` exists, is a directory, and `fs.realpath(dbPath)` resolves to a location whose `realPath` is neither inside `realParent` nor equal to it — e.g. a symlink-to-directory or bind-mount pointing outside `.gitnexus/`. The guard throws rather than delete.","commonSituations":"A user symlinked the database path to another volume/dir; a bind-mount or container volume maps the db path outside the storage tree; an old directory-style database that was relocated.","solutions":["Remove the symlink/bind-mount so `dbPath` resolves inside the storage directory, then retry.","Manually delete the old directory database at its real location if it is genuinely stale, then let gitnexus create a fresh file-based DB.","Do not point the dbPath at a location outside the `.gitnexus/` storage directory."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { realpathSync, lstatSync } from 'node:fs';\nimport path from 'node:path';\n\nfunction assertDbPathInStorage(dbPath, storageDir) {\n  const st = lstatSync(dbPath);\n  if (st.isDirectory()) {\n    const real = realpathSync(dbPath);\n    const realParent = realpathSync(path.dirname(dbPath));\n    const safePrefix = realParent.endsWith(path.sep) ? realParent : realParent + path.sep;\n    if (!real.startsWith(safePrefix) && real !== realParent) {\n      throw new Error(`dbPath resolves outside storage dir; refusing to let gitnexus delete it.`);\n    }\n  }\n}\nassertDbPathInStorage(dbPath, storageDir);","typeGuard":null,"tryCatchPattern":"try {\n  await initLbug(dbPath);\n} catch (err) {\n  if (/resolved path .* is outside storage directory/i.test(err.message)) {\n    // A symlink/bind-mount points the db outside .gitnexus — remove it and retry.\n    console.error(err.message);\n    process.exit(9);\n  }\n  throw err;\n}","preventionTips":["Never symlink or bind-mount the dbPath to a location outside the .gitnexus storage directory.","If relocating storage, move the whole storage directory rather than symlinking the db file.","Keep the .gitnexus directory self-contained on one filesystem."],"tags":["safety","symlink","filesystem","delete-guard","database","security"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}