{"record":{"id":"76116ef93fd82c98","repo":"Crosstalk-Solutions/project-nomad","slug":"invalid-document-slug","errorCode":null,"errorMessage":"Invalid document slug","messagePattern":"Invalid document slug","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"admin/app/services/docs_service.ts","lineNumber":77,"sourceCode":"    } catch (error) {\n      logger.error('Error parsing Markdoc content:', error)\n      throw new InternalServerErrorException(`Error parsing content: ${(error as Error).message}`)\n    }\n  }\n\n  async parseFile(_filename: string) {\n    try {\n      if (!_filename) {\n        throw new Error('Filename is required')\n      }\n\n      const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md`\n\n      // Prevent path traversal — resolved path must stay within the docs directory\n      const basePath = path.resolve(this.docsPath)\n      const fullPath = path.resolve(path.join(this.docsPath, filename))\n      if (!fullPath.startsWith(basePath + path.sep)) {\n        throw new Error('Invalid document slug')\n      }\n\n      const fileExists = await getFileStatsIfExists(fullPath)\n      if (!fileExists) {\n        throw new Error(`File not found: ${filename}`)\n      }\n\n      const fileStream = await getFile(fullPath, 'stream')\n      if (!fileStream) {\n        throw new Error(`Failed to read file stream: ${filename}`)\n      }\n      const content = await streamToString(fileStream)\n      return this.parse(content)\n    } catch (error) {\n      throw new InternalServerErrorException(`Error parsing file: ${(error as Error).message}`)\n    }\n  }\n","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/services/docs_service.ts#L59-L95","documentation":"Path-traversal guard in DocsService.parseFile: the requested filename is joined onto docsPath, resolved, and required to stay strictly under the resolved base directory (basePath + path.sep prefix). Any slug that escapes the docs directory (via .., absolute paths, or symlinked resolution tricks) throws 'Invalid document slug'.","triggerScenarios":"parseFile('../../etc/passwd'), parseFile('../..\\\\..\\\\secret'), a slug containing a leading slash making it absolute on POSIX, or any resolved path equal to or outside the docs root (basePath itself without a trailing separator is also rejected).","commonSituations":"User-supplied slugs passed unsanitized from query/path params, URL-encoded %2e%2e%2f sequences that decode before resolution, testing with slugs like '.' or empty-ish values that resolve to the base dir itself.","solutions":["Reject the request at the API layer — this is a security guard, do not bypass it","Sanitize slugs: allow only [a-z0-9-] segments before calling parseFile","If a legitimate nested doc fails, confirm the file truly lives under docsPath and the slug uses correct relative separators","Never try to work around by stripping the check; fix the caller's path construction"],"exampleFix":"// before\nawait docs.parseFile(decodeURIComponent(req.query.slug)) // '../../config'\n\n// after\nconst slug = req.query.slug as string\nif (!/^[a-z0-9\\/-]+$/i.test(slug) || slug.includes('..')) {\n  throw new BadRequestException('Invalid slug')\n}\nawait docs.parseFile(slug)","handlingStrategy":"validation","validationCode":"const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/\nif (!SLUG_RE.test(slug)) throw new BadRequestException('Invalid slug')\nawait docsService.parseFile(slug) // cannot traverse","typeGuard":"const isSafeDocSlug = (slug: string): boolean =>\n  !slug.includes('..') && !path.isAbsolute(slug) && SLUG_RE.test(slug)","tryCatchPattern":"try {\n  await docsService.parseFile(slug)\n} catch (e) {\n  if ((e as Error).message === 'Invalid document slug')\n    return res.status(400).json({ error: 'Invalid document slug' })\n  throw e\n}","preventionTips":["Never pass raw decoded query/path segments as filenames","Whitelist slug characters at the API boundary","Treat this error as a security signal: log and audit occurrences, don't just 404"],"tags":["security","path-traversal","validation","docs"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}