-
Notifications
You must be signed in to change notification settings - Fork 0
fix(backend): prevent static file path traversal #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { describe, expect, test } from "bun:test"; | ||
| import { resolveStaticPath } from "./staticFile"; | ||
|
|
||
| describe("resolveStaticPath", () => { | ||
| test("resolves request paths inside the static root", () => { | ||
| expect(resolveStaticPath("/app/public", "/assets/main.js")).toBe( | ||
| "/app/public/assets/main.js", | ||
| ); | ||
| }); | ||
|
|
||
| test("allows the static root itself", () => { | ||
| expect(resolveStaticPath("/app/public", "/")).toBe("/app/public"); | ||
| }); | ||
|
|
||
| test("blocks parent-directory traversal", () => { | ||
| expect(resolveStaticPath("/app/public", "/../../etc/passwd")).toBeNull(); | ||
| }); | ||
|
|
||
| test("blocks traversal without a leading slash", () => { | ||
| expect(resolveStaticPath("/app/public", "../../etc/passwd")).toBeNull(); | ||
| }); | ||
|
|
||
| test("normalizes dot segments that stay inside the root", () => { | ||
| expect(resolveStaticPath("/app/public", "/assets/../index.html")).toBe( | ||
| "/app/public/index.html", | ||
| ); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { isAbsolute, relative, resolve } from "node:path"; | ||
|
|
||
| export function resolveStaticPath( | ||
| rootDir: string, | ||
| requestPath: string, | ||
| ): string | null { | ||
| const resolvedRoot = resolve(rootDir); | ||
| const normalizedRequestPath = requestPath.replace(/^\/+/u, ""); | ||
| const resolvedPath = resolve(resolvedRoot, normalizedRequestPath); | ||
| const relativePath = relative(resolvedRoot, resolvedPath); | ||
|
|
||
| if (relativePath.startsWith("..") || isAbsolute(relativePath)) { | ||
| return null; | ||
| } | ||
|
|
||
| return resolvedPath; | ||
| } | ||
|
Comment on lines
+3
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check Elysia's path handling behavior and whether it URL-decodes paths
# Search for Elysia documentation or tests about path parameter behavior
rg -n -C3 'path.*decode' --type=ts
# Check if there's any URL decoding in the codebase for request paths
rg -n 'decodeURI' --type=tsRepository: EM-GeekLab/NexusGate Length of output: 355 🌐 Web query:
💡 Result: No, the Elysia framework does not automatically URL decode path parameters before passing them to route handlers. Path parameters are extracted from the raw pathname segments as received by the server (after browser/server encoding), without additional decoding by Elysia. Developers must manually decode if needed, e.g., using decodeURI in a transform hook. Citations:
对 该函数未对
修复方案: 在路径规范化前添加 URL 解码: const decodedPath = decodeURIComponent(requestPath);
const normalizedRequestPath = decodedPath.replace(/^\/+/u, "");🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For improved robustness against malformed input, consider wrapping the path resolution logic in a
try...catchblock. Functions fromnode:pathlikeresolvecan throw aTypeErrorif the path contains a null byte (\0), which is a common technique for probing for vulnerabilities. An unhandled exception would cause a 500 error. By catching this and returningnull, you can treat it as just another invalid path and return a 404, which is a more graceful and secure way to handle it.References
JSON.parseshould be wrapped in atry...catchblock to handle malformed JSON and prevent crashes, as per the rule: 'When parsing JSON from a data source, even a trusted one, wrapJSON.parsein atry...catchblock to handle potential malformed data gracefully and prevent crashes.' Applyingtry...catchtonode:path.resolvefor user-provided paths ensures similar robustness against invalid or malicious input like null bytes.