summaryrefslogtreecommitdiffhomepage
path: root/scripts/vite-network-guard.ts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/vite-network-guard.ts')
-rw-r--r--scripts/vite-network-guard.ts74
1 files changed, 74 insertions, 0 deletions
diff --git a/scripts/vite-network-guard.ts b/scripts/vite-network-guard.ts
new file mode 100644
index 0000000..7fe9eca
--- /dev/null
+++ b/scripts/vite-network-guard.ts
@@ -0,0 +1,74 @@
+import type { IncomingMessage } from "node:http";
+import type { Plugin, UserConfig } from "vite";
+import { checkDevRequest, type DevNetworkPolicy, normalizeHostHeader } from "./network-policy";
+
+/** Explicitly protect BOTH serving modes; preview must not inherit permissive CORS alone. */
+export function applyViteNetworkPolicy(config: UserConfig, network: DevNetworkPolicy): UserConfig {
+ return {
+ ...config,
+ plugins: [devNetworkGuard(network), ...(config.plugins ?? [])],
+ server: {
+ ...config.server,
+ allowedHosts: [...network.allowedHosts],
+ // Safe reflection only after our pre-middleware validates the Origin.
+ cors: { origin: true },
+ },
+ preview: {
+ ...config.preview,
+ allowedHosts: [...network.allowedHosts],
+ cors: { origin: true },
+ },
+ };
+}
+
+export function devNetworkGuard(policy: DevNetworkPolicy): Plugin {
+ const check = (req: IncomingMessage) =>
+ checkDevRequest(
+ {
+ localAddress: req.socket.localAddress,
+ host: req.headers.host,
+ origin: req.headers.origin,
+ },
+ policy,
+ );
+ // Installed before Vite's CORS, host check and static-file middleware in
+ // either mode. No post-hook: even preflights and error responses are guarded.
+ const middleware: import("vite").Connect.NextHandleFunction = (req, res, next) => {
+ const decision = check(req);
+ if (decision !== "ok") {
+ res.writeHead(decision.status, { "Content-Type": "text/plain" });
+ res.end(decision.reason);
+ return;
+ }
+ req.headers.host = normalizeHostHeader(req.headers.host ?? "");
+ next();
+ };
+ return {
+ name: "dispatch-dev-network-guard",
+ enforce: "pre",
+ configurePreviewServer(server) {
+ server.middlewares.use(middleware);
+ },
+ configureServer(server) {
+ server.middlewares.use(middleware);
+ // Vite registers HMR before configureServer. Wrap (rather than prepend to)
+ // its listeners: EventEmitter otherwise continues into HMR after a denial.
+ const http = server.httpServer;
+ if (http) {
+ const upgrades = http.listeners("upgrade");
+ http.removeAllListeners("upgrade");
+ http.on("upgrade", (req, socket, head) => {
+ const decision = check(req);
+ if (decision !== "ok") {
+ socket.end(
+ `HTTP/1.1 ${decision.status} ${decision.status === 421 ? "Misdirected Request" : "Forbidden"}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`,
+ );
+ return;
+ }
+ req.headers.host = normalizeHostHeader(req.headers.host ?? "");
+ for (const upgrade of upgrades) upgrade.call(http, req, socket, head);
+ });
+ }
+ },
+ };
+}