validate admin form origins behind proxy chain
Build & Deploy Formulation / Build & Push Image (push) Successful in 18s
Build & Deploy Formulation / deploy (push) Successful in 41s

This commit is contained in:
nicholas committed 2026-09-03 08:16:00 -05:00
1 parent fe6846ea0f
commit 3ccbae2471
2 files changed
+18 -5

No files matched your search

+3 -5
View File
@@ -10,11 +10,9 @@ export default defineConfig({
adapter: node({ mode: "standalone" }),
integrations: [preact()],
security: {
checkOrigin: true,
allowedDomains: [
{ hostname: "recipes.uuard.com", protocol: "https" },
{ hostname: "recipes-admin.uuard.com", protocol: "https" },
],
// Origin validation is performed in application middleware because the
// edge and K3s proxy chain terminates TLS before the Node request.
checkOrigin: false,
},
vite: { server: { fs: { allow: ["."] } } },
});
+15
View File
@@ -2,9 +2,24 @@ import { defineMiddleware } from "astro:middleware";
import { readOnlyMode } from "../lib/runtime";
const safeMethods = new Set(["GET", "HEAD", "OPTIONS"]);
const formContentTypes = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
const productionWriteOrigin = "https://recipes-admin.uuard.com";
function isForbiddenFormSubmission(request: Request, url: URL) {
if (safeMethods.has(request.method)) return false;
const contentType = request.headers.get("content-type")?.toLowerCase();
if (contentType && !formContentTypes.some((value) => contentType.includes(value))) return false;
const origin = request.headers.get("origin");
if (origin === productionWriteOrigin) return false;
const localDevelopment = url.hostname === "localhost" || url.hostname === "127.0.0.1";
return !localDevelopment || origin !== url.origin;
}
export const onRequest = defineMiddleware(async ({ request, url, redirect }, next) => {
if (url.pathname === "/") return redirect("/app/", 302);
if (isForbiddenFormSubmission(request, url)) {
return new Response(`Cross-site ${request.method} form submissions are forbidden`, { status: 403 });
}
if (!readOnlyMode) return next();
if (!safeMethods.has(request.method)) {
return new Response("This Formulation instance is read-only.", { status: 405, headers: { Allow: "GET, HEAD, OPTIONS" } });