{"id":"GHSA-pxcc-8665-phx8","summary":"YARD static cache reads raw traversal paths before router sanitization","details":"### Summary\nYARD's static cache lookup reads a request path before the router's path cleanup runs. When a server is configured with a document root, a traversal path such as `/../yard-cache-secret.html` is joined against that root and can return a readable sibling `.html` file outside the intended static tree. \n\nThe potential security risk seems low, as only html-ending files can be read, but still the risk of reading arbitrary html files is a confiendtiality issue in itself, which is why we decided to report. Please let us know if this is out of your project's scope.\n\n### Details\nThe `--docroot` CLI option stores the configured directory in `server_options[:DocumentRoot]` at `lib/yard/cli/server.rb:198`, and adapter initialization copies that value into `adapter.document_root` at `lib/yard/server/adapter.rb:76`. For Rack requests, `RackAdapter#call` builds a request object from the Rack environment at `lib/yard/server/rack_adapter.rb:58` and passes it to `router.call(request)` at `lib/yard/server/rack_adapter.rb:60`. `Router#call` then stores the incoming request at `lib/yard/server/router.rb:55` and invokes `check_static_cache` before normal routing at `lib/yard/server/router.rb:56`. Inside `check_static_cache`, the only initial guard is that `adapter.document_root` is present at `lib/yard/server/static_caching.rb:35`; the cache path is built from `File.join(adapter.document_root, request.path.sub(/\\.html$/, '') + '.html')` at `lib/yard/server/static_caching.rb:36`, without cleaning `..` components first. If that resolved path is a regular file, `File.file?` accepts it at `lib/yard/server/static_caching.rb:38` and the file bytes are returned as a `200` HTML response at `lib/yard/server/static_caching.rb:40`. The later route sanitizer in `final_options` uses `File.cleanpath(...).gsub(...)` at `lib/yard/server/router.rb:181` and `lib/yard/server/router.rb:182`, but a static-cache hit returns before that code is reached.\n\n### PoC\n[poc.zip](https://github.com/user-attachments/files/28185421/poc.zip)\n\n```bash\nbash ./poc/run.sh\n```\nexpected output:\n```text\nrun 1: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint='YARD_STATIC_CACHE_PATH_TRAVERSAL'\nrun 2: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint='YARD_STATIC_CACHE_PATH_TRAVERSAL'\nrun 3: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint='YARD_STATIC_CACHE_PATH_TRAVERSAL'\n```\n\nThe `YARD_STATIC_CACHE_PATH_TRAVERSAL` fingerprint is emitted only after the PoC observes a `200` static-cache response whose body contains the sibling file outside the configured document root. A setup failure, syntax failure, or cache miss would not print this oracle and would not demonstrate this traversal read.\n\n### Impact\nA remote unauthenticated HTTP client who can reach a YARD documentation server with `DocumentRoot`/`--docroot` enabled can request `.html` paths containing parent-directory components and receive readable matching files outside the configured document root. The required guards are narrow: `adapter.document_root` must be set, the traversed target must exist as a regular readable file, and the target must be reachable through the implementation's forced `.html` suffix. Those requests bypass the later `final_options` path cleanup because the cache check runs first. The resulting severity class is information disclosure: response bodies can contain off-root `.html` file contents, but this path does not show write access, code execution, or arbitrary files without the `.html` constraint.","aliases":["CVE-2026-49342"],"modified":"2026-06-26T22:56:27.767407Z","published":"2026-06-26T22:29:29Z","database_specific":{"nvd_published_at":"2026-06-19T20:16:18Z","cwe_ids":["CWE-22"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-06-26T22:29:29Z"},"references":[{"type":"WEB","url":"https://github.com/lsegal/yard/security/advisories/GHSA-pxcc-8665-phx8"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-49342"},{"type":"WEB","url":"https://github.com/lsegal/yard/commit/f78c19f0dd33a407085b4ed181bb60c0aa0078b4"},{"type":"PACKAGE","url":"https://github.com/lsegal/yard"}],"affected":[{"package":{"name":"yard","ecosystem":"RubyGems","purl":"pkg:gem/yard"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.9.44"}]}],"versions":["0.2.0","0.2.1","0.2.2","0.2.3","0.2.3.2","0.2.3.3","0.2.3.4","0.2.3.5","0.4.0","0.5.0","0.5.1","0.5.1p1","0.5.2","0.5.3","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.6.6","0.6.7","0.6.8","0.7.0","0.7.1","0.7.2","0.7.3","0.7.4","0.7.5","0.8.0","0.8.1","0.8.2","0.8.2.1","0.8.3","0.8.4","0.8.4.1","0.8.5","0.8.5.1","0.8.5.2","0.8.6","0.8.6.1","0.8.6.2","0.8.7","0.8.7.1","0.8.7.2","0.8.7.3","0.8.7.4","0.8.7.5","0.8.7.6","0.9.0","0.9.1","0.9.10","0.9.11","0.9.12","0.9.13","0.9.14","0.9.15","0.9.16","0.9.17","0.9.18","0.9.19","0.9.2","0.9.20","0.9.21","0.9.22","0.9.23","0.9.24","0.9.25","0.9.26","0.9.27","0.9.28","0.9.29","0.9.3","0.9.30","0.9.31","0.9.32","0.9.33","0.9.34","0.9.35","0.9.36","0.9.37","0.9.38","0.9.39","0.9.4","0.9.40","0.9.41","0.9.42","0.9.43","0.9.5","0.9.6","0.9.7","0.9.8","0.9.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-pxcc-8665-phx8/GHSA-pxcc-8665-phx8.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"}]}