GHSA-3P34-W4F6-5XH2

Vulnerability from github – Published: 2026-06-26 20:56 – Updated: 2026-06-26 20:56
VLAI
Summary
better-helperjs Vulnerable to Directory Traversal via String Prefix Bypass in Static Server
Details

Summary

A directory traversal vulnerability exists in the production static file server of better-helperjs (<= 3.0.5). Attackers can read arbitrary files located in adjacent directory structures that share the same string prefix as the intended static root directory.

Details

The framework utilizes a custom static file server engine used when running in NODE_ENV=production (src/ssr/site-server.ts). Inside the safeStaticPath() method, the requested path is checked against the static root directory to prevent directory traversal out of the designated public folder.

However, the validation uses String.prototype.startsWith():

  const root = path.resolve(rootDir);
  const resolved = path.resolve(root, `.${decodedPath}`);

  if (!resolved.startsWith(root)) {
    return null;
  }

This is logically flawed because startsWith evaluates plain strings rather than structural directory paths. If the application's assigned rootDir is /app/dist/client, and an attacker tries to access /app/dist/client-secrets/database.sqlite, the string "/app/dist/client-secrets/database.sqlite" successfully starts with "/app/dist/client".

Because of this bypass, an attacker can read sensitive files stored inside any adjacent directory traversing through the parent, as long as the adjacent directory name begins with the exact same prefix as the target public directory.

Impact

  • Severity: High (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
  • Affected Versions: <= 3.0.5
  • Patched Version: >= 3.0.6

Proof of Concept

To reproduce this locally on a vulnerable installation: 1. Assume the framework static configuration serves from a .dist/client directory. 2. Build the project and create an adjacent prefix secret simulating sensitive deployment structure: bash npm run build mkdir -p dist/client-secrets echo "EXPOSED_SECRET" > dist/client-secrets/secret.txt 3. Start the application in Production mode (NODE_ENV=production tsx server.ts). 4. Using an HTTP client that doesn't pre-normalize /../ paths (e.g., netcat or raw sockets), send a GET request spanning into the prefix-sharing adjacent folder: http GET /%2e%2e%2fclient-secrets/secret.txt HTTP/1.1 Host: localhost:4174 Connection: close 5. The server incorrectly validates the path and responds with HTTP/1.1 200 OK exposing EXPOSED_SECRET.

(Note: Developer environment npm run dev servers are immune as static requests are handled defensively by Vite's Dev Middlewares. This vulnerability only triggers upon production starts).

Remediation / Patches

The path validation block has been updated to mandate exact path separation bounds by enforcing path.sep onto the evaluated traversal string.

  // Enforces separator OR exact root matches preventing prefix extension
  if (!resolved.startsWith(root + path.sep) && resolved !== root) {
    return null;
  }

Workarounds

If upgrading is temporarily impossible, users can safeguard their environment by ensuring no sensitive directories are deployed adjacent to their static build client directories sharing the identical word-prefix string.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.0.5"
      },
      "package": {
        "ecosystem": "npm",
        "name": "better-helperjs"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.0.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-26T20:56:26Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\nA directory traversal vulnerability exists in the production static file server of `better-helperjs` (`\u003c= 3.0.5`). Attackers can read arbitrary files located in adjacent directory structures that share the same string prefix as the intended static root directory.\n\n## Details\nThe framework utilizes a custom static file server engine used when running in `NODE_ENV=production` (`src/ssr/site-server.ts`). Inside the `safeStaticPath()` method, the requested path is checked against the static root directory to prevent directory traversal out of the designated public folder.\n\nHowever, the validation uses `String.prototype.startsWith()`:\n```typescript\n  const root = path.resolve(rootDir);\n  const resolved = path.resolve(root, `.${decodedPath}`);\n  \n  if (!resolved.startsWith(root)) {\n    return null;\n  }\n```\nThis is logically flawed because `startsWith` evaluates plain strings rather than structural directory paths. If the application\u0027s assigned `rootDir` is `/app/dist/client`, and an attacker tries to access `/app/dist/client-secrets/database.sqlite`, the string `\"/app/dist/client-secrets/database.sqlite\"` successfully **starts with** `\"/app/dist/client\"`. \n\nBecause of this bypass, an attacker can read sensitive files stored inside any adjacent directory traversing through the parent, as long as the adjacent directory name begins with the exact same prefix as the target public directory.\n\n## Impact\n- **Severity:** High (7.5)\n- **Vector:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`\n- **Affected Versions:** `\u003c= 3.0.5`\n- **Patched Version:** `\u003e= 3.0.6`\n\n## Proof of Concept\nTo reproduce this locally on a vulnerable installation:\n1. Assume the framework static configuration serves from a `.dist/client` directory.\n2. Build the project and create an adjacent prefix secret simulating sensitive deployment structure:\n   ```bash\n   npm run build\n   mkdir -p dist/client-secrets\n   echo \"EXPOSED_SECRET\" \u003e dist/client-secrets/secret.txt\n   ```\n3. Start the application in Production mode (`NODE_ENV=production tsx server.ts`).\n4. Using an HTTP client that doesn\u0027t pre-normalize `/../` paths (e.g., `netcat` or raw sockets), send a GET request spanning into the prefix-sharing adjacent folder:\n   ```http\n   GET /%2e%2e%2fclient-secrets/secret.txt HTTP/1.1\n   Host: localhost:4174\n   Connection: close\n   ```\n5. The server incorrectly validates the path and responds with `HTTP/1.1 200 OK` exposing `EXPOSED_SECRET`.\n\n*(Note: Developer environment `npm run dev` servers are immune as static requests are handled defensively by Vite\u0027s Dev Middlewares. This vulnerability only triggers upon `production` starts).*\n\n## Remediation / Patches\nThe path validation block has been updated to mandate exact path separation bounds by enforcing `path.sep` onto the evaluated traversal string.\n```typescript\n  // Enforces separator OR exact root matches preventing prefix extension\n  if (!resolved.startsWith(root + path.sep) \u0026\u0026 resolved !== root) {\n    return null;\n  }\n```\n\n## Workarounds\nIf upgrading is temporarily impossible, users can safeguard their environment by ensuring no sensitive directories are deployed adjacent to their static build `client` directories sharing the identical word-prefix string.",
  "id": "GHSA-3p34-w4f6-5xh2",
  "modified": "2026-06-26T20:56:26Z",
  "published": "2026-06-26T20:56:26Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Rigby-Foundation/BetterHelperjs/security/advisories/GHSA-3p34-w4f6-5xh2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Rigby-Foundation/BetterHelperjs"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "better-helperjs Vulnerable to Directory Traversal via String Prefix Bypass in Static Server"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

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.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
How durations are calculated

Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.

Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.

Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.