mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-21 05:16:20 -07:00
fix(stats): restrict local requests and serve the dashboard over HTTP (#263)
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
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();
|
||||
};
|
||||
Reference in New Issue
Block a user