{"record":{"id":"6d98f9081465eb6c","repo":"abhigyanpatwari/GitNexus","slug":"path-must-be-an-absolute-path","errorCode":null,"errorMessage":"\"path\" must be an absolute path","messagePattern":"\"path\" must be an absolute path","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"gitnexus/src/server/api.ts","lineNumber":1540,"sourceCode":"        if (tokenError) {\n          res.status(tokenError.status).json({ error: tokenError.error });\n          return;\n        }\n\n        // Path validation. The previous `normalize !== resolve` guard was inert\n        // (both collapse `..` identically) and only false-rejected trailing\n        // slashes, so it is dropped. Analyzing a local path the operator names\n        // is the tool's intended capability (same as the CLI); the dangerous\n        // part was cross-origin reach, which is closed by requireTrustedOrigin\n        // on this route (scoped to loopback, the server's own bound host, and a\n        // configured GITNEXUS_PUBLIC_ORIGIN — other LAN devices are NOT\n        // trusted). We only require an absolute path here and\n        // let the analyze worker surface a clear error if it does not exist.\n        // (We do NOT realpath/stat the path in-route: that would be a\n        // user-controlled filesystem read — CodeQL js/path-injection — for no\n        // security gain.)\n        if (repoLocalPath && !path.isAbsolute(repoLocalPath)) {\n          res.status(400).json({ error: '\"path\" must be an absolute path' });\n          return;\n        }\n\n        const job = jobManager.createJob({ repoUrl, repoPath: repoLocalPath });\n\n        // If job was already running (dedup), just return its id. The token is\n        // not part of the dedup identity and is never stored on the job, so a\n        // token on THIS request had no effect — the existing job already\n        // cloned (or is cloning) with whatever credentials its originating\n        // request supplied. Surface `tokenIgnored` so an authenticated caller\n        // isn't misled into thinking their PAT took effect on a reused job.\n        if (job.status !== 'queued') {\n          const body: { jobId: string; status: string; tokenIgnored?: boolean } = {\n            jobId: job.id,\n            status: job.status,\n          };\n          if (repoToken !== undefined) body.tokenIgnored = true;\n          res.status(202).json(body);","sourceCodeStart":1522,"sourceCodeEnd":1558,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/api.ts#L1522-L1558","documentation":"HTTP 400 returned by POST /api/analyze when \"path\" is present but relative (fails path.isAbsolute). The server refuses to resolve relative paths itself — resolution would depend on the server's cwd — and deliberately does not stat or realpath user input in-route (rejected as a user-controlled filesystem probe), so it demands an already-absolute path and lets the analyze worker surface a clean error if the directory does not exist.","triggerScenarios":"{\"path\": \"../my-repo\"}, {\"path\": \"src\"}, {\"path\": \"~/repos/foo\"} (tilde is never expanded and is not absolute), or any client forwarding cwd-relative input such as raw shell arguments or process.argv values.","commonSituations":"CLI-style users passing relative arguments; front-ends forwarding relative paths from config files; '~' home shorthand on POSIX; Windows drive-relative forms like \"repo\\sub\" without a drive letter.","solutions":["Send an OS-absolute path: /home/me/repos/foo on POSIX, C:\\Users\\me\\repos\\foo on Windows","Resolve client-side first: path.resolve(input) or path.join(process.cwd(), input)","Expand '~' with os.homedir() before sending — the server will not do it","If you meant a remote repository, send \"url\" instead of \"path\""],"exampleFix":"// before\nconst body = { path: inputDir }; // e.g. \"../repos/foo\" → 400\n\n// after\nimport path from 'node:path';\nconst body = { path: path.resolve(inputDir) }; // \"/home/me/repos/foo\"","handlingStrategy":"validation","validationCode":"import path from 'node:path';\n// Normalize before sending\nif (typeof body.path === 'string' && !path.isAbsolute(body.path)) {\n  body.path = path.resolve(body.path);\n}","typeGuard":"const isAcceptableAnalyzePath = (v: unknown): v is string =>\n  typeof v === 'string' && path.isAbsolute(v);","tryCatchPattern":null,"preventionTips":["Resolve user input against a base dir client-side with path.resolve","Expand '~' via os.homedir() before sending","Remember the server never stats or canonicalizes the path in-route — hand it a full absolute path"],"tags":["http-400","validation","filesystem","path","api"],"backgroundTag":"invalid-file-path","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}