Security fixes pass #2
This commit is contained in:
+14
-3
@@ -8,6 +8,8 @@ import handleBunextPublicAssets from "./handle-bunext-public-assets";
|
||||
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
|
||||
import { AppData } from "../../data/app-data";
|
||||
import fullRebuild from "./full-rebuild";
|
||||
const HMR_RETRY_COOLDOWN_MS = 5000;
|
||||
let lastHmrRetryTime = 0;
|
||||
export default async function bunextRequestHandler({ req: initial_req, server, }) {
|
||||
const is_dev = isDevelopment();
|
||||
let req = initial_req.clone();
|
||||
@@ -30,6 +32,11 @@ export default async function bunextRequestHandler({ req: initial_req, server, }
|
||||
}
|
||||
}
|
||||
if (is_dev && url.pathname == AppData["BunextHMRRetryRoute"]) {
|
||||
const now = Date.now();
|
||||
if (now - lastHmrRetryTime < HMR_RETRY_COOLDOWN_MS) {
|
||||
return new Response("Too Many Requests", { status: 429 });
|
||||
}
|
||||
lastHmrRetryTime = now;
|
||||
await fullRebuild({ msg: `HMR Retry Rebuild ...` });
|
||||
return new Response("Modules Rebuilt");
|
||||
}
|
||||
@@ -60,8 +67,12 @@ export default async function bunextRequestHandler({ req: initial_req, server, }
|
||||
return response;
|
||||
}
|
||||
catch (error) {
|
||||
return new Response(`Server Error: ${error.message}`, {
|
||||
status: 500,
|
||||
});
|
||||
if (is_dev) {
|
||||
return new Response(`Server Error: ${error.message}`, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
console.error(`Server Error: ${error.message}`, error);
|
||||
return new Response("Internal Server Error", { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,13 +2,14 @@ import grabDirNames from "../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import isDevelopment from "../../utils/is-development";
|
||||
import { readFileResponse } from "./handle-public";
|
||||
import isSafePath from "../../utils/is-safe-path";
|
||||
const { BUNEXT_PUBLIC_DIR } = grabDirNames();
|
||||
export default async function ({ req }) {
|
||||
try {
|
||||
const is_dev = isDevelopment();
|
||||
const url = new URL(req.url);
|
||||
const file_path = path.join(BUNEXT_PUBLIC_DIR, url.pathname.replace(/\/\.bunext\/public\//, ""));
|
||||
if (!file_path.startsWith(BUNEXT_PUBLIC_DIR + path.sep)) {
|
||||
if (!isSafePath({ filePath: file_path, allowedDir: BUNEXT_PUBLIC_DIR })) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
return readFileResponse({
|
||||
|
||||
+7
-2
@@ -2,13 +2,14 @@ import grabDirNames from "../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import isDevelopment from "../../utils/is-development";
|
||||
import { existsSync } from "fs";
|
||||
import isSafePath from "../../utils/is-safe-path";
|
||||
const { PUBLIC_DIR } = grabDirNames();
|
||||
export default async function ({ req }) {
|
||||
try {
|
||||
const is_dev = isDevelopment();
|
||||
const url = new URL(req.url);
|
||||
const file_path = path.join(PUBLIC_DIR, url.pathname);
|
||||
if (!file_path.startsWith(PUBLIC_DIR + path.sep)) {
|
||||
if (!isSafePath({ filePath: file_path, allowedDir: PUBLIC_DIR })) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
if (!existsSync(file_path)) {
|
||||
@@ -17,7 +18,11 @@ export default async function ({ req }) {
|
||||
});
|
||||
}
|
||||
const file = Bun.file(file_path);
|
||||
return new Response(file);
|
||||
const headers = new Headers();
|
||||
if (!is_dev) {
|
||||
headers.set("Cache-Control", "public, max-age=3600");
|
||||
}
|
||||
return new Response(file, { headers });
|
||||
}
|
||||
catch (error) {
|
||||
return new Response(`File Not Found`, {
|
||||
|
||||
Vendored
+19
-6
@@ -1,5 +1,21 @@
|
||||
function removeController(controller) {
|
||||
const idx = global.HMR_CONTROLLERS.findIndex((c) => c.controller == controller);
|
||||
if (typeof idx == "number" && idx >= 0) {
|
||||
global.HMR_CONTROLLERS.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
export default async function ({ req }) {
|
||||
const referer_url = new URL(req.headers.get("referer") || "");
|
||||
const referer = req.headers.get("referer");
|
||||
if (!referer) {
|
||||
return new Response("Missing Referer Header", { status: 400 });
|
||||
}
|
||||
let referer_url;
|
||||
try {
|
||||
referer_url = new URL(referer);
|
||||
}
|
||||
catch {
|
||||
return new Response("Invalid Referer Header", { status: 400 });
|
||||
}
|
||||
const match = global.ROUTER.match(referer_url.pathname);
|
||||
const target_map = match?.filePath
|
||||
? global.BUNDLER_CTX_MAP?.[match.filePath]
|
||||
@@ -20,16 +36,13 @@ export default async function ({ req }) {
|
||||
}
|
||||
catch {
|
||||
clearInterval(heartbeat);
|
||||
removeController(controller);
|
||||
}
|
||||
}, 5000);
|
||||
},
|
||||
cancel() {
|
||||
clearInterval(heartbeat);
|
||||
const targetControllerIndex = global.HMR_CONTROLLERS.findIndex((c) => c.controller == controller);
|
||||
if (typeof targetControllerIndex == "number" &&
|
||||
targetControllerIndex >= 0) {
|
||||
global.HMR_CONTROLLERS.splice(targetControllerIndex, 1);
|
||||
}
|
||||
removeController(controller);
|
||||
},
|
||||
});
|
||||
return new Response(stream, {
|
||||
|
||||
+5
-1
@@ -2,13 +2,14 @@ import grabDirNames from "../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import isDevelopment from "../../utils/is-development";
|
||||
import { existsSync } from "fs";
|
||||
import isSafePath from "../../utils/is-safe-path";
|
||||
const { PUBLIC_DIR } = grabDirNames();
|
||||
export default async function ({ req }) {
|
||||
try {
|
||||
const is_dev = isDevelopment();
|
||||
const url = new URL(req.url);
|
||||
const file_path = path.join(PUBLIC_DIR, url.pathname.replace(/^\/public/, ""));
|
||||
if (!file_path.startsWith(PUBLIC_DIR + path.sep)) {
|
||||
if (!isSafePath({ filePath: file_path, allowedDir: PUBLIC_DIR })) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
return readFileResponse({ file_path });
|
||||
@@ -33,6 +34,9 @@ export function readFileResponse({ file_path, cache }) {
|
||||
else if (cache?.duration) {
|
||||
headers.set("Cache-Control", `public, max-age=${cache.duration}`);
|
||||
}
|
||||
else if (!isDevelopment()) {
|
||||
headers.set("Cache-Control", "public, max-age=3600");
|
||||
}
|
||||
return new Response(file, {
|
||||
headers,
|
||||
});
|
||||
|
||||
+19
-3
@@ -41,12 +41,13 @@ export default async function ({ req }) {
|
||||
module = await import(import_path);
|
||||
}
|
||||
const config = module.config;
|
||||
const maxBodyBytes = config?.max_request_body_mb
|
||||
? config.max_request_body_mb * MBInBytes
|
||||
: ServerDefaultRequestBodyLimitBytes;
|
||||
const contentLength = req.headers.get("content-length");
|
||||
if (contentLength) {
|
||||
const size = parseInt(contentLength, 10);
|
||||
if ((config?.max_request_body_mb &&
|
||||
size > config.max_request_body_mb * MBInBytes) ||
|
||||
size > ServerDefaultRequestBodyLimitBytes) {
|
||||
if (size > maxBodyBytes) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
msg: "Request Body Too Large!",
|
||||
@@ -58,6 +59,21 @@ export default async function ({ req }) {
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (req.method !== "GET" && req.method !== "HEAD") {
|
||||
const body = await req.arrayBuffer();
|
||||
if (body.byteLength > maxBodyBytes) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
msg: "Request Body Too Large!",
|
||||
}, {
|
||||
status: 413,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
routeParams.body = JSON.parse(new TextDecoder().decode(body) || "{}");
|
||||
}
|
||||
const target_module = (module["default"] ||
|
||||
module["handler"]);
|
||||
const res = await target_module?.({
|
||||
|
||||
+2
-2
@@ -30,8 +30,8 @@ export default async function serverPostBuildFn(params) {
|
||||
controller.controller.enqueue(reload_enqueue);
|
||||
continue;
|
||||
}
|
||||
const mock_req = target_artifact.req
|
||||
? target_artifact.req.clone()
|
||||
const mock_req = target_artifact.req_url
|
||||
? new Request(target_artifact.req_url)
|
||||
: new Request(controller.page_url);
|
||||
const page_component = global.IS_SERVER_COMPONENT
|
||||
? await grabPageComponent({
|
||||
|
||||
+14
-9
@@ -76,15 +76,20 @@ export default async function genWebHTML({ component: Main, pageProps, bundledMa
|
||||
console.error = () => { };
|
||||
console.info = () => { };
|
||||
console.debug = () => { };
|
||||
const stream = await renderToReadableStream(final_component, {
|
||||
onError(error) {
|
||||
if (error.message.includes('unique "key" prop'))
|
||||
return;
|
||||
originalConsole.error(error);
|
||||
},
|
||||
});
|
||||
const htmlBody = await new Response(stream).text();
|
||||
Object.assign(console, originalConsole);
|
||||
let htmlBody;
|
||||
try {
|
||||
const stream = await renderToReadableStream(final_component, {
|
||||
onError(error) {
|
||||
if (error.message.includes('unique "key" prop'))
|
||||
return;
|
||||
originalConsole.error(error);
|
||||
},
|
||||
});
|
||||
htmlBody = await new Response(stream).text();
|
||||
}
|
||||
finally {
|
||||
Object.assign(console, originalConsole);
|
||||
}
|
||||
html += htmlBody;
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function grabPageCombinedServerRes({ file_path, debug, url,
|
||||
const page_server_ctx = global.SSR_BUNDLER_CTX_MAP[server_file_path || ""];
|
||||
const final_page_server_path = page_server_ctx?.local_path
|
||||
? path.join(ROOT_DIR, page_server_ctx.path)
|
||||
: root_server_file_path;
|
||||
: server_file_path;
|
||||
const server_module = final_page_server_path
|
||||
? await import(`${final_page_server_path}?t=${now}`)
|
||||
: undefined;
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ export default async function grabPageComponent(params) {
|
||||
}
|
||||
}
|
||||
if (req && !is_hydration) {
|
||||
global.BUNDLER_CTX_MAP[file_path].req = req;
|
||||
global.BUNDLER_CTX_MAP[file_path].req_url = req.url;
|
||||
}
|
||||
if (debug) {
|
||||
log.info(`bundledMap:`, bundledMap);
|
||||
|
||||
Reference in New Issue
Block a user