mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Vite installs its Host check after the middleware plugins add in configureServer, so the provider and /api routes answered any Host, including a DNS-rebinding name. hostCheckPlugin runs first on the dev and preview servers and applies Vite's rule with the same allowed hosts: IP addresses, localhost and *.localhost, the configured server host, and the listed names. A test drives a real Vite server with an unlisted Host. Co-Authored-By: Claude Opus 5.5 <[email protected]>
95 lines
3.5 KiB
JavaScript
95 lines
3.5 KiB
JavaScript
/**
|
|
* Host header allowlist for the Vite dev/preview server. The configuration
|
|
* factory passes the configured names in; this module reads no environment.
|
|
*
|
|
* Binding to a wildcard interface only controls which network interfaces accept
|
|
* connections. It must not turn into a wildcard Host-header policy: this server
|
|
* brokers configured API keys through local proxy routes.
|
|
*/
|
|
|
|
import { isIP } from 'node:net';
|
|
|
|
export const DEFAULT_ALLOWED_HOSTS = Object.freeze(['localhost', '127.0.0.1']);
|
|
|
|
/**
|
|
* Resolve Vite's allowedHosts setting from an optional comma-separated list.
|
|
* Empty entries and suffix/wildcard entries are ignored; repeated names keep
|
|
* their first occurrence. Vite treats leading-dot values as suffix wildcards,
|
|
* which would defeat an explicit host policy.
|
|
*
|
|
* @param {string|undefined|null} configuredHosts
|
|
* @returns {string[]}
|
|
*/
|
|
export function resolveAllowedHosts(configuredHosts) {
|
|
const hosts = String(configuredHosts ?? '')
|
|
.split(',')
|
|
.map((host) => host.trim())
|
|
.filter((host) => host && !host.startsWith('.') && !host.includes('*'));
|
|
return [...new Set([...DEFAULT_ALLOWED_HOSTS, ...hosts])];
|
|
}
|
|
|
|
/**
|
|
* Whether a Host header names a host the server answers, by Vite's own rule:
|
|
* IP addresses, `localhost` and `*.localhost` always; then `additional` names
|
|
* (the configured server host) and `allowedHosts` (a leading dot allows a
|
|
* domain and its subdomains). `allowedHosts === true` allows every host.
|
|
*
|
|
* @param {string|undefined} hostHeader
|
|
* @param {string[]|true} allowedHosts
|
|
* @param {string[]} [additional]
|
|
* @returns {boolean}
|
|
*/
|
|
export function isAllowedHost(hostHeader, allowedHosts, additional = []) {
|
|
if (allowedHosts === true) return true;
|
|
const host = String(hostHeader ?? '').trim();
|
|
if (!host) return false;
|
|
if (host[0] === '[') {
|
|
const end = host.indexOf(']');
|
|
return end > 0 && isIP(host.slice(1, end)) === 6;
|
|
}
|
|
const colon = host.indexOf(':');
|
|
const hostname = (colon === -1 ? host : host.slice(0, colon)).toLowerCase();
|
|
if (isIP(hostname) === 4) return true;
|
|
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
|
|
if (additional.includes(hostname)) return true;
|
|
return (allowedHosts || []).some(
|
|
(allowed) =>
|
|
allowed === hostname ||
|
|
(allowed[0] === '.' &&
|
|
(allowed.slice(1) === hostname || hostname.endsWith(allowed))),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Vite plugin that applies the Host check before every other middleware.
|
|
* Vite installs its own check after the middleware plugins add in
|
|
* `configureServer`, so without this the app's `/api` routes would answer any
|
|
* Host, including a DNS-rebinding name. It runs first (`enforce: 'pre'`) and
|
|
* uses the same allowed hosts as Vite, on the dev and preview servers.
|
|
*/
|
|
export function hostCheckPlugin() {
|
|
const middleware = (config, preview) => {
|
|
const allowed = preview
|
|
? config.preview.allowedHosts
|
|
: config.server.allowedHosts;
|
|
const additional = [config.server.host, config.preview.host].filter(
|
|
(value) => typeof value === 'string' && value,
|
|
);
|
|
return (req, res, next) => {
|
|
if (isAllowedHost(req.headers.host, allowed, additional)) return next();
|
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
res.end('Blocked request. This host is not allowed.');
|
|
};
|
|
};
|
|
return {
|
|
name: 'host-check',
|
|
enforce: 'pre',
|
|
configureServer(server) {
|
|
server.middlewares.use(middleware(server.config, false));
|
|
},
|
|
configurePreviewServer(server) {
|
|
server.middlewares.use(middleware(server.config, true));
|
|
},
|
|
};
|
|
}
|