@lensmcp/cluster 1.16.6 → 1.16.9

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.
@@ -1,8 +1,9 @@
1
1
  import type { AuthVerdict, GatewayRuntime, GwTrace, Route } from './types';
2
2
  import type { Observability } from './observability';
3
+ import type { JwtVerifier } from '../jwks-verify';
3
4
  export interface EdgeAuth {
4
5
  internal(req: import('node:http').IncomingMessage, route: Route, trace: GwTrace | undefined): AuthVerdict;
5
6
  public(req: import('node:http').IncomingMessage, route: Route, trace: GwTrace | undefined): AuthVerdict;
6
7
  }
7
- export declare function createEdgeAuth(rt: GatewayRuntime, obs: Observability): EdgeAuth;
8
+ export declare function createEdgeAuth(rt: GatewayRuntime, obs: Observability, verifier?: JwtVerifier): EdgeAuth;
8
9
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/auth.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;IAC1G,MAAM,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;CACzG;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,QAAQ,CA8C/E"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/auth.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;IAC1G,MAAM,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;CACzG;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,QAAQ,CA8CvG"}
@@ -14,7 +14,7 @@ exports.createEdgeAuth = createEdgeAuth;
14
14
  */
15
15
  const manifest_1 = require("../manifest");
16
16
  const dev_auth_1 = require("./dev-auth");
17
- function createEdgeAuth(rt, obs) {
17
+ function createEdgeAuth(rt, obs, verifier) {
18
18
  const { traceStep } = obs;
19
19
  const internal = (req, _route, trace) => {
20
20
  // service-to-service: validate the CALLER's api key at the boundary
@@ -37,7 +37,7 @@ function createEdgeAuth(rt, obs) {
37
37
  const method = (req.method ?? 'GET').toUpperCase();
38
38
  const resolved = (0, manifest_1.authRuleFor)(policy, url0, method);
39
39
  if (resolved.mode === 'jwt') {
40
- const claims = (0, dev_auth_1.verifyDevJwt)(req);
40
+ const claims = (0, dev_auth_1.verifyDevJwt)(req, verifier);
41
41
  if (!claims) {
42
42
  traceStep(trace, 'auth', { mode: 'jwt', ok: false });
43
43
  return { ok: false, status: 401, reason: 'unauthorized', detail: 'Unauthorized: invalid or missing token' };
@@ -1,7 +1,14 @@
1
1
  import type * as http from 'node:http';
2
2
  import type { Attrs } from './types';
3
- /** Verify the end-user JWT and return its claims, or undefined. */
4
- export declare function verifyDevJwt(req: http.IncomingMessage): Record<string, unknown> | undefined;
3
+ import type { JwtVerifier } from '../jwks-verify';
4
+ /**
5
+ * Verify the end-user JWT and return its claims, or undefined.
6
+ *
7
+ * @param verifier when provided (the RS256/JWKS verifier wired by the composition root), it is the ONLY
8
+ * acceptor — the token is verified against the issuer's published keys by `kid`, so a rogue key / unknown
9
+ * kid / bad signature / `alg:none` / HS256-confusion / expired token all return undefined (fail closed).
10
+ */
11
+ export declare function verifyDevJwt(req: http.IncomingMessage, verifier?: JwtVerifier): Record<string, unknown> | undefined;
5
12
  /** Stamp verified identity onto the forwarded request — tenant from the `tid` CLAIM, never a client header. */
6
13
  export declare function stampIdentity(req: http.IncomingMessage, claims: Record<string, unknown>): void;
7
14
  /** Build the ABAC attribute bag from the verified claims + request envelope. */
@@ -1 +1 @@
1
- {"version":3,"file":"dev-auth.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/dev-auth.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAiC3F;AAED,+GAA+G;AAC/G,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAQ9F;AAED,gFAAgF;AAChF,eAAO,MAAM,QAAQ,GAAI,KAAK,IAAI,CAAC,eAAe,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,KAKpF,CAAC"}
1
+ {"version":3,"file":"dev-auth.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/dev-auth.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AASlD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAyCnH;AAED,+GAA+G;AAC/G,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAQ9F;AAED,gFAAgF;AAChF,eAAO,MAAM,QAAQ,GAAI,KAAK,IAAI,CAAC,eAAe,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,KAKpF,CAAC"}
@@ -4,25 +4,45 @@ exports.devAttrs = void 0;
4
4
  exports.verifyDevJwt = verifyDevJwt;
5
5
  exports.stampIdentity = stampIdentity;
6
6
  /**
7
- * Dev edge auth (HS256), mirroring the prod gateway: verify the end-user token, then stamp the
8
- * trusted identity headers the services read (via GatewayTrustGuard). RS256/JWKS is prod's job;
9
- * dev shares the `AUTH_JWT_SECRET` the auth host signs with. Pure functions.
7
+ * Dev edge auth: verify the end-user token, then stamp the trusted identity headers the services read
8
+ * (via GatewayTrustGuard). It MIRRORS the prod gateway's posture — a real JWKS/RS256 verifier when one is
9
+ * wired (see `../jwks-verify` + the composition in `server.ts`), an HS256 shared-secret path as the legacy
10
+ * fallback. Pure functions (the verifier is injected, so unit tests supply their own JWKS).
11
+ *
12
+ * SECURITY (the invariant this file must hold): a `jwt` route MUST NOT accept a token whose signature the
13
+ * edge cannot verify. When a JWKS verifier is wired it is the SOLE acceptor — every alg goes through it, so
14
+ * an unknown `kid`, a bad signature, `alg:none`, an HS256-confusion token, and an expired token ALL fail
15
+ * closed (rejected). Without a verifier, an asymmetric (RS256/ES*) token cannot be checked (we don't hold
16
+ * the issuer's public keys), so it also fails closed — unless an operator EXPLICITLY opts into the old
17
+ * insecure decode-only path (`LENSMCP_GW_DEV_JWT_INSECURE_DECODE=1`) for local bring-up. The HS256
18
+ * shared-secret path stays for services using that convention (and is itself bypassed once JWKS is wired).
10
19
  */
11
20
  const node_crypto_1 = require("node:crypto");
12
- /** Verify the end-user JWT and return its claims, or undefined. */
13
- function verifyDevJwt(req) {
14
- const secret = process.env['AUTH_JWT_SECRET'] ?? 'tetros-dev-secret';
15
- const auth = String((Array.isArray(req.headers['authorization']) ? req.headers['authorization'][0] : req.headers['authorization']) ?? '');
16
- if (!auth.startsWith('Bearer '))
21
+ /** The compact JWS from the `Authorization: Bearer …` header, or undefined. */
22
+ function bearerToken(req) {
23
+ const raw = Array.isArray(req.headers['authorization']) ? req.headers['authorization'][0] : req.headers['authorization'];
24
+ const auth = String(raw ?? '');
25
+ return auth.startsWith('Bearer ') ? auth.slice(7) : undefined;
26
+ }
27
+ /**
28
+ * Verify the end-user JWT and return its claims, or undefined.
29
+ *
30
+ * @param verifier when provided (the RS256/JWKS verifier wired by the composition root), it is the ONLY
31
+ * acceptor — the token is verified against the issuer's published keys by `kid`, so a rogue key / unknown
32
+ * kid / bad signature / `alg:none` / HS256-confusion / expired token all return undefined (fail closed).
33
+ */
34
+ function verifyDevJwt(req, verifier) {
35
+ const token = bearerToken(req);
36
+ if (!token)
17
37
  return undefined;
18
- const [h, p, s] = auth.slice(7).split('.');
38
+ const [h, p, s] = token.split('.');
19
39
  if (!h || !p || !s)
20
40
  return undefined;
21
- // An IdP that signs ASYMMETRIC (RS256 + JWKS — e.g. tetros's auth host) does NOT use the HS256 dev
22
- // shared-secret this dev edge assumed. The PROD gateway verifies RS256 via jwks-verify; in dev we
23
- // DECODE + exp-check the claims (no signature check) — the gateway is the only local ingress, the token
24
- // came from the trusted local IdP, and a dev IdP's ephemeral per-pod keys make in-cluster JWKS
25
- // verification unreliable. (The HS256 path below stays for services using the shared-secret convention.)
41
+ // Preferred: a real JWKS/RS256 verifier (mirrors the prod gateway's `jwks ? jwks.verify(tok) : hs256`).
42
+ // When wired it is the SOLE acceptor, so every forgery vector below is closed at once.
43
+ if (verifier)
44
+ return verifier.verify(token);
45
+ // ── No verifier wired (legacy dev) ──────────────────────────────────────────────────────────────────
26
46
  let alg;
27
47
  try {
28
48
  alg = JSON.parse(Buffer.from(h, 'base64url').toString())['alg'];
@@ -31,6 +51,13 @@ function verifyDevJwt(req) {
31
51
  return undefined;
32
52
  }
33
53
  if (typeof alg === 'string' && alg !== 'HS256') {
54
+ // An asymmetric IdP (RS256 + JWKS) does NOT use the HS256 dev shared secret. Without the issuer's public
55
+ // keys we CANNOT verify the signature, so — unlike the old code, which decode-accepted any RS256 token
56
+ // (a full edge auth bypass: a rogue key or a forged `alg:none` sailed through) — we FAIL CLOSED. Wire a
57
+ // JWKS verifier (LENSMCP_GW_JWKS_URL, or the auto-derived auth-host JWKS) to accept RS256 tokens.
58
+ if (process.env['LENSMCP_GW_DEV_JWT_INSECURE_DECODE'] !== '1')
59
+ return undefined; // fail closed
60
+ // Explicit, dev-only opt-in to the legacy decode + exp-check (NO signature check) — bring-up escape hatch.
34
61
  try {
35
62
  const rsClaims = JSON.parse(Buffer.from(p, 'base64url').toString());
36
63
  if (typeof rsClaims['exp'] === 'number' && Date.now() / 1000 > rsClaims['exp'])
@@ -41,6 +68,8 @@ function verifyDevJwt(req) {
41
68
  return undefined;
42
69
  }
43
70
  }
71
+ // HS256 shared-secret convention (only reachable when NO JWKS verifier is wired).
72
+ const secret = process.env['AUTH_JWT_SECRET'] ?? 'tetros-dev-secret';
44
73
  const expected = (0, node_crypto_1.createHmac)('sha256', secret).update(`${h}.${p}`).digest('base64url');
45
74
  const a = Buffer.from(s);
46
75
  const b = Buffer.from(expected);
@@ -10,7 +10,7 @@ export declare function lensPortFromUpstream(upstream?: string): number | undefi
10
10
  */
11
11
  export declare function discoverRoutes(rootDir: string, projects: Record<string, {
12
12
  root: string;
13
- }>, sockBaseDir?: string): {
13
+ }>, sockBaseDir?: string, wsKey?: string): {
14
14
  routes: Route[];
15
15
  services: ServiceCtl[];
16
16
  };
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/discovery.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAoC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7F,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,SAAO,GAAG,MAAM,GAAG,SAAS,CAa7E;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1C,WAAW,SAAc,GACxB;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,CAyD7C"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/discovery.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAoC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7F,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,SAAO,GAAG,MAAM,GAAG,SAAS,CAa7E;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1C,WAAW,SAAc,EAIzB,KAAK,SAAK,GACT;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,CA2D7C"}
@@ -43,7 +43,12 @@ function lensPortFromUpstream(upstream) {
43
43
  * the route table: host routes first (plus auto `internal.<host>` for pod
44
44
  * services), the `default` catch-all last.
45
45
  */
46
- function discoverRoutes(rootDir, projects, sockBaseDir = os.tmpdir()) {
46
+ function discoverRoutes(rootDir, projects, sockBaseDir = os.tmpdir(),
47
+ // The workspace key namespaces the pod sock dir (planning/multi-workspace-gateway.md) — MUST match
48
+ // `main.devserver.ts`'s `SOCK_DIR` derivation so the gateway scans the same dir the devserver writes.
49
+ // Empty → the legacy un-namespaced path (single-workspace / pre-multi-workspace).
50
+ wsKey = '') {
51
+ const sockDirFor = (service) => wsKey ? path.join(sockBaseDir, wsKey, `${service}-devserver`) : path.join(sockBaseDir, `${service}-devserver`);
47
52
  const routes = [];
48
53
  const services = [];
49
54
  let defaultRoute;
@@ -67,7 +72,7 @@ function discoverRoutes(rootDir, projects, sockBaseDir = os.tmpdir()) {
67
72
  // instrumented by the serve executor's node-instrumentation graft.
68
73
  if (decl.lens)
69
74
  console.warn(`[gateway] ${name}: cluster.lens is ignored for pod (service) decls — backends are instrumented by serve-hmr.`);
70
- pool = { dir: path.join(sockBaseDir, `${decl.service}-devserver`), socks: [], idx: -1, scannedAt: 0 };
75
+ pool = { dir: sockDirFor(decl.service), socks: [], idx: -1, scannedAt: 0 };
71
76
  svc = { project: name, decl, pool, state: 'down', lastUsed: Date.now(), inflight: 0, lastScaleAt: 0 };
72
77
  services.push(svc);
73
78
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lifecycle.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAapE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA4CrD;;sGAEsG;AACtG,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,oBAAoB,CAAC,EACpG,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,gBAAgB,GACpB,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CA2PvF"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lifecycle.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAapE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA4CrD;;sGAEsG;AACtG,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,oBAAoB,CAAC,EACpG,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,gBAAgB,GACpB,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CA6PvF"}
@@ -162,7 +162,9 @@ function createServiceLayer(rt, obs) {
162
162
  // "idle-kill leaves live pods" leak. (--childCount as a CLI arg already
163
163
  // avoids the env-loss the daemon was relied on for.) NX_DAEMON=false also
164
164
  // means env vars DO reach the pod, so the east-west key + CA propagate.
165
- env: { ...process.env, NX_DAEMON: 'false', ...eastWestEnv(rt, svc.project) },
165
+ // LENSMCP_WS_KEY namespaces this service's pod sock dir ($TMPDIR/<wsKey>/<service>-devserver) so a
166
+ // shared multi-workspace gateway's same-named services don't collide (planning/multi-workspace-gateway.md).
167
+ env: { ...process.env, NX_DAEMON: 'false', ...(rt.wsKey ? { LENSMCP_WS_KEY: rt.wsKey } : {}), ...eastWestEnv(rt, svc.project) },
166
168
  stdio: ['ignore', 'pipe', 'pipe'],
167
169
  });
168
170
  svc.lastErrors = [];
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/server.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAkB,qBAAqB,EAAe,MAAM,SAAS,CAAC;AAEjH,wBAAsB,YAAY,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAkKxB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/server.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAkB,qBAAqB,EAAsB,MAAM,SAAS,CAAC;AAkDxH,wBAAsB,YAAY,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAgLxB"}
@@ -15,6 +15,7 @@ const tslib_1 = require("tslib");
15
15
  const http = tslib_1.__importStar(require("node:http"));
16
16
  const path = tslib_1.__importStar(require("node:path"));
17
17
  const discovery_1 = require("./discovery");
18
+ const scope_1 = require("./scope");
18
19
  const service_keys_1 = require("./service-keys");
19
20
  const observability_1 = require("./observability");
20
21
  const lifecycle_1 = require("./lifecycle");
@@ -24,6 +25,57 @@ const auth_1 = require("./auth");
24
25
  const hooks_1 = require("./hooks");
25
26
  const handler_1 = require("./handler");
26
27
  const upgrade_1 = require("./upgrade");
28
+ const jwks_verify_1 = require("../jwks-verify");
29
+ /**
30
+ * A `fetch`-shaped shim for the JWKS pull that accepts the dev gateway's OWN self-signed cert. The dev
31
+ * IdP's JWKS is served behind the same local HTTPS front door (e.g. `https://auth.tetros.ai.local`), whose
32
+ * cert chains to the local basic-ssl CA — global `fetch` would reject it. Read-only GET of a public,
33
+ * gateway-served JWKS document; the SIGNATURE trust comes from verifying tokens against those keys, not
34
+ * from the transport. Falls back to plain `http` for an http URL. Dev-only (only the dev executor uses it).
35
+ */
36
+ function insecureLocalFetch(url, init) {
37
+ return new Promise((resolve, reject) => {
38
+ const mod = url.startsWith('https:') ? require('node:https') : require('node:http');
39
+ const req = mod.request(url, { method: 'GET', headers: init?.headers, rejectUnauthorized: false }, (res) => {
40
+ const chunks = [];
41
+ res.on('data', (c) => chunks.push(c));
42
+ res.on('end', () => {
43
+ const status = res.statusCode ?? 0;
44
+ const text = Buffer.concat(chunks).toString('utf8');
45
+ resolve({ ok: status >= 200 && status < 300, status, json: async () => JSON.parse(text) });
46
+ });
47
+ });
48
+ req.on('error', reject);
49
+ req.end();
50
+ });
51
+ }
52
+ /**
53
+ * The dev edge's JWT verifier — matches the prod gateway's RS256/JWKS posture so the LOCAL edge rejects
54
+ * forged tokens instead of decode-accepting them. The JWKS URL is `LENSMCP_GW_JWKS_URL` (explicit, same
55
+ * env as prod) or, absent that, is AUTO-DERIVED from the trusted `auth.*` host in the gateway's own route
56
+ * table (`https://<auth-host>/jwks.json`) — so a standard `auth.<apex>` IdP needs zero config. The host is
57
+ * taken from project.json (trusted), never from a token, so there's no SSRF/attacker-chosen JWKS. Returns
58
+ * undefined when no source is found (→ non-HS256 tokens fail closed unless the insecure opt-in is set).
59
+ */
60
+ function buildEdgeJwtVerifier(routes) {
61
+ const explicit = process.env['LENSMCP_GW_JWKS_URL'];
62
+ let jwksUrl = explicit;
63
+ let derived = false;
64
+ if (!jwksUrl) {
65
+ const authHost = routes
66
+ .map((r) => r.host)
67
+ .find((h) => typeof h === 'string' && /^auth\./.test(h) && !h.startsWith('internal.'));
68
+ if (authHost) {
69
+ jwksUrl = `https://${authHost}/jwks.json`;
70
+ derived = true;
71
+ }
72
+ }
73
+ if (!jwksUrl)
74
+ return undefined;
75
+ const issuer = process.env['LENSMCP_GW_JWT_ISS'];
76
+ console.log(`[gateway] edge JWT: verifying via JWKS ${jwksUrl}${derived ? ' (auto-derived from the auth route)' : ''}`);
77
+ return (0, jwks_verify_1.createJwksVerifier)({ jwksUrl, ...(issuer ? { issuer } : {}), fetchImpl: insecureLocalFetch });
78
+ }
27
79
  async function startGateway(options, context) {
28
80
  // NB: this local `https` (TLS-on flag) deliberately SHADOWS the `node:https`
29
81
  // module — which is why TLS/agents are reached via `require('node:https')`.
@@ -39,7 +91,12 @@ async function startGateway(options, context) {
39
91
  // The gateway is a first-class lens source — the shared event file the MCP tails.
40
92
  const eventFile = process.env['LENSMCP_EVENT_FILE'] ?? path.join(context.root, '.lensmcp', 'events.jsonl');
41
93
  // --- discover ---------------------------------------------------------------
42
- const { routes, services } = (0, discovery_1.discoverRoutes)(context.root, context.projectsConfigurations?.projects ?? {});
94
+ // The workspace key namespaces this workspace's pod sock dirs ($TMPDIR/<wsKey>/<service>-devserver) so a
95
+ // SHARED multi-workspace gateway can host several workspaces without their same-named services colliding
96
+ // (planning/multi-workspace-gateway.md). It flows to each service's devserver via `LENSMCP_WS_KEY` (see
97
+ // lifecycle.ts), and `discoverRoutes` derives the matching pool dir the gateway scans.
98
+ const wsKey = (0, scope_1.readLensScope)(context.root).key;
99
+ const { routes, services } = (0, discovery_1.discoverRoutes)(context.root, context.projectsConfigurations?.projects ?? {}, undefined, wsKey);
43
100
  if (routes.length === 0) {
44
101
  throw new Error('[gateway] no project declares a `cluster` field — nothing to route.');
45
102
  }
@@ -48,6 +105,7 @@ async function startGateway(options, context) {
48
105
  // --- the shared runtime (state + knobs; stopped() is a live getter) ---------
49
106
  const rt = {
50
107
  root: context.root,
108
+ wsKey,
51
109
  eventFile,
52
110
  routes,
53
111
  services,
@@ -65,7 +123,15 @@ async function startGateway(options, context) {
65
123
  const svcLayer = (0, lifecycle_1.createServiceLayer)(rt, obs);
66
124
  const lens = (0, lens_children_1.createLensChildren)(rt, obs, options);
67
125
  const proxyLayer = (0, proxy_1.createProxy)(rt, obs, svcLayer);
68
- const edgeAuth = (0, auth_1.createEdgeAuth)(rt, obs);
126
+ // Edge JWT verification (mirrors the prod gateway): when a JWKS source is configured/derivable, the dev
127
+ // edge verifies RS256 tokens by `kid` against the IdP's published keys and REJECTS anything it can't
128
+ // verify (rogue kid / bad sig / alg:none / HS256-confusion / expired) — closing the dev decode-only bypass.
129
+ const jwtVerifier = buildEdgeJwtVerifier(routes);
130
+ const edgeAuth = (0, auth_1.createEdgeAuth)(rt, obs, jwtVerifier);
131
+ // Warm the key cache (non-fatal, non-blocking): the IdP pod may still be booting, so a failure here just
132
+ // means the first tokens fail closed until the on-miss/periodic refresh lands the keys (matches prod).
133
+ if (jwtVerifier)
134
+ void jwtVerifier.refresh().catch((e) => console.warn('[gateway] edge JWKS warm failed (will retry):', e.message));
69
135
  // Custom user middleware (the JWT seam; internal.* routes skip it) is wired as
70
136
  // the BUILT-IN FIRST `afterAuth` hook — so "custom middleware runs AFTER core
71
137
  // auth" and the `{ mode:'middleware' }` trace step are preserved.
@@ -167,6 +233,7 @@ async function startGateway(options, context) {
167
233
  clearInterval(edgeFlusher);
168
234
  clearInterval(sweeper);
169
235
  obs.emit('info', 'gateway down', 'cluster-gateway', { kind: 'gateway-down' });
236
+ jwtVerifier?.stop(); // clear the JWKS periodic-refresh timer
170
237
  for (const svc of services)
171
238
  svcLayer.killSpawned(svc, 'gateway shutdown');
172
239
  // Reap the lens-mode managed children (dashboard + MCP singletons + each
@@ -237,6 +237,10 @@ export interface GwTrace {
237
237
  */
238
238
  export interface GatewayRuntime {
239
239
  readonly root: string;
240
+ /** The per-workspace key (from `readLensScope`) — namespaces this workspace's pod sock dirs so a shared
241
+ * multi-workspace gateway can host several workspaces without same-named services colliding. Flows to
242
+ * each service devserver as `LENSMCP_WS_KEY`. */
243
+ readonly wsKey: string;
240
244
  readonly eventFile: string;
241
245
  readonly routes: Route[];
242
246
  readonly services: ServiceCtl[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/types.ts"],"names":[],"mappings":"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"}
package/main.devserver.js CHANGED
@@ -109,7 +109,17 @@ const WORKER_NAMES = (() => {
109
109
  }
110
110
  })();
111
111
  // ----------------------------- Socket Helpers ---------------------------------
112
- const SOCK_DIR = path.join(os.tmpdir(), `${SERVICE_NAME || 'lensmcp-cluster'}-devserver`);
112
+ // The pod sockets live under a per-WORKSPACE dir when the gateway sets `LENSMCP_WS_KEY` (the shared
113
+ // multi-workspace gateway — planning/multi-workspace-gateway.md): `$TMPDIR/<wsKey>/<service>-devserver`.
114
+ // Without a key (standalone `serve`, or a single-workspace gateway that predates this) it's the legacy
115
+ // `$TMPDIR/<service>-devserver`. Namespacing is REQUIRED so two workspaces each running a service named
116
+ // `agent`/`auth` don't share `$TMPDIR/agent-devserver` — which would cross-route the gateway into the wrong
117
+ // workspace's pods and let one workspace's reap unlink the other's sockets. The gateway's `discovery.ts`
118
+ // mirror MUST derive the identical path.
119
+ const LENSMCP_WS_KEY = process.env.LENSMCP_WS_KEY || '';
120
+ const SOCK_DIR = LENSMCP_WS_KEY
121
+ ? path.join(os.tmpdir(), LENSMCP_WS_KEY, `${SERVICE_NAME || 'lensmcp-cluster'}-devserver`)
122
+ : path.join(os.tmpdir(), `${SERVICE_NAME || 'lensmcp-cluster'}-devserver`);
113
123
  function childSockPath(id) {
114
124
  return path.join(SOCK_DIR, `child-${id}.sock`);
115
125
  }
@@ -692,6 +702,21 @@ else {
692
702
  proxy.ws(req, socket, head, { target: { socketPath: targetChild.socketPath } }, () => socket.destroy());
693
703
  };
694
704
  server.on('upgrade', upgradeHandler);
705
+ // The public TCP port is VESTIGIAL under the gateway: the gateway routes to this devserver over the
706
+ // unix-socket POOL ($TMPDIR/<service>-devserver/child-*.sock), never the TCP port. So a port clash must
707
+ // NOT crash the devserver — without this handler an EADDRINUSE (two services falling back to the same
708
+ // default port, or a not-yet-released port from a prior instance) throws an UNHANDLED 'error' that kills
709
+ // the parent → the supervisors respawn it → the old port is still held → EADDRINUSE again → a zombie
710
+ // devserver cascade. Handle it: log once and keep serving on the socket pool (gateway routing is
711
+ // unaffected). A distinct `cluster.port` / `config.<env>.yaml` port silences the warning.
712
+ server.on('error', (err) => {
713
+ if (err.code === 'EADDRINUSE') {
714
+ console.warn(`[parent] public port ${publicPort} already in use — continuing on the unix-socket pool only ` +
715
+ `(gateway routing is unaffected). Give this service a distinct cluster.port to silence this.`);
716
+ return; // do NOT rethrow: the socket pool is the real routing path under the gateway.
717
+ }
718
+ console.error(`[parent] public server error:`, err);
719
+ });
695
720
  server.listen(publicPort, async () => {
696
721
  const scheme = GATEWAY_HTTPS ? 'https' : 'http';
697
722
  console.log(`[parent] Listening on ${scheme}://localhost:${publicPort}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.16.6",
3
+ "version": "1.16.9",
4
4
  "description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -65,8 +65,8 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@lensmcp/node-instrumentation": "1.16.6",
69
- "@lensmcp/nx-plugin": "1.16.6",
68
+ "@lensmcp/node-instrumentation": "1.16.9",
69
+ "@lensmcp/nx-plugin": "1.16.9",
70
70
  "fork-ts-checker-webpack-plugin": "^9.0.0",
71
71
  "glob": "^11.0.0",
72
72
  "http-proxy": "^1.18.0",