mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
fix(server): apply the Host check before the app's own routes
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]>
This commit is contained in:
1 parent
81513f9aae
commit
e6f1dd70a1
6 files changed
+203
-5
No files matched your search
@@ -1,7 +1,11 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { request as httpRequest } from 'node:http';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
DEFAULT_ALLOWED_HOSTS,
|
||||
hostCheckPlugin,
|
||||
isAllowedHost,
|
||||
resolveAllowedHosts,
|
||||
} from '../../build/allowedHosts.js';
|
||||
|
||||
@@ -32,3 +36,118 @@ test('rejects suffix and wildcard entries so every LAN hostname is explicit', ()
|
||||
'globe.lan',
|
||||
]);
|
||||
});
|
||||
|
||||
test('a Host is allowed by the same rule Vite applies', () => {
|
||||
const allowed = ['localhost', '127.0.0.1', 'globe.lan', '.corp.example'];
|
||||
for (const host of [
|
||||
'localhost:4173',
|
||||
'LOCALHOST:4173',
|
||||
'app.localhost',
|
||||
'127.0.0.1:4173',
|
||||
'192.168.1.20:4173',
|
||||
'[::1]:4173',
|
||||
'globe.lan:4173',
|
||||
'corp.example',
|
||||
'a.corp.example:8080',
|
||||
'office:4173',
|
||||
])
|
||||
assert.equal(isAllowedHost(host, allowed, ['office']), true, host);
|
||||
for (const host of [
|
||||
undefined,
|
||||
'',
|
||||
'evil.example:4173',
|
||||
'globe.lan.evil.example',
|
||||
'localhost.evil.example',
|
||||
'mybox.local:4173',
|
||||
'[not-an-ip]:4173',
|
||||
])
|
||||
assert.equal(isAllowedHost(host, allowed, ['office']), false, String(host));
|
||||
assert.equal(isAllowedHost('anything.example', true), true);
|
||||
});
|
||||
|
||||
test('the Host check is installed first on the dev and preview servers', () => {
|
||||
const plugin = hostCheckPlugin();
|
||||
assert.equal(plugin.enforce, 'pre');
|
||||
for (const hook of ['configureServer', 'configurePreviewServer']) {
|
||||
let middleware;
|
||||
plugin[hook]({
|
||||
config: {
|
||||
server: { allowedHosts: ['localhost'], host: 'localhost' },
|
||||
preview: { allowedHosts: ['localhost'], host: undefined },
|
||||
},
|
||||
middlewares: { use: (fn) => (middleware = fn) },
|
||||
});
|
||||
const respond = (host) => {
|
||||
const res = {
|
||||
status: null,
|
||||
writeHead(status) {
|
||||
this.status = status;
|
||||
},
|
||||
end() {},
|
||||
};
|
||||
let passed = false;
|
||||
middleware({ headers: { host } }, res, () => (passed = true));
|
||||
return passed ? 'next' : res.status;
|
||||
};
|
||||
assert.equal(respond('localhost:4173'), 'next', hook);
|
||||
assert.equal(respond('evil.example:4173'), 403, hook);
|
||||
}
|
||||
});
|
||||
|
||||
test('an unlisted Host cannot reach a route a plugin mounts', async (t) => {
|
||||
const { createServer } = await import('vite');
|
||||
const { createBrowserViteConfig } = await import('../../build/vite.js');
|
||||
const provider = {
|
||||
name: 'fixture-provider',
|
||||
configureServer(server) {
|
||||
server.middlewares.use('/api/fixture', (req, res) => {
|
||||
res.end('provider answered');
|
||||
});
|
||||
},
|
||||
};
|
||||
const base = createBrowserViteConfig({ plugins: [provider] });
|
||||
const vite = await createServer({
|
||||
...base,
|
||||
root: fileURLToPath(new URL('../../', import.meta.url)),
|
||||
configFile: false,
|
||||
envFile: false,
|
||||
publicDir: false,
|
||||
logLevel: 'silent',
|
||||
// Only this test's plugins: the Host check and the fixture provider.
|
||||
plugins: base.plugins.filter((plugin) =>
|
||||
['host-check', 'fixture-provider'].includes(plugin?.name),
|
||||
),
|
||||
optimizeDeps: { noDiscovery: true, include: [] },
|
||||
server: {
|
||||
...base.server,
|
||||
host: '127.0.0.1',
|
||||
port: 0,
|
||||
hmr: false,
|
||||
watch: null,
|
||||
},
|
||||
});
|
||||
await vite.listen();
|
||||
t.after(() => vite.close());
|
||||
const { port } = vite.httpServer.address();
|
||||
const get = (host) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = httpRequest(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
path: '/api/fixture',
|
||||
headers: { Host: host },
|
||||
},
|
||||
(response) => {
|
||||
response.resume();
|
||||
resolve(response.statusCode);
|
||||
},
|
||||
);
|
||||
request.on('error', reject);
|
||||
request.end();
|
||||
});
|
||||
assert.equal(await get(`localhost:${port}`), 200);
|
||||
assert.equal(await get(`127.0.0.1:${port}`), 200);
|
||||
assert.equal(await get(`evil.example:${port}`), 403);
|
||||
assert.equal(await get(`mybox.local:${port}`), 403);
|
||||
});
|
||||
Reference in new issue
Block a user