mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-25 08:34:39 +00:00
fix(server-v2): treat loopback origins as same-origin for WS upgrade
A browser behind a same-machine dev proxy (e.g. Vite on localhost:5175) sends Origin localhost while the upstream server is bound to 127.0.0.1; the two are not string-equal so the WS upgrade was rejected with 403. Treat loopback<->loopback (localhost / 127.0.0.1 / [::1] / *.localhost) as same-origin. Public cross-origin is unchanged, auth (401) is unchanged.
This commit is contained in:
parent
cb7be13f9a
commit
bba9f4489f
2 changed files with 45 additions and 2 deletions
|
|
@ -78,14 +78,37 @@ export function isOriginAllowed(
|
|||
if (oh === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (host !== undefined && stripPort(oh) === stripPort(host)) {
|
||||
return true;
|
||||
const ohStripped = stripPort(oh);
|
||||
if (host !== undefined) {
|
||||
const hostStripped = stripPort(host);
|
||||
if (ohStripped === hostStripped) {
|
||||
return true;
|
||||
}
|
||||
// Dev-proxy case: a browser hitting a same-machine dev server (e.g. Vite on
|
||||
// `localhost:5175`) whose upstream server is bound to a different loopback
|
||||
// name (e.g. `127.0.0.1:58627`). The two are not string-equal, but both ends
|
||||
// are loopback, so there is no real cross-origin threat — treat as
|
||||
// same-origin so WebSocket upgrades are not rejected with 403.
|
||||
if (isLoopbackHost(ohStripped) && isLoopbackHost(hostStripped)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// `origin` is defined here (originHost returned a host), so the whitelist
|
||||
// match is against the full origin string (scheme + host).
|
||||
return allowed.includes(origin as string);
|
||||
}
|
||||
|
||||
/** Loopback-only host names, mirroring the allowlist in `hostnames.ts`. */
|
||||
function isLoopbackHost(h: string): boolean {
|
||||
return (
|
||||
h === 'localhost' ||
|
||||
h === '::1' ||
|
||||
h === '[::1]' ||
|
||||
h.startsWith('127.') ||
|
||||
h.endsWith('.localhost')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Fastify `onRequest` CORS hook.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -50,6 +50,26 @@ describe('isOriginAllowed', () => {
|
|||
it('treats a malformed origin as absent (allowed)', () => {
|
||||
expect(isOriginAllowed('not a url', 'h', [])).toBe(true);
|
||||
});
|
||||
|
||||
it('treats localhost origin vs 127.0.0.1 host as same-origin (dev proxy)', () => {
|
||||
expect(isOriginAllowed('http://localhost:5175', '127.0.0.1:58627', [])).toBe(true);
|
||||
});
|
||||
|
||||
it('treats 127.0.0.1 origin vs localhost host as same-origin', () => {
|
||||
expect(isOriginAllowed('http://127.0.0.1:5175', 'localhost:58627', [])).toBe(true);
|
||||
});
|
||||
|
||||
it('treats [::1] origin vs localhost host as same-origin (IPv6 loopback)', () => {
|
||||
expect(isOriginAllowed('http://[::1]:5175', 'localhost:58627', [])).toBe(true);
|
||||
});
|
||||
|
||||
it('still denies a non-loopback cross-origin that is not whitelisted', () => {
|
||||
expect(isOriginAllowed('http://evil.com', 'localhost:80', [])).toBe(false);
|
||||
});
|
||||
|
||||
it('does not widen to a public host even when the origin is loopback', () => {
|
||||
expect(isOriginAllowed('http://localhost:5175', 'example.com:80', [])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseCorsOrigins', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue