mirror of
https://github.com/devkitshq/notifkit.git
synced 2026-10-19 18:24:52 +02:00
fix(api): prevent path prefix traversal in admin static handler
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { resolve, extname, join } from "node:path";
|
||||
import { resolve, extname, join, relative, isAbsolute } from "node:path";
|
||||
import { existsSync, statSync, createReadStream } from "node:fs";
|
||||
import { request as httpRequest } from "node:http";
|
||||
|
||||
@@ -155,7 +155,8 @@ export async function handleAdminRequest(
|
||||
let filePath = resolve(dashboardDir, subPath);
|
||||
|
||||
// Security check: ensure filePath is inside dashboardDir
|
||||
if (!filePath.startsWith(dashboardDir)) {
|
||||
const rel = relative(dashboardDir, filePath);
|
||||
if (rel.startsWith("..") || isAbsolute(rel)) {
|
||||
res.writeHead(403, { "Content-Type": "text/plain" });
|
||||
res.end("Forbidden");
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect, vi, beforeAll, afterAll } from "vitest";
|
||||
import { handleAdminRequest } from "@/services/api/admin-static.js";
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
function createMockReq(url: string, method = "GET") {
|
||||
return {
|
||||
@@ -39,6 +41,19 @@ function createMockRes() {
|
||||
}
|
||||
|
||||
describe("Admin Static Server", () => {
|
||||
const testDir = resolve(process.cwd(), "dist", "admin");
|
||||
|
||||
beforeAll(() => {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
writeFileSync(resolve(testDir, "index.html"), "<html>Admin</html>", "utf8");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("ignores non-admin requests", async () => {
|
||||
const req = createMockReq("/v1/notify");
|
||||
const res = createMockRes();
|
||||
@@ -67,4 +82,15 @@ describe("Admin Static Server", () => {
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
it("blocks directory traversal attempts outside dashboardDir", async () => {
|
||||
const req = createMockReq("/admin/assets/../../admin_secret/config.json");
|
||||
const res = createMockRes();
|
||||
const url = new URL("http://localhost:3000/admin/assets/../../admin_secret/config.json");
|
||||
Object.defineProperty(url, "pathname", { value: "/admin/../admin_secret/config.json" });
|
||||
|
||||
const handled = await handleAdminRequest(req, res, url);
|
||||
expect(handled).toBe(true);
|
||||
expect(res.writeHead).toHaveBeenCalledWith(403, { "Content-Type": "text/plain" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user