GHSA-PXCC-8665-PHX8

Vulnerability from github – Published: 2026-06-26 22:29 – Updated: 2026-06-26 22:29
VLAI
Summary
YARD static cache reads raw traversal paths before router sanitization
Details

Summary

YARD'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.

The 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.

Details

The --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.

PoC

poc.zip

bash ./poc/run.sh

expected output:

run 1: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint='YARD_STATIC_CACHE_PATH_TRAVERSAL'
run 2: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint='YARD_STATIC_CACHE_PATH_TRAVERSAL'
run 3: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint='YARD_STATIC_CACHE_PATH_TRAVERSAL'

The 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.

Impact

A 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.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "yard"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.9.44"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-49342"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-26T22:29:29Z",
    "nvd_published_at": "2026-06-19T20:16:18Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\nYARD\u0027s static cache lookup reads a request path before the router\u0027s 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\u0027s 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$/, \u0027\u0027) + \u0027.html\u0027)` 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=\u0027YARD_STATIC_CACHE_PATH_TRAVERSAL\u0027\nrun 2: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint=\u0027YARD_STATIC_CACHE_PATH_TRAVERSAL\u0027\nrun 3: exit=0 timed_out=False duration=0.08s matched=True phase=oracle fingerprint=\u0027YARD_STATIC_CACHE_PATH_TRAVERSAL\u0027\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\u0027s 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.",
  "id": "GHSA-pxcc-8665-phx8",
  "modified": "2026-06-26T22:29:29Z",
  "published": "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "YARD static cache reads raw traversal paths before router sanitization"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…