mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-21 05:16:20 -07:00
43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import type { MiddlewareHandler } from 'hono';
|
|
|
|
function isLoopbackUrl(url: URL): boolean {
|
|
return (
|
|
url.protocol === 'http:' &&
|
|
!url.username &&
|
|
!url.password &&
|
|
['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)
|
|
);
|
|
}
|
|
|
|
/** Protect the local API even when a browser can reach the loopback listener. */
|
|
export const enforceStatsRequestSafety: MiddlewareHandler = async (c, next) => {
|
|
const url = new URL(c.req.url);
|
|
if (!isLoopbackUrl(url)) return c.body(null, 403);
|
|
|
|
const host = c.req.header('host');
|
|
if (host !== undefined) {
|
|
if (!/^(localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(host)) {
|
|
return c.body(null, 403);
|
|
}
|
|
// Node derives the request URL from Host; Bun provides them independently.
|
|
try {
|
|
if (new URL(`http://${host}`).origin !== url.origin) return c.body(null, 403);
|
|
} catch {
|
|
return c.body(null, 403);
|
|
}
|
|
}
|
|
|
|
// Compare the serialized origin exactly. Opaque origins and malformed values
|
|
// containing credentials, paths, or multiple origins must not gain trust.
|
|
const origin = c.req.header('origin');
|
|
if (origin !== undefined && origin !== url.origin) return c.body(null, 403);
|
|
const site = c.req.header('sec-fetch-site');
|
|
if (site === 'cross-site' || site === 'same-site') return c.body(null, 403);
|
|
|
|
if (!['GET', 'HEAD', 'OPTIONS'].includes(c.req.method) && c.req.raw.body !== null) {
|
|
const contentType = c.req.header('content-type')?.split(';', 1)[0]?.trim().toLowerCase();
|
|
if (contentType !== 'application/json') return c.body(null, 415);
|
|
}
|
|
await next();
|
|
};
|