@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.
Files changed (2) hide show
  1. package/lib/auth-guard.mjs +54 -0
  2. 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.1",
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/",