devframe 0.9.19 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/cac.mjs +1 -1
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +29 -10
- package/dist/adapters/mcp.mjs +19 -11
- package/dist/{dev-DbWecpjA.mjs → dev-BdKKOH8S.mjs} +1 -1
- package/dist/{instance-shell-370Oee9K.mjs → instance-shell-yfrM3_vK.mjs} +1 -1
- package/dist/internal/index.mjs +1 -1
- package/dist/{origin-DGDH8BI9.d.mts → origin-CY6BBHOy.d.mts} +14 -1
- package/dist/{origin-DZit9g29.mjs → origin-D_eBKEzr.mjs} +21 -1
- package/dist/rpc/transports/sse-server.mjs +1 -1
- package/dist/rpc/transports/ws-bun.mjs +1 -1
- package/dist/rpc/transports/ws-deno.mjs +1 -1
- package/dist/rpc/transports/ws-server.d.mts +1 -1
- package/dist/rpc/transports/ws-server.mjs +1 -1
- package/dist/utils/origin.d.mts +2 -2
- package/dist/utils/origin.mjs +21 -1
- package/package.json +1 -1
package/dist/adapters/cac.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as colors } from "../nostics-D0PvLZsn.mjs";
|
|
2
2
|
import { i as resolveDevServerPort, s as importRuntimeModule } from "../_shared-U8D-xzrp.mjs";
|
|
3
3
|
import { createBuild } from "./build.mjs";
|
|
4
|
-
import { t as createDevServer } from "../dev-
|
|
4
|
+
import { t as createDevServer } from "../dev-BdKKOH8S.mjs";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import cac$1 from "cac";
|
|
7
7
|
//#region src/adapters/flags.ts
|
package/dist/adapters/dev.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { i as resolveDevServerPort, o as resolveMcpConnectionMeta } from "../_shared-U8D-xzrp.mjs";
|
|
2
|
-
import { t as createDevServer } from "../dev-
|
|
2
|
+
import { t as createDevServer } from "../dev-BdKKOH8S.mjs";
|
|
3
3
|
export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as getInstanceInternals, r as initDevframe } from "../dev-
|
|
1
|
+
import { n as getInstanceInternals, r as initDevframe } from "../dev-BdKKOH8S.mjs";
|
|
2
2
|
export { getInstanceInternals, initDevframe };
|
package/dist/adapters/mcp.d.mts
CHANGED
|
@@ -63,13 +63,26 @@ interface CreateMcpFetchHandlerOptions {
|
|
|
63
63
|
*/
|
|
64
64
|
allowedOrigins?: readonly string[] | false;
|
|
65
65
|
}
|
|
66
|
+
/** Connection facts a host knows about a request beyond the `Request` itself. */
|
|
67
|
+
interface McpConnectionInfo {
|
|
68
|
+
/**
|
|
69
|
+
* The connecting peer's remote address (a node socket's `remoteAddress`),
|
|
70
|
+
* used to prove a same-machine caller when the endpoint relies on the
|
|
71
|
+
* loopback origin default with no identity check. A host that can resolve a
|
|
72
|
+
* trustworthy peer address (the h3/node mount) supplies it; when it's
|
|
73
|
+
* omitted the origin gate stays the only locality signal.
|
|
74
|
+
*/
|
|
75
|
+
remoteAddress?: string;
|
|
76
|
+
}
|
|
66
77
|
interface McpFetchHandler {
|
|
67
78
|
/**
|
|
68
79
|
* WHATWG-`fetch` handler for the MCP endpoint. Hand every method
|
|
69
80
|
* (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
|
|
70
|
-
* host's job.
|
|
81
|
+
* host's job. Pass {@link McpConnectionInfo} when the host can resolve the
|
|
82
|
+
* peer address so the default trust boundary can enforce same-machine
|
|
83
|
+
* locality.
|
|
71
84
|
*/
|
|
72
|
-
fetch: (request: Request) => Promise<Response>;
|
|
85
|
+
fetch: (request: Request, connection?: McpConnectionInfo) => Promise<Response>;
|
|
73
86
|
/** Tear down the handler (aborts in-flight exchanges, drops the change bridge). */
|
|
74
87
|
dispose: () => Promise<void>;
|
|
75
88
|
}
|
|
@@ -89,13 +102,19 @@ interface McpFetchHandler {
|
|
|
89
102
|
*
|
|
90
103
|
* The origin gate guards every request: loopback-default DNS-rebinding
|
|
91
104
|
* protection that (unlike the WS upgrade's `isAllowedOrigin`) also rejects
|
|
92
|
-
* `Origin`-less requests, so a
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
105
|
+
* `Origin`-less requests, so a browser can't reach the route across origins (a
|
|
106
|
+
* disallowed origin gets `403`). The `Origin` header is only browser hardening:
|
|
107
|
+
* a non-browser client forges it. So on the zero-config default (no widened
|
|
108
|
+
* `allowedOrigins`, no identity check) a second locality gate requires the
|
|
109
|
+
* connected peer to be loopback, proven from the host-supplied
|
|
110
|
+
* {@link McpConnectionInfo.remoteAddress} (which a client cannot forge), so the
|
|
111
|
+
* "trusts same-machine callers" default holds against a remote raw client.
|
|
112
|
+
* When same-machine isn't your trust boundary, add an identity gate
|
|
113
|
+
* ({@link CreateMcpFetchHandlerOptions.authorization}), checked after the
|
|
114
|
+
* origin gate: a bearer/callback check that proves *who* is calling (a
|
|
115
|
+
* missing/invalid credential gets `401` with a `WWW-Authenticate: Bearer`
|
|
116
|
+
* challenge), which also lifts the loopback-peer restriction for authenticated
|
|
117
|
+
* callers.
|
|
99
118
|
*/
|
|
100
119
|
export declare function createMcpFetchHandler(ctx: DevframeNodeContext, options: CreateMcpFetchHandlerOptions): McpFetchHandler;
|
|
101
120
|
//#endregion
|
|
@@ -120,4 +139,4 @@ interface MountedMcpHttp {
|
|
|
120
139
|
*/
|
|
121
140
|
export declare function mountMcpHttp(app: H3, ctx: DevframeNodeContext, path: string, options: MountMcpHttpOptions): MountedMcpHttp;
|
|
122
141
|
//#endregion
|
|
123
|
-
export type { CreateMcpFetchHandlerOptions, CreateMcpServerOptions, McpFetchHandler, McpServerHandle, MountMcpHttpOptions, MountedMcpHttp };
|
|
142
|
+
export type { CreateMcpFetchHandlerOptions, CreateMcpServerOptions, McpConnectionInfo, McpFetchHandler, McpServerHandle, MountMcpHttpOptions, MountedMcpHttp };
|
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Diagnostic } from "../nostics-D0PvLZsn.mjs";
|
|
2
2
|
import { t as DEVFRAME_EVENTS } from "../events-BV4Dj59a.mjs";
|
|
3
|
-
import { t as isAllowedOrigin } from "../origin-
|
|
3
|
+
import { n as isLoopbackAddress, t as isAllowedOrigin } from "../origin-D_eBKEzr.mjs";
|
|
4
4
|
import { r as timingSafeEqual } from "../crypto-token-TVbGGFuj.mjs";
|
|
5
5
|
import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
|
|
6
6
|
import { t as createHostContext } from "../context-BLXXjoBW.mjs";
|
|
@@ -8,7 +8,7 @@ import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
|
|
|
8
8
|
import { join } from "pathe";
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
-
import { defineHandler } from "h3";
|
|
11
|
+
import { defineHandler, getRequestIP } from "h3";
|
|
12
12
|
import { Server, createMcpHandler } from "@modelcontextprotocol/server";
|
|
13
13
|
//#region src/adapters/mcp/stringify.ts
|
|
14
14
|
/**
|
|
@@ -512,13 +512,19 @@ async function isAuthorized(req, authorization) {
|
|
|
512
512
|
*
|
|
513
513
|
* The origin gate guards every request: loopback-default DNS-rebinding
|
|
514
514
|
* protection that (unlike the WS upgrade's `isAllowedOrigin`) also rejects
|
|
515
|
-
* `Origin`-less requests, so a
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
515
|
+
* `Origin`-less requests, so a browser can't reach the route across origins (a
|
|
516
|
+
* disallowed origin gets `403`). The `Origin` header is only browser hardening:
|
|
517
|
+
* a non-browser client forges it. So on the zero-config default (no widened
|
|
518
|
+
* `allowedOrigins`, no identity check) a second locality gate requires the
|
|
519
|
+
* connected peer to be loopback, proven from the host-supplied
|
|
520
|
+
* {@link McpConnectionInfo.remoteAddress} (which a client cannot forge), so the
|
|
521
|
+
* "trusts same-machine callers" default holds against a remote raw client.
|
|
522
|
+
* When same-machine isn't your trust boundary, add an identity gate
|
|
523
|
+
* ({@link CreateMcpFetchHandlerOptions.authorization}), checked after the
|
|
524
|
+
* origin gate: a bearer/callback check that proves *who* is calling (a
|
|
525
|
+
* missing/invalid credential gets `401` with a `WWW-Authenticate: Bearer`
|
|
526
|
+
* challenge), which also lifts the loopback-peer restriction for authenticated
|
|
527
|
+
* callers.
|
|
522
528
|
*/
|
|
523
529
|
function createMcpFetchHandler(ctx, options) {
|
|
524
530
|
const allowedOrigins = options.allowedOrigins;
|
|
@@ -536,9 +542,11 @@ function createMcpFetchHandler(ctx, options) {
|
|
|
536
542
|
handler.notify.resourcesChanged();
|
|
537
543
|
}
|
|
538
544
|
});
|
|
539
|
-
|
|
545
|
+
const originOnlyDefault = allowedOrigins === void 0 && authorization === false;
|
|
546
|
+
async function handle(req, connection) {
|
|
540
547
|
const origin = req.headers.get("origin") ?? void 0;
|
|
541
548
|
if (allowedOrigins !== false && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden", { status: 403 });
|
|
549
|
+
if (originOnlyDefault && connection?.remoteAddress !== void 0 && !isLoopbackAddress(connection.remoteAddress)) return new Response("Forbidden", { status: 403 });
|
|
542
550
|
if (!await isAuthorized(req, authorization)) return new Response("Unauthorized", {
|
|
543
551
|
status: 401,
|
|
544
552
|
headers: { "WWW-Authenticate": "Bearer" }
|
|
@@ -570,7 +578,7 @@ function createMcpFetchHandler(ctx, options) {
|
|
|
570
578
|
*/
|
|
571
579
|
function mountMcpHttp(app, ctx, path, options) {
|
|
572
580
|
const handler = createMcpFetchHandler(ctx, options);
|
|
573
|
-
app.use(path, defineHandler(async (event) => respond(event, await handler.fetch(event.req))));
|
|
581
|
+
app.use(path, defineHandler(async (event) => respond(event, await handler.fetch(event.req, { remoteAddress: getRequestIP(event) }))));
|
|
574
582
|
return { dispose: handler.dispose };
|
|
575
583
|
}
|
|
576
584
|
/**
|
|
@@ -5,7 +5,7 @@ import { t as createHostContext } from "./context-BLXXjoBW.mjs";
|
|
|
5
5
|
import { t as resolveStaticAssetsSource } from "./remote-assets-D0StJVLp.mjs";
|
|
6
6
|
import { a as resolveMcpConfig, i as resolveDevServerPort, n as normalizeBasePath, r as resolveBasePath, s as importRuntimeModule, t as loadAutoMcpAdapter } from "./_shared-U8D-xzrp.mjs";
|
|
7
7
|
import { t as createH3DevframeHost } from "./host-h3-BROI6SQ4.mjs";
|
|
8
|
-
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-
|
|
8
|
+
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-yfrM3_vK.mjs";
|
|
9
9
|
import { open } from "./utils/open.mjs";
|
|
10
10
|
import { mountStaticHandler } from "./utils/serve-static.mjs";
|
|
11
11
|
import { resolve } from "pathe";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./constants.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { i as validateOriginCandidate } from "./origin-D_eBKEzr.mjs";
|
|
3
3
|
import { t as diagnostics } from "./diagnostics-DZA6KQ_Z.mjs";
|
|
4
4
|
import { t as getInternalContext } from "./context-jXO5vzjH.mjs";
|
|
5
5
|
import { createInteractiveAuth } from "./recipes/interactive-auth.mjs";
|
package/dist/internal/index.mjs
CHANGED
|
@@ -5,6 +5,6 @@ import { n as coerceAgentPositionalArgs, r as toolInputToCommandArgs, t as Devfr
|
|
|
5
5
|
import { a as resolveMcpConfig, n as normalizeBasePath, r as resolveBasePath, s as importRuntimeModule, t as loadAutoMcpAdapter } from "../_shared-U8D-xzrp.mjs";
|
|
6
6
|
import { t as createH3DevframeHost } from "../host-h3-BROI6SQ4.mjs";
|
|
7
7
|
import { i as registerDevframeInstance, n as listLiveDevframeInstances } from "../instance-registry-DCq762YJ.mjs";
|
|
8
|
-
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, r as samePath, t as createInstanceShell } from "../instance-shell-
|
|
8
|
+
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, r as samePath, t as createInstanceShell } from "../instance-shell-yfrM3_vK.mjs";
|
|
9
9
|
import { t as createContextRpcServer } from "../rpc-core-CvkU23bQ.mjs";
|
|
10
10
|
export { DevframeAgentHost, coerceAgentPositionalArgs, createContextRpcServer, createH3DevframeHost, createInstanceShell, createRpcWireCodec, diagnostics, importRuntimeModule, listLiveDevframeInstances, loadAutoMcpAdapter, normalizeBasePath, normalizeHttpServerUrl, peekRpcWireFrame, registerDevframeInstance, resolveBasePath, resolveClientAssets, resolveInstanceRegister, resolveMcpConfig, samePath, toolInputToCommandArgs };
|
|
@@ -22,6 +22,19 @@
|
|
|
22
22
|
* those DNS names.
|
|
23
23
|
*/
|
|
24
24
|
declare function isLoopbackHostname(hostname: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether `address` is a loopback peer address as reported by a socket
|
|
27
|
+
* (`net.Socket.remoteAddress`): the IPv6 loopback `::1`, an IPv4 literal in
|
|
28
|
+
* `127.0.0.0/8`, or an IPv4-mapped IPv6 form of one (`::ffff:127.0.0.1`).
|
|
29
|
+
*
|
|
30
|
+
* Unlike {@link isLoopbackHostname} this takes a raw address, not a hostname:
|
|
31
|
+
* it never accepts a `localhost`-style name (a socket peer is always a literal
|
|
32
|
+
* address) and understands the IPv4-mapped IPv6 form the OS hands back on a
|
|
33
|
+
* dual-stack listener. Used to prove a same-machine caller from the connected
|
|
34
|
+
* peer, which a client cannot forge, rather than from the `Origin` header,
|
|
35
|
+
* which it can.
|
|
36
|
+
*/
|
|
37
|
+
declare function isLoopbackAddress(address: string): boolean;
|
|
25
38
|
/**
|
|
26
39
|
* Default origin policy for a localhost dev tool: allow requests with no
|
|
27
40
|
* `Origin` header (native, non-browser clients), allow any loopback origin
|
|
@@ -42,4 +55,4 @@ declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: rea
|
|
|
42
55
|
*/
|
|
43
56
|
declare function validateOriginCandidate(candidate: string, allowedOrigins?: readonly string[]): string | undefined;
|
|
44
57
|
//#endregion
|
|
45
|
-
export {
|
|
58
|
+
export { validateOriginCandidate as i, isLoopbackAddress as n, isLoopbackHostname as r, isAllowedOrigin as t };
|
|
@@ -26,6 +26,26 @@ function isLoopbackHostname(hostname) {
|
|
|
26
26
|
if (h === "localhost" || h.endsWith(".localhost") || h === "::1") return true;
|
|
27
27
|
return isLoopbackIPv4(h);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether `address` is a loopback peer address as reported by a socket
|
|
31
|
+
* (`net.Socket.remoteAddress`): the IPv6 loopback `::1`, an IPv4 literal in
|
|
32
|
+
* `127.0.0.0/8`, or an IPv4-mapped IPv6 form of one (`::ffff:127.0.0.1`).
|
|
33
|
+
*
|
|
34
|
+
* Unlike {@link isLoopbackHostname} this takes a raw address, not a hostname:
|
|
35
|
+
* it never accepts a `localhost`-style name (a socket peer is always a literal
|
|
36
|
+
* address) and understands the IPv4-mapped IPv6 form the OS hands back on a
|
|
37
|
+
* dual-stack listener. Used to prove a same-machine caller from the connected
|
|
38
|
+
* peer, which a client cannot forge, rather than from the `Origin` header,
|
|
39
|
+
* which it can.
|
|
40
|
+
*/
|
|
41
|
+
function isLoopbackAddress(address) {
|
|
42
|
+
let h = address.trim().replace(/^\[|\]$/g, "");
|
|
43
|
+
const zone = h.indexOf("%");
|
|
44
|
+
if (zone !== -1) h = h.slice(0, zone);
|
|
45
|
+
if (h === "::1") return true;
|
|
46
|
+
const mapped = /^::ffff:(.+)$/i.exec(h);
|
|
47
|
+
return isLoopbackIPv4(mapped ? mapped[1] : h);
|
|
48
|
+
}
|
|
29
49
|
/** A canonical dotted-decimal IPv4 literal in `127.0.0.0/8`. */
|
|
30
50
|
function isLoopbackIPv4(hostname) {
|
|
31
51
|
const octets = hostname.split(".");
|
|
@@ -77,4 +97,4 @@ function validateOriginCandidate(candidate, allowedOrigins) {
|
|
|
77
97
|
if (isLoopbackHostname(url.hostname) || allowedOrigins?.includes(canonical)) return canonical;
|
|
78
98
|
}
|
|
79
99
|
//#endregion
|
|
80
|
-
export {
|
|
100
|
+
export { validateOriginCandidate as i, isLoopbackAddress as n, isLoopbackHostname as r, isAllowedOrigin as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEVFRAME_SSE_SESSION_HEADER } from "../../constants.mjs";
|
|
2
2
|
import { n as peekRpcWireFrame, t as createRpcWireCodec } from "../../wire-codec-B1W3zH3L.mjs";
|
|
3
|
-
import { t as isAllowedOrigin } from "../../origin-
|
|
3
|
+
import { t as isAllowedOrigin } from "../../origin-D_eBKEzr.mjs";
|
|
4
4
|
import { t as createRpcSessionMeta } from "../../session-DPfj6zQG.mjs";
|
|
5
5
|
//#region src/rpc/transports/sse-server.ts
|
|
6
6
|
const SSE_STREAM_HEADERS = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { a as DevframeWsPeer, i as DevframeRpcTransportKind, n as DevframeRpcConnection, r as DevframeRpcConnectionRequest, t as DevframeNodeRpcSessionMeta } from "../../session-r6PDixP6.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { r as isLoopbackHostname, t as isAllowedOrigin } from "../../origin-CY6BBHOy.mjs";
|
|
3
3
|
import { a as attachWsRpcTransport, i as WsRpcTransportOptions, n as WsOriginRegistry, o as createWsOriginRegistry, r as WsRpcTransport, s as createWsRpcPeerHooks, t as CreateWsOriginRegistryOptions } from "../../ws-server-BB8jhxeT.mjs";
|
|
4
4
|
export { CreateWsOriginRegistryOptions, type DevframeNodeRpcSessionMeta, type DevframeRpcConnection, type DevframeRpcConnectionRequest, type DevframeRpcTransportKind, type DevframeWsPeer, WsOriginRegistry, WsRpcTransport, WsRpcTransportOptions, attachWsRpcTransport, createWsOriginRegistry, createWsRpcPeerHooks, isAllowedOrigin, isLoopbackHostname };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../../constants.mjs";
|
|
2
2
|
import { t as createRpcWireCodec } from "../../wire-codec-B1W3zH3L.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { r as isLoopbackHostname, t as isAllowedOrigin } from "../../origin-D_eBKEzr.mjs";
|
|
4
4
|
import { t as createRpcSessionMeta } from "../../session-DPfj6zQG.mjs";
|
|
5
5
|
import { n as randomToken, r as timingSafeEqual } from "../../crypto-token-TVbGGFuj.mjs";
|
|
6
6
|
import { createServer } from "node:http";
|
package/dist/utils/origin.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
export { isAllowedOrigin, isLoopbackHostname, validateOriginCandidate };
|
|
1
|
+
import { i as validateOriginCandidate, n as isLoopbackAddress, r as isLoopbackHostname, t as isAllowedOrigin } from "../origin-CY6BBHOy.mjs";
|
|
2
|
+
export { isAllowedOrigin, isLoopbackAddress, isLoopbackHostname, validateOriginCandidate };
|
package/dist/utils/origin.mjs
CHANGED
|
@@ -26,6 +26,26 @@ function isLoopbackHostname(hostname) {
|
|
|
26
26
|
if (h === "localhost" || h.endsWith(".localhost") || h === "::1") return true;
|
|
27
27
|
return isLoopbackIPv4(h);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether `address` is a loopback peer address as reported by a socket
|
|
31
|
+
* (`net.Socket.remoteAddress`): the IPv6 loopback `::1`, an IPv4 literal in
|
|
32
|
+
* `127.0.0.0/8`, or an IPv4-mapped IPv6 form of one (`::ffff:127.0.0.1`).
|
|
33
|
+
*
|
|
34
|
+
* Unlike {@link isLoopbackHostname} this takes a raw address, not a hostname:
|
|
35
|
+
* it never accepts a `localhost`-style name (a socket peer is always a literal
|
|
36
|
+
* address) and understands the IPv4-mapped IPv6 form the OS hands back on a
|
|
37
|
+
* dual-stack listener. Used to prove a same-machine caller from the connected
|
|
38
|
+
* peer, which a client cannot forge, rather than from the `Origin` header,
|
|
39
|
+
* which it can.
|
|
40
|
+
*/
|
|
41
|
+
function isLoopbackAddress(address) {
|
|
42
|
+
let h = address.trim().replace(/^\[|\]$/g, "");
|
|
43
|
+
const zone = h.indexOf("%");
|
|
44
|
+
if (zone !== -1) h = h.slice(0, zone);
|
|
45
|
+
if (h === "::1") return true;
|
|
46
|
+
const mapped = /^::ffff:(.+)$/i.exec(h);
|
|
47
|
+
return isLoopbackIPv4(mapped ? mapped[1] : h);
|
|
48
|
+
}
|
|
29
49
|
/** A canonical dotted-decimal IPv4 literal in `127.0.0.0/8`. */
|
|
30
50
|
function isLoopbackIPv4(hostname) {
|
|
31
51
|
const octets = hostname.split(".");
|
|
@@ -77,4 +97,4 @@ function validateOriginCandidate(candidate, allowedOrigins) {
|
|
|
77
97
|
if (isLoopbackHostname(url.hostname) || allowedOrigins?.includes(canonical)) return canonical;
|
|
78
98
|
}
|
|
79
99
|
//#endregion
|
|
80
|
-
export { isAllowedOrigin, isLoopbackHostname, validateOriginCandidate };
|
|
100
|
+
export { isAllowedOrigin, isLoopbackAddress, isLoopbackHostname, validateOriginCandidate };
|
package/package.json
CHANGED