better-helperjs Path Traversal via String Prefix Bypass in Static Server
The production static file server in better-helperjs lets attackers read files from adjacent directories on disk by exploiting a flawed string prefix check that mistakes a shared directory name prefix
The problem
In versions up to and including 3.0.5, the `safeStaticPath()` function in `src/ssr/site-server.ts` validates a resolved file path using `resolved.startsWith(root)`. That check treats the root directory string as a plain text prefix, not a filesystem boundary.
If the static root is `/app/dist/client`, the resolved path `/app/dist/client-secrets/database.sqlite` passes the check because the string literally starts with `/app/dist/client`. An unauthenticated remote attacker can therefore read any file in a sibling directory whose name begins with the same prefix as the configured static root.
This only triggers in `NODE_ENV=production`; the Vite dev middleware used during development is not affected.
Proof of concept
GET /%2e%2e%2fclient-secrets/secret.txt HTTP/1.1
Host: localhost:4174
Connection: closeThe percent-encoded `%2e%2e%2f` (`../`) is decoded before `path.resolve()` runs, so the server resolves the request to `/app/dist/client-secrets/secret.txt`. Because that string starts with the root `/app/dist/client`, the old `startsWith(root)` guard passes and the file is served.
The patch tightens the check to `resolved.startsWith(root + path.sep) || resolved === root`. Appending `path.sep` (`/` on POSIX, `\` on Windows) means a path must start with `/app/dist/client/`, so `/app/dist/client-secrets/...` no longer matches. The root cause is CWE-22: the validator used lexical string comparison instead of canonical path boundary comparison.
The fix
Upgrade `better-helperjs` to version 3.0.6 or later. The fix is in `safeStaticPath()`: the guard is now `!resolved.startsWith(root + path.sep) && resolved !== root`. If you cannot upgrade immediately, ensure no sensitive directories exist adjacent to your static build output directory that share the same name prefix (for example, avoid having both `dist/client` and `dist/client-secrets` on the same host).