| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
`urlPath.slice(1)` only stripped one leading slash, so `//about.php` became `/about.php`, which `path.resolve(dataRoot, ...)` then treated as absolute, escaped dataRoot, and tripped the path-traversal guard with a 500 "Path traversal detected" instead of resolving cleanly to a 404. 100% of 5xx in production logs (~2,130/week on melvin.me) were these double-slash bot probes — `//about.php`, `//cgi-bin/...`, etc. Replacing slice(1) with replace(/^\\/+/, '') normalizes any number of leading slashes; the path then resolves to dataRoot/foo, the file doesn't exist, and the existing 404 path takes over. Security guard preserved: paths with internal '..' that escape after normalization (e.g. /../etc/passwd → ../etc/passwd → /etc/passwd absolute residue) still trip the traversal guard, tested explicitly.
There was a problem hiding this comment.
This PR fixes an edge case in URL-to-filesystem-path normalization where multiple leading slashes (e.g. //about.php) could cause path.resolve() to treat the URL path as absolute, triggering the path traversal guard and returning 500 instead of the expected 404.
Changes:
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/utils/url.js | Adjusts URL path normalization to remove multiple leading slashes before path.resolve(). |
| test/url.test.js | Adds regression tests for //... bot-probe paths and traversal guard behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sorry, something went wrong.
| // multi-slash stripping these used to escape dataRoot via path.resolve | ||
| // (which treats `/foo` as absolute) and 500 with "Path traversal detected" | ||
| // instead of the expected 404. | ||
| const dataRoot = path.resolve('./data'); |
There was a problem hiding this comment.
Sorry, something went wrong.
…rder flakiness (PR #360 round 1) Caught by Copilot: other test suites mutate process.env.DATA_ROOT via createServer's root option without always restoring it. The new #131 block hardcoded path.resolve('./data') in its assertions, so a preceding suite leaving DATA_ROOT set would make these tests fail under different run orders. Same pattern as test/idp-change-password.test.js — save in before(), restore in after().
…ckend (#373) (#374) * git handler: collapse multi-slash in URL before forwarding to http-backend (#373) git http-backend rejects PATH_INFO with `//` as "aliased" and JSS was forwarding the urlPath unchanged, returning a 500 instead of routing to the repo. Same shape as #131 (LDP path); the fix in #360 only covered src/utils/url.js. The git handler builds urlPath independently and never got the same treatment. Verified with a local boot test: GET /repo.git/info/refs, /repo.git//info/refs, and ///repo.git//info/refs all return 200 with application/x-git-upload-pack-advertisement. Frontends (jss.live/git/) producing `${repo}/info/refs` from a trailing-slash repo URL, and bot probes hitting ///wp-admin/..., both now resolve cleanly. * git handler: also catch malformed percent-encoding (don't 500) Adjacent to the multi-slash fix: decodeURIComponent throws URIError on malformed inputs like `%g1`, truncated `%E0%`, or a lone `%` — common in bot traffic. Fastify surfaces the throw as 500. Wrap in try/catch and return 400. Verified locally: malformed `%g1`, `%E0%`, and lone `%` all now return HTTP 400 (was 500); good URLs (single + double slash) still return 200. Per Copilot review on #374. * git handler: drop dead URIError catch, add CORS to remaining 4xx paths Per Copilot follow-up review on #374: - Drops the try/catch around decodeURIComponent. Empirically verified that current Fastify rejects every malformed percent-encoding (`%g1`, truncated `%E0%`, `%C3%28`, `%FF%FE`, `%80`) with 400 FST_ERR_BAD_URL at the URL parser, before this handler runs. The catch was dead code. Comment now records that finding. - Extracts setGitCorsHeaders() helper and applies it to the reachable 4xx returns (extractRepoPath 400, traversal 403, missing-repo 404). Without these headers, browser git clients (e.g. jss.live/git/) see a generic CORS/network error instead of the actual status — undermining the CORS work in #371. Note: Fastify's own 400 (FST_ERR_BAD_URL) still doesn't carry CORS headers, since it short-circuits before our handler. That's a broader issue across all routes, separate from #373. * git handler: GIT_CORS_HEADERS const, drop dead null-check Per Copilot pass on #374: - Centralizes CORS header values in GIT_CORS_HEADERS const. Both the setGitCorsHeaders() Fastify-reply helper and the streaming success path's reply.raw.setHeader loop iterate over the same map. Future updates (allowed methods/headers) live in one place — drift risk gone. - Drops the unreachable `if (!repoRelative)` check. extractRepoPath always returns a non-empty string ('.' for root); JSDoc updated to match the actual contract. Regression set still passes: 200 single, 200 double-slash, 404 missing repo with CORS, OPTIONS preflight with CORS.
| Back | FazBrowse Home | New Git URL |
Closes #131.
Problem
URLs with multiple leading slashes (typical bot probes — `//about.php`, `//wp-admin/...`, `//cgi-bin/...`) returned 500 Internal Server Error instead of 404. On production, this was 100% of 5xx traffic (~2,130/week on melvin.me), inflating monitoring noise without representing real errors.
Reproduce against any current JSS:
```
$ curl -s https://solid.social//about.php
{"statusCode":500,"error":"Internal Server Error","message":"Path traversal detected"}
```
Root cause
In `src/utils/url.js`:
```js
let normalized = urlPath.startsWith('/') ? urlPath.slice(1) : urlPath;
```
`slice(1)` only strips one leading slash. With input `//about.php`:
The traversal guard fires not because traversal occurred, but because the absolute residue escaped `dataRoot` accidentally.
Fix
Replace `slice(1)` with `replace(/^\/+/, '')` in both `urlToPath` and `urlToPathWithPod`. Strips any number of leading slashes; `//about.php` → `about.php` → resolves to `dataRoot/about.php` → file doesn't exist → existing 404 path takes over.
Security
Real `..` traversal is still rejected. The fix only changes what happens to leading slashes — internal `..` segments still strip down to absolute residues that the guard catches. Tested explicitly:
```js
it('still rejects real `..` traversal that escapes after normalization', () => {
assert.throws(() => urlToPath('/../etc/passwd'), /Path traversal/);
});
```
Test plan
6 new unit tests in `test/url.test.js` covering:
Deploy / verify
After merge + 0.0.167 publish + solid.social upgrade:
```
$ curl -s -o /dev/null -w "%{http_code}\n" https://solid.social//about.php
404
```