{"record":{"id":"770c4166f1231143","repo":"abhigyanpatwari/GitNexus","slug":"trusted-cache-directory-env-must-not-traverse-s","errorCode":null,"errorMessage":"${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions","messagePattern":"(.+?) must not traverse symbolic links or junctions","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/analyzer-identity.ts","lineNumber":2085,"sourceCode":"  }\n  const normalized = path.normalize(configured);\n  let resolved: string;\n  try {\n    const link = lstatSync(normalized);\n    if (!link.isDirectory() || link.isSymbolicLink()) {\n      throw new Error('not a real directory');\n    }\n    resolved = realpathSync.native(normalized);\n  } catch {\n    throw new Error(\n      `${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory`,\n    );\n  }\n  // Reject junctions/symlinked ancestors as well as a symlink final component.\n  // The environment variable is an explicit trust assertion, but its spelling\n  // must still bind exactly to the directory the cache will use.\n  if (!pathsEqual(path.resolve(normalized), resolved)) {\n    throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions`);\n  }\n  return resolved;\n}\n\nfunction cacheDirectory(\n  options: AnalyzerIdentityResolveOptions,\n  packageRoot: string,\n  buildRoot: string,\n): string | null {\n  // An explicit location is a trusted operator/test override and therefore\n  // remains authoritative, including when the secure default is unavailable.\n  if (options.cacheDirectory) {\n    const explicit = path.resolve(options.cacheDirectory);\n    try {\n      // Create it before any build/dependency directory guards are captured.\n      // A cache nested immediately under a package root then changes that\n      // parent's directory state once, not after we persist the first entry.\n      mkdirSync(explicit, { recursive: true, mode: 0o700 });","sourceCodeStart":2067,"sourceCodeEnd":2103,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/analyzer-identity.ts#L2067-L2103","documentation":"Thrown by trustedEnvironmentCacheDirectory when the directory exists and is a real directory, BUT path.resolve(normalized) differs from realpathSync.native(normalized) — meaning some component of the path (an ancestor or the final segment) is a symlink/junction. Even though lstat already rejected a symlink final segment, this second check catches junctions and symlinked ancestors, because the trust assertion must bind to the exact on-disk directory.","triggerScenarios":"The configured absolute path resolves to a different real path than its lexical form: an ancestor directory is a symlink (e.g. `/tmp` -> `/private/tmp` on macOS, or a `/var` symlink common on some Linux distros), or the path is a junction on Windows. pathsEqual(resolve(normalized), resolved) returns false and the throw fires.","commonSituations":"macOS `/tmp` (which symlinks to `/private/tmp`) — using `/tmp/gn-id` trips this because `/tmp` is a symlink; `/var` -> `/private/var` similarly; Windows junctions used to redirect a folder onto another drive; a docker volume mount that presents as a symlinked ancestor.","solutions":["Point at the realpath directly: run `realpath <configured>` and set the env var to that resolved path.","On macOS prefer `/private/tmp/...` or `$HOME/...` over `/tmp/...`.","On Windows replace junctions with real directories on the target drive.","Re-check after fixing: path.resolve and realpath must agree — verify by comparing the output of `realpath <dir>` against the env var value."],"exampleFix":"# before: /tmp is a symlink to /private/tmp on macOS\n#   export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/tmp/gn-id\n#   -> \"...must not traverse symbolic links or junctions\"\n#\n# after: use the real path\n#   export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/private/tmp/gn-id","handlingStrategy":"validation","validationCode":"const fs = require('node:fs');\nconst path = require('node:path');\nfunction validateTrustedCacheNoSymlink() {\n  const v = process.env.GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR;\n  if (!v) return;\n  const normalized = path.normalize(v);\n  const resolved = fs.realpathSync.native(normalized);\n  const same = process.platform === 'win32'\n    ? path.resolve(normalized).toLowerCase() === resolved.toLowerCase()\n    : path.resolve(normalized) === resolved;\n  if (!same) {\n    throw new Error(`${v} resolves to ${resolved}; set the env var to the real path.`);\n  }\n}\n// validateTrustedCacheNoSymlink();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run `realpath <dir>` and use its output as the env value.","On macOS avoid `/tmp` and `/var` (they symlink to `/private/...`); use `/private/tmp` or `$HOME`.","On Windows avoid junctions; use real directories on the target drive.","Verify equality: `[[ \"$(realpath \"$V\")\" == \"$V\" ]]`."],"tags":["analyzer-identity","cache","configuration","symlink","environment"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}