{"record":{"id":"351d112376333e04","repo":"abhigyanpatwari/GitNexus","slug":"invalid-upload-path","errorCode":null,"errorMessage":"Invalid upload path","messagePattern":"Invalid upload path","errorType":"http","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"gitnexus/src/server/upload-ingest.ts","lineNumber":68,"sourceCode":"\nexport interface IngestResult {\n  /** Absolute path to the populated staging directory (realpath-canonical). */\n  stageRoot: string;\n  fileCount: number;\n  totalBytes: number;\n  /** First path segment shared by the uploaded tree (the picked folder). */\n  topLevelName: string;\n}\n\n/**\n * Resolve a client-provided relative path to an absolute destination PROVABLY\n * contained within `stageRoot`. Throws BadRequestError on any unsafe input.\n * This is the load-bearing path-traversal-on-write control; keep it pure and\n * unit-tested.\n */\nexport function resolveContainedDest(stageRoot: string, rel: unknown): string {\n  if (typeof rel !== 'string' || rel.length === 0) {\n    throw new BadRequestError('Invalid upload path');\n  }\n  if (rel.length > MAX_PATH_LENGTH) {\n    throw new BadRequestError('Upload path too long');\n  }\n  // webkitRelativePath is always relative; a leading slash is absolute/hostile.\n  if (rel.startsWith('/')) {\n    throw new BadRequestError('Invalid upload path');\n  }\n  // Browsers emit forward slashes only; a NUL byte or backslash is hostile.\n  if (rel.includes('\\u0000') || rel.includes('\\\\')) {\n    throw new BadRequestError('Invalid upload path');\n  }\n  const rawSegments = rel.split('/').filter((s) => s.length > 0);\n  if (rawSegments.length === 0 || rawSegments.length > MAX_PATH_DEPTH) {\n    throw new BadRequestError('Invalid upload path');\n  }\n  const segments: string[] = [];\n  for (const seg of rawSegments) {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/upload-ingest.ts#L50-L86","documentation":"resolveContainedDest is the load-bearing path-traversal control for browser folder uploads in gitnexus's serve upload-ingest pipeline. This first throw rejects a manifest webkitRelativePath that is not a non-empty string: a missing/undefined manifest entry, a number, null, or ''. Every file part's declared path passes through here before any filesystem write, so malformed values never reach disk.","triggerScenarios":"POST multipart upload to the serve ingest endpoint where a file part's path in the JSON manifest is absent, '', null, a number, or any non-string; a hand-rolled uploader client that omits webkitRelativePath for root-level files instead of sending just the filename; manifest deserialization producing undefined for skipped keys.","commonSituations":"Custom upload clients not using the browser File.webkitRelativePath API (drag-drop scripts, curl-based tests); older browsers/polyfills where webkitRelativePath is undefined; a manifest built from Object.keys with a typo'd key so lookups yield undefined; API consumers testing the endpoint with fabricated manifests.","solutions":["In your uploader, always send file.webkitRelativePath || file.name for every file part so the manifest path is a non-empty string","Validate the manifest client-side before POSTing: every entry must be a string of length > 0","If you hit this as an API consumer, treat HTTP 400 with 'Invalid upload path' as a client bug in manifest construction, not a server fault"],"exampleFix":"// before — client manifest omits path for root files\nmanifest.files.forEach(f => entries.push({ path: f.webkitRelativePath })); // undefined for some picks\n// after\nmanifest.files.forEach(f => entries.push({ path: f.webkitRelativePath || f.name }));","handlingStrategy":"type-guard","validationCode":"// Client-side: build a manifest that can never contain empty/non-string paths\nconst entries = files.map((f) => ({\n  path: (f.webkitRelativePath || f.name) as string, // always non-empty string\n}));","typeGuard":"function isNonEmptyRelativePath(rel: unknown): rel is string {\n  return (\n    typeof rel === 'string' &&\n    rel.length > 0 &&\n    !rel.startsWith('/') &&\n    !rel.includes('\\\\') &&\n    !rel.includes('\\u0000')\n  );\n}","tryCatchPattern":null,"preventionTips":["Always send file.webkitRelativePath || file.name for every file part","Validate the manifest client-side (type, non-empty, forward slashes only) before POSTing","Treat HTTP 400 'Invalid upload path' as a client bug — fix the uploader, don't retry the same body"],"tags":["upload","path-traversal","manifest","validation","security-guard","multipart"],"backgroundTag":"upload-path-validation-failed","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}