@marsnme/mcp-gateway 0.3.1 → 0.3.2
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/lib/auth-guard.mjs +54 -0
- package/package.json +2 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// MARS-422: bearer bypass guard.
|
|
2
|
+
//
|
|
3
|
+
// Before this module, the gateway decided "internal request" from the
|
|
4
|
+
// client-supplied `X-Forwarded-Host` header alone. Cloudflare forwards that header
|
|
5
|
+
// untouched, so any internet client could send `X-Forwarded-Host: 127.0.0.1` and get
|
|
6
|
+
// full unauthenticated access to coco / toto / draft.
|
|
7
|
+
//
|
|
8
|
+
// Rule now: the private bypass requires BOTH
|
|
9
|
+
// 1. no Cloudflare edge markers (cf-ray / cf-connecting-ip), and
|
|
10
|
+
// 2. a private socket peer (loopback or RFC1918 / IPv6 local).
|
|
11
|
+
// cloudflared always adds the edge markers, so public traffic can never qualify.
|
|
12
|
+
// In-process and LAN callers (dream runner, coco-memory-local over the SSH tunnel to
|
|
13
|
+
// 192.168.100.102) keep working because their socket peer is private.
|
|
14
|
+
|
|
15
|
+
const EDGE_MARKER_HEADERS = ['cf-ray', 'cf-connecting-ip'];
|
|
16
|
+
|
|
17
|
+
export function isEdgeRequest(headers = {}) {
|
|
18
|
+
return EDGE_MARKER_HEADERS.some((name) => Boolean(headers[name]));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ipv4IsPrivate(ip) {
|
|
22
|
+
const parts = ip.split('.').map((n) => Number.parseInt(n, 10));
|
|
23
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return false;
|
|
24
|
+
const [a, b] = parts;
|
|
25
|
+
if (a === 10) return true;
|
|
26
|
+
if (a === 127) return true;
|
|
27
|
+
if (a === 192 && b === 168) return true;
|
|
28
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
29
|
+
if (a === 169 && b === 254) return true; // link-local
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isPrivatePeer(remoteAddress) {
|
|
34
|
+
if (!remoteAddress) return false;
|
|
35
|
+
let addr = String(remoteAddress).trim().toLowerCase();
|
|
36
|
+
// Normalise IPv4-mapped IPv6 (::ffff:192.168.1.5).
|
|
37
|
+
const mapped = addr.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
|
|
38
|
+
if (mapped) addr = mapped[1];
|
|
39
|
+
if (addr === '::1' || addr === '::') return true;
|
|
40
|
+
if (addr.includes(':')) {
|
|
41
|
+
// IPv6 link-local (fe80::/10) and unique-local (fc00::/7).
|
|
42
|
+
return addr.startsWith('fe8') || addr.startsWith('fe9') || addr.startsWith('fea') ||
|
|
43
|
+
addr.startsWith('feb') || addr.startsWith('fc') || addr.startsWith('fd');
|
|
44
|
+
}
|
|
45
|
+
return ipv4IsPrivate(addr);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function shouldRequireBearer({ headers, remoteAddress, requireBearer, bypassForPrivate }) {
|
|
49
|
+
if (!requireBearer) return false;
|
|
50
|
+
if (bypassForPrivate && !isEdgeRequest(headers) && isPrivatePeer(remoteAddress)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marsnme/mcp-gateway",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"mcpName": "io.github.Marsmanleo/marsnme",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Agent-agnostic, LLM-agnostic memory backend for MCP-compatible tools",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"marsnme": "server.mjs"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
|
+
"lib/",
|
|
36
37
|
"server.mjs",
|
|
37
38
|
"scripts/",
|
|
38
39
|
"deploy/phase2/",
|