veryfront 0.1.80 → 0.1.82

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 (26) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/mcp/server.d.ts.map +1 -1
  3. package/esm/src/mcp/server.js +45 -9
  4. package/esm/src/proxy/handler.d.ts.map +1 -1
  5. package/esm/src/proxy/handler.js +54 -3
  6. package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -1
  7. package/esm/src/rendering/orchestrator/module-loader/index.js +7 -20
  8. package/esm/src/transforms/esm/bundle-manifest.d.ts +3 -1
  9. package/esm/src/transforms/esm/bundle-manifest.d.ts.map +1 -1
  10. package/esm/src/transforms/esm/bundle-manifest.js +2 -2
  11. package/esm/src/transforms/esm/cached-bundle-validation.d.ts +9 -0
  12. package/esm/src/transforms/esm/cached-bundle-validation.d.ts.map +1 -0
  13. package/esm/src/transforms/esm/cached-bundle-validation.js +25 -0
  14. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.d.ts.map +1 -1
  15. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.js +9 -21
  16. package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
  17. package/esm/src/transforms/pipeline/index.js +8 -24
  18. package/package.json +1 -1
  19. package/src/deno.js +1 -1
  20. package/src/src/mcp/server.ts +57 -9
  21. package/src/src/proxy/handler.ts +63 -2
  22. package/src/src/rendering/orchestrator/module-loader/index.ts +11 -19
  23. package/src/src/transforms/esm/bundle-manifest.ts +6 -3
  24. package/src/src/transforms/esm/cached-bundle-validation.ts +40 -0
  25. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts +13 -24
  26. package/src/src/transforms/pipeline/index.ts +8 -26
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAG/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAMlD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;AAOzD,UAAU,cAAc;IACtB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC,CAAC;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,iBAAiB,CAAC,CAA0B;IACpD,OAAO,CAAC,kBAAkB,CAAS;gBAEvB,MAAM,EAAE,eAAe;IAInC;;;OAGG;IACH,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI;IAK3D,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAsBhG,OAAO,CAAC,QAAQ;IA8BhB,OAAO,CAAC,UAAU;YAYJ,SAAS;IA2BvB,OAAO,CAAC,QAAQ;IAmChB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,YAAY;IA6CpB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,SAAS;IAuCjB,iBAAiB,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;IAsD5E,OAAO,CAAC,qBAAqB;YAkBf,YAAY;IAe1B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;YAYR,oBAAoB;CA0BnC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAElE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAG/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAMlD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAOzE,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;AAOzD,UAAU,cAAc;IACtB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC,CAAC;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,iBAAiB,CAAC,CAA0B;IACpD,OAAO,CAAC,kBAAkB,CAAS;gBAEvB,MAAM,EAAE,eAAe;IAQnC;;;OAGG;IACH,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI;IAK3D,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAsBhG,OAAO,CAAC,QAAQ;IA8BhB,OAAO,CAAC,UAAU;YAYJ,SAAS;IA2BvB,OAAO,CAAC,QAAQ;IAmChB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,YAAY;IA6CpB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,SAAS;IAuCjB,iBAAiB,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;IA+E5E,OAAO,CAAC,qBAAqB;YAkBf,YAAY;IAoB1B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;YAqBR,oBAAoB;CA0BnC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAElE"}
@@ -9,6 +9,9 @@ import { withSpan } from "../observability/tracing/otlp-setup.js";
9
9
  import { VERSION } from "../utils/version.js";
10
10
  import { validateContentType } from "../security/input-validation/limits.js";
11
11
  import { VeryfrontError } from "../security/input-validation/errors.js";
12
+ import { logger as baseLogger } from "../utils/index.js";
13
+ const logger = baseLogger.component("mcp-server");
14
+ const MAX_REQUEST_BODY_SIZE = 1_048_576; // 1 MB
12
15
  function asParamsRecord(params) {
13
16
  if (!params || Array.isArray(params))
14
17
  return {};
@@ -20,6 +23,9 @@ export class MCPServer {
20
23
  integrationsLoaded = false;
21
24
  constructor(config) {
22
25
  this.config = config;
26
+ if (!config.auth || config.auth.type === "none") {
27
+ logger.warn("MCP server has no authentication configured — all requests will be accepted");
28
+ }
23
29
  }
24
30
  /**
25
31
  * Configure integration tools to be lazily loaded on first tools/list call.
@@ -207,13 +213,23 @@ export class MCPServer {
207
213
  }
208
214
  createHTTPHandler() {
209
215
  return async (request) => {
216
+ const requestOrigin = request.headers.get("Origin");
210
217
  if (request.method === "OPTIONS")
211
- return this.handleCORS();
218
+ return this.handleCORS(requestOrigin);
212
219
  if (this.config.auth?.type && this.config.auth.type !== "none") {
213
220
  const authorized = await this.validateAuth(request);
214
221
  if (!authorized)
215
222
  return new dntShim.Response("Unauthorized", { status: 401 });
216
223
  }
224
+ // Enforce request body size limit (fast path via Content-Length header)
225
+ const contentLength = request.headers.get("content-length");
226
+ if (contentLength && Number(contentLength) > MAX_REQUEST_BODY_SIZE) {
227
+ return new dntShim.Response(JSON.stringify({
228
+ jsonrpc: "2.0",
229
+ id: null,
230
+ error: { code: -32600, message: "Request body too large" },
231
+ }), { status: 413, headers: { "Content-Type": "application/json" } });
232
+ }
217
233
  try {
218
234
  validateContentType(request, "application/json");
219
235
  }
@@ -227,7 +243,15 @@ export class MCPServer {
227
243
  }
228
244
  let rpcRequest;
229
245
  try {
230
- rpcRequest = await request.json();
246
+ const bodyText = await request.text();
247
+ if (bodyText.length > MAX_REQUEST_BODY_SIZE) {
248
+ return new dntShim.Response(JSON.stringify({
249
+ jsonrpc: "2.0",
250
+ id: null,
251
+ error: { code: -32600, message: "Request body too large" },
252
+ }), { status: 413, headers: { "Content-Type": "application/json" } });
253
+ }
254
+ rpcRequest = JSON.parse(bodyText);
231
255
  }
232
256
  catch (_) {
233
257
  // expected: malformed JSON in request body
@@ -246,7 +270,7 @@ export class MCPServer {
246
270
  return new dntShim.Response(JSON.stringify(rpcResponse), {
247
271
  headers: {
248
272
  "Content-Type": "application/json",
249
- ...this.getCORSHeaders(),
273
+ ...this.getCORSHeaders(requestOrigin),
250
274
  },
251
275
  });
252
276
  };
@@ -275,21 +299,33 @@ export class MCPServer {
275
299
  if (auth.type !== "bearer")
276
300
  return false;
277
301
  const token = authHeader.replace("Bearer ", "");
278
- if (!auth.validate)
302
+ // When bearer auth is configured without a validate function, reject all requests
303
+ if (!auth.validate) {
304
+ logger.warn("Bearer auth configured without validate function — rejecting request");
279
305
  return false;
306
+ }
280
307
  return await auth.validate(token);
281
308
  }
282
- handleCORS() {
283
- return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders() });
309
+ handleCORS(requestOrigin) {
310
+ return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders(requestOrigin) });
284
311
  }
285
- getCORSHeaders() {
312
+ getCORSHeaders(requestOrigin) {
286
313
  if (!this.config.cors?.enabled)
287
314
  return {};
288
- const origin = this.config.cors.origins?.[0] ?? "*";
315
+ const origins = this.config.cors.origins;
316
+ if (!origins || origins.length === 0)
317
+ return {};
318
+ // Match request origin against the configured origins list
319
+ const matchedOrigin = requestOrigin && origins.includes(requestOrigin)
320
+ ? requestOrigin
321
+ : undefined;
322
+ if (!matchedOrigin)
323
+ return {};
289
324
  return {
290
- "Access-Control-Allow-Origin": origin,
325
+ "Access-Control-Allow-Origin": matchedOrigin,
291
326
  "Access-Control-Allow-Methods": "POST, OPTIONS",
292
327
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-End-User-Id, X-Project-Id",
328
+ "Vary": "Origin",
293
329
  };
294
330
  }
295
331
  async loadIntegrationTools(config) {
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,eAAO,MAAM,sBAAsB,0MAYzB,CAAC;AA6EX,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,GAAG,YAAY,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA4CD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB;0BA6K1B,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC;0BA+NvC,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;0BAjVrD,MAAM,EAAE;;EA+XpC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAwB7F"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,eAAO,MAAM,sBAAsB,0MAYzB,CAAC;AAoGX,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,GAAG,YAAY,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA8ED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB;0BAiL1B,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC;0BA+NvC,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;0BArVrD,MAAM,EAAE;;EAmYpC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAwB7F"}
@@ -6,7 +6,7 @@ import { cwd, getEnv } from "../platform/compat/process.js";
6
6
  import { join } from "../platform/compat/path/index.js";
7
7
  import { injectContext, ProxySpanNames, withSpan } from "./tracing.js";
8
8
  import { computeContentSourceId } from "../cache/keys.js";
9
- import { jwtVerify } from "jose";
9
+ import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
10
10
  export const INTERNAL_PROXY_HEADERS = [
11
11
  "x-token",
12
12
  "x-project-slug",
@@ -20,6 +20,26 @@ export const INTERNAL_PROXY_HEADERS = [
20
20
  "x-branch-id",
21
21
  "x-branch-name",
22
22
  ];
23
+ const remoteJwksByUrl = new Map();
24
+ function getApiJwks(apiBaseUrl, logger) {
25
+ try {
26
+ const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
27
+ const jwksUrl = new URL(".well-known/jwks.json", normalizedBaseUrl);
28
+ const cacheKey = jwksUrl.toString();
29
+ let jwks = remoteJwksByUrl.get(cacheKey);
30
+ if (!jwks) {
31
+ jwks = createRemoteJWKSet(jwksUrl);
32
+ remoteJwksByUrl.set(cacheKey, jwks);
33
+ }
34
+ return jwks;
35
+ }
36
+ catch (error) {
37
+ logger?.error("Invalid API base URL for JWKS lookup", error, {
38
+ apiBaseUrl,
39
+ });
40
+ return undefined;
41
+ }
42
+ }
23
43
  async function lookupProjectByDomain(domain, apiBaseUrl, token, logger) {
24
44
  return withSpan(ProxySpanNames.PROXY_DOMAIN_LOOKUP, async () => {
25
45
  const domainWithoutPort = domain.replace(/:\d+$/, "");
@@ -70,7 +90,38 @@ function extractUserToken(cookieHeader) {
70
90
  const match = cookieHeader.match(/(?:^|;\s*)authToken=([^;]+)/);
71
91
  return match?.[1] ? decodeURIComponent(match[1]) : undefined;
72
92
  }
73
- async function extractUserIdFromToken(token, log) {
93
+ async function extractUserIdFromToken(token, apiBaseUrl, log) {
94
+ let algorithm;
95
+ try {
96
+ algorithm = decodeProtectedHeader(token).alg;
97
+ }
98
+ catch (error) {
99
+ log?.debug("Failed to decode JWT header", {
100
+ error: error instanceof Error ? error.message : String(error),
101
+ });
102
+ return undefined;
103
+ }
104
+ if (algorithm === "RS256") {
105
+ const jwks = getApiJwks(apiBaseUrl, log);
106
+ if (!jwks)
107
+ return undefined;
108
+ try {
109
+ const { payload } = await jwtVerify(token, jwks, {
110
+ algorithms: ["RS256"],
111
+ });
112
+ return payload.userId;
113
+ }
114
+ catch (error) {
115
+ log?.debug("RS256 JWT verification failed", {
116
+ error: error instanceof Error ? error.message : String(error),
117
+ });
118
+ return undefined;
119
+ }
120
+ }
121
+ if (algorithm !== "HS256") {
122
+ log?.debug("Unsupported JWT algorithm", { algorithm: algorithm ?? null });
123
+ return undefined;
124
+ }
74
125
  const jwtSecret = getEnv("JWT_SECRET");
75
126
  if (!jwtSecret) {
76
127
  log?.warn("JWT_SECRET not configured — cannot verify user token");
@@ -184,7 +235,7 @@ export function createProxyHandler(options) {
184
235
  });
185
236
  return { status: 302, message: "Authentication required", redirectUrl };
186
237
  }
187
- const userId = await extractUserIdFromToken(userToken, logger);
238
+ const userId = await extractUserIdFromToken(userToken, config.apiBaseUrl, logger);
188
239
  if (!userId) {
189
240
  const redirectUrl = makeAuthRedirectUrl(req);
190
241
  logger?.info("Could not extract userId from token", {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA6BzE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAkJpE,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,cAAc,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,UAAQ,GACtB,OAAO,CAAC,MAAM,CAAC,CAkSjB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAqClC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA2BzE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAkJpE,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,cAAc,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,UAAQ,GACtB,OAAO,CAAC,MAAM,CAAC,CA4RjB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAqClC"}
@@ -14,14 +14,12 @@ import { createFileSystem } from "../../../platform/compat/fs.js";
14
14
  import { getProjectTmpDir } from "../../../modules/react-loader/index.js";
15
15
  import { generateCacheKey as generateTransformCacheKey, getOrComputeTransform, initializeTransformCache, setCachedTransformAsync, } from "../../../transforms/esm/transform-cache.js";
16
16
  import { TRANSFORM_DISTRIBUTED_TTL_SEC } from "../../../utils/constants/cache.js";
17
- import { ensureHttpBundlesExist } from "../../../transforms/esm/http-cache.js";
18
- import { validateBundleGroup } from "../../../transforms/esm/bundle-manifest.js";
17
+ import { validateCachedBundlesByManifestOrCode } from "../../../transforms/esm/cached-bundle-validation.js";
19
18
  import { getHttpBundleCacheDir, getMdxEsmCacheDir } from "../../../utils/cache-dir.js";
20
19
  import { dirname, join, normalize } from "../../../platform/compat/path/index.js";
21
20
  import { hashCodeHex } from "../../../utils/hash-utils.js";
22
21
  import { VERSION } from "../../../utils/version.js";
23
22
  import { getModulePathCache, lookupMdxEsmCache, saveModulePathCache, } from "../../../transforms/mdx/esm-module-loader/cache/index.js";
24
- import { extractHttpBundlePaths } from "../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
25
23
  const logger = rendererLogger.component("module-loader");
26
24
  // Re-export utilities
27
25
  export { createEsmCache, createModuleCache, generateHash } from "./cache.js";
@@ -243,30 +241,19 @@ export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, co
243
241
  let transformedCode = transformResult.code;
244
242
  const cacheDir = getHttpBundleCacheDir();
245
243
  let bundlesValid = true;
246
- if (transformResult.cacheHit && transformResult.bundleManifestId) {
247
- const validation = await validateBundleGroup(transformResult.bundleManifestId, cacheDir);
244
+ if (transformResult.cacheHit) {
245
+ const validation = await validateCachedBundlesByManifestOrCode(transformedCode, transformResult.bundleManifestId, cacheDir);
248
246
  if (!validation.valid) {
249
- logger.warn("Bundle manifest validation failed, re-transforming", {
247
+ logger.warn("Cached HTTP bundle validation failed, re-transforming", {
250
248
  filePath,
251
- manifestId: transformResult.bundleManifestId.slice(0, 12),
249
+ manifestId: transformResult.bundleManifestId?.slice(0, 12),
252
250
  failedHashes: validation.failedHashes,
251
+ reason: validation.reason,
252
+ source: validation.source,
253
253
  });
254
254
  bundlesValid = false;
255
255
  }
256
256
  }
257
- else {
258
- const bundlePaths = extractHttpBundlePaths(transformedCode);
259
- if (bundlePaths.length > 0) {
260
- const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
261
- if (failed.length > 0) {
262
- logger.warn("HTTP bundle recovery failed, re-transforming", {
263
- filePath,
264
- failed,
265
- });
266
- bundlesValid = false;
267
- }
268
- }
269
- }
270
257
  if (!bundlesValid) {
271
258
  transformedCode = await transformToESM(fileContent, filePath, projectDir, adapter, {
272
259
  projectId: effectiveProjectId,
@@ -20,10 +20,12 @@ interface BundleManifest {
20
20
  createdAt: number;
21
21
  ttlSeconds: number;
22
22
  }
23
+ export type ManifestValidationReason = "manifest_missing" | "bundle_missing";
23
24
  /** Result of manifest validation. */
24
- interface ManifestValidationResult {
25
+ export interface ManifestValidationResult {
25
26
  valid: boolean;
26
27
  failedHashes: string[];
28
+ reason?: ManifestValidationReason;
27
29
  }
28
30
  /**
29
31
  * Compute a deterministic manifest ID from bundle hashes.
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-manifest.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/bundle-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;4BAQ4B;AAiB5B,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oEAAoE;AACpE,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qCAAqC;AACrC,UAAU,wBAAwB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAgBD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc1F;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBjF;AAuBD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,CAAC,CA6CnC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1E"}
1
+ {"version":3,"file":"bundle-manifest.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/bundle-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;4BAQ4B;AAiB5B,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oEAAoE;AACpE,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,wBAAwB,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;AAE7E,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,wBAAwB,CAAC;CACnC;AAgBD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc1F;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBjF;AAuBD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,CAAC,CA6CnC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1E"}
@@ -106,7 +106,7 @@ export async function validateBundleGroup(manifestId, cacheDir) {
106
106
  logger.debug(`${LOG_PREFIX} Manifest not found in distributed cache`, {
107
107
  manifestId: manifestId.slice(0, 12),
108
108
  });
109
- return { valid: false, failedHashes: [] };
109
+ return { valid: false, failedHashes: [], reason: "manifest_missing" };
110
110
  }
111
111
  const missingBundles = [];
112
112
  await Promise.all(manifest.bundles.map(async ({ hash }) => {
@@ -132,7 +132,7 @@ export async function validateBundleGroup(manifestId, cacheDir) {
132
132
  manifestId: manifestId.slice(0, 12),
133
133
  unrecoverable: unrecoverableHashes,
134
134
  });
135
- return { valid: false, failedHashes: unrecoverableHashes };
135
+ return { valid: false, failedHashes: unrecoverableHashes, reason: "bundle_missing" };
136
136
  }
137
137
  logger.info(`${LOG_PREFIX} All missing bundles recovered successfully`, {
138
138
  manifestId: manifestId.slice(0, 12),
@@ -0,0 +1,9 @@
1
+ import { type ManifestValidationReason } from "./bundle-manifest.js";
2
+ export interface CachedBundleValidationResult {
3
+ valid: boolean;
4
+ failedHashes: string[];
5
+ reason?: ManifestValidationReason;
6
+ source: "manifest" | "code";
7
+ }
8
+ export declare function validateCachedBundlesByManifestOrCode(code: string, bundleManifestId: string | undefined, cacheDir: string): Promise<CachedBundleValidationResult>;
9
+ //# sourceMappingURL=cached-bundle-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached-bundle-validation.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/cached-bundle-validation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,wBAAwB,EAAuB,MAAM,sBAAsB,CAAC;AAE1F,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;CAC7B;AAED,wBAAsB,qCAAqC,CACzD,IAAI,EAAE,MAAM,EACZ,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,4BAA4B,CAAC,CAwBvC"}
@@ -0,0 +1,25 @@
1
+ import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
2
+ import { ensureHttpBundlesExist } from "./http-cache.js";
3
+ import { validateBundleGroup } from "./bundle-manifest.js";
4
+ export async function validateCachedBundlesByManifestOrCode(code, bundleManifestId, cacheDir) {
5
+ if (bundleManifestId) {
6
+ const validation = await validateBundleGroup(bundleManifestId, cacheDir);
7
+ if (validation.valid || validation.reason === "bundle_missing") {
8
+ return { ...validation, source: "manifest" };
9
+ }
10
+ }
11
+ const bundlePaths = extractHttpBundlePaths(code);
12
+ if (bundlePaths.length === 0) {
13
+ return { valid: true, failedHashes: [], source: "code" };
14
+ }
15
+ const failedHashes = await ensureHttpBundlesExist(bundlePaths, cacheDir);
16
+ if (failedHashes.length === 0) {
17
+ return { valid: true, failedHashes: [], source: "code" };
18
+ }
19
+ return {
20
+ valid: false,
21
+ failedHashes,
22
+ reason: "bundle_missing",
23
+ source: "code",
24
+ };
25
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"distributed-cache.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAYjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAWjF,qDAAqD;AACrD,KAAK,gBAAgB,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC,CAAC;AAEhG;;;;;GAKG;AACH,UAAU,0BAA0B;IAClC,4EAA4E;IAC5E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,4DAA4D;IAC5D,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CACxC,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CA4G5C;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,MAAM,GACV,IAAI,CAqCN"}
1
+ {"version":3,"file":"distributed-cache.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AASjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAWjF,qDAAqD;AACrD,KAAK,gBAAgB,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC,CAAC;AAEhG;;;;;GAKG;AACH,UAAU,0BAA0B;IAClC,4EAA4E;IAC5E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,4DAA4D;IAC5D,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CACxC,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAoG5C;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,MAAM,GACV,IAAI,CAqCN"}
@@ -7,10 +7,11 @@
7
7
  * @module transforms/mdx/esm-module-loader/module-fetcher/distributed-cache
8
8
  */
9
9
  import { detokenizeAllCachePaths, tokenizeAllVeryFrontPaths } from "../../../../cache/index.js";
10
- import { cacheHttpImportsToLocal, ensureHttpBundlesExist } from "../../../esm/http-cache.js";
10
+ import { cacheHttpImportsToLocal } from "../../../esm/http-cache.js";
11
11
  import { loadImportMap } from "../../../../modules/import-map/index.js";
12
12
  import { extractHttpBundlePaths } from "../../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
13
- import { createBundleManifest, storeBundleManifest, validateBundleGroup, } from "../../../esm/bundle-manifest.js";
13
+ import { createBundleManifest, storeBundleManifest } from "../../../esm/bundle-manifest.js";
14
+ import { validateCachedBundlesByManifestOrCode } from "../../../esm/cached-bundle-validation.js";
14
15
  import { getHttpBundleCacheDir } from "../../../../utils/cache-dir.js";
15
16
  import { FRAMEWORK_ROOT, LOG_PREFIX_MDX_LOADER } from "../constants.js";
16
17
  import { getDistributedTransformBackend } from "../../../esm/transform-cache.js";
@@ -50,33 +51,20 @@ export async function readDistributedCache(transformCacheKey, normalizedPath, pr
50
51
  });
51
52
  const bundleManifestKey = `${transformCacheKey}:bm`;
52
53
  const manifestId = await distributedCache.get(bundleManifestKey).catch(() => null);
53
- if (manifestId) {
54
+ if (moduleCode) {
54
55
  const cacheDir = getHttpBundleCacheDir();
55
- const validation = await validateBundleGroup(manifestId, cacheDir);
56
+ const validation = await validateCachedBundlesByManifestOrCode(moduleCode, manifestId ?? undefined, cacheDir);
56
57
  if (!validation.valid) {
57
- log.warn(`${LOG_PREFIX_MDX_LOADER} Bundle manifest validation failed`, {
58
+ log.warn(`${LOG_PREFIX_MDX_LOADER} Cached HTTP bundle validation failed`, {
58
59
  normalizedPath,
59
- manifestId: manifestId.slice(0, 12),
60
+ manifestId: manifestId?.slice(0, 12),
60
61
  failedHashes: validation.failedHashes,
62
+ reason: validation.reason,
63
+ source: validation.source,
61
64
  });
62
65
  moduleCode = null;
63
66
  }
64
67
  }
65
- else {
66
- // Use detokenized code for bundle path extraction
67
- const bundlePaths = extractHttpBundlePaths(moduleCode);
68
- if (bundlePaths.length > 0) {
69
- const cacheDir = getHttpBundleCacheDir();
70
- const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
71
- if (failed.length > 0) {
72
- log.warn(`${LOG_PREFIX_MDX_LOADER} Some HTTP bundles could not be recovered`, {
73
- normalizedPath,
74
- failed,
75
- });
76
- moduleCode = null;
77
- }
78
- }
79
- }
80
68
  // Use detokenized code for framework path checks
81
69
  if (moduleCode && await hasIncompatibleFrameworkPaths(moduleCode, log)) {
82
70
  log.warn(`${LOG_PREFIX_MDX_LOADER} Cached code has incompatible framework paths`, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/pipeline/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAEhB,eAAe,EAChB,MAAM,YAAY,CAAC;AA4IpB,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAoI1B;AAoBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmCD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,SAAS,EACT,KAAK,EACL,KAAK,EACL,YAAY,GACb,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/pipeline/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAEhB,eAAe,EAChB,MAAM,YAAY,CAAC;AA0HpB,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAoI1B;AAoBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmCD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,SAAS,EACT,KAAK,EACL,KAAK,EACL,YAAY,GACb,MAAM,cAAc,CAAC"}
@@ -11,10 +11,8 @@ import { computeConfigHash } from "../../cache/config-hash.js";
11
11
  import { computeDepsHash } from "../../cache/dependency-graph.js";
12
12
  import { compilePlugin, cssStripPlugin, finalizePlugin, parsePlugin, resolveImportsPlugin, ssrHttpCachePlugin, ssrHttpStubPlugin, ssrVfModulesPlugin, } from "./stages/index.js";
13
13
  import { createFileSystem, exists } from "../../platform/compat/fs.js";
14
- import { ensureHttpBundlesExist } from "../esm/http-cache.js";
15
- import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
16
14
  import { getHttpBundleCacheDir } from "../../utils/cache-dir.js";
17
- import { validateBundleGroup } from "../esm/bundle-manifest.js";
15
+ import { validateCachedBundlesByManifestOrCode } from "../esm/cached-bundle-validation.js";
18
16
  import { extractFrameworkBundlePaths } from "../shared/framework-bundle-paths.js";
19
17
  const SSR_PIPELINE = [
20
18
  parsePlugin,
@@ -96,29 +94,15 @@ async function validateFrameworkBundles(code, cacheKey) {
96
94
  */
97
95
  async function validateCachedBundles(code, bundleManifestId, cacheKey) {
98
96
  const cacheDir = getHttpBundleCacheDir();
99
- // If we have a manifest ID, use the faster manifest-based validation
100
- if (bundleManifestId) {
101
- const validation = await validateBundleGroup(bundleManifestId, cacheDir);
102
- if (validation.valid)
103
- return true;
104
- logger.debug("Bundle manifest validation failed", {
105
- cacheKey: cacheKey.slice(-40),
106
- manifestId: bundleManifestId.slice(0, 12),
107
- failedCount: validation.failedHashes.length,
108
- });
109
- return false;
110
- }
111
- // Fall back to extracting bundle paths from code and validating each
112
- const bundlePaths = extractHttpBundlePaths(code);
113
- if (bundlePaths.length === 0)
114
- return true;
115
- const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
116
- if (failed.length === 0)
97
+ const validation = await validateCachedBundlesByManifestOrCode(code, bundleManifestId, cacheDir);
98
+ if (validation.valid)
117
99
  return true;
118
- logger.debug("HTTP bundle validation failed", {
100
+ logger.debug("Cached HTTP bundle validation failed", {
119
101
  cacheKey: cacheKey.slice(-40),
120
- failedCount: failed.length,
121
- totalBundles: bundlePaths.length,
102
+ manifestId: bundleManifestId?.slice(0, 12),
103
+ failedCount: validation.failedHashes.length,
104
+ reason: validation.reason,
105
+ source: validation.source,
122
106
  });
123
107
  return false;
124
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -12,6 +12,11 @@ import { VERSION } from "../utils/version.js";
12
12
  import { validateContentType } from "../security/input-validation/limits.js";
13
13
  import { VeryfrontError } from "../security/input-validation/errors.js";
14
14
  import type { IntegrationRuntimeConfig } from "../integrations/types.js";
15
+ import { logger as baseLogger } from "../utils/index.js";
16
+
17
+ const logger = baseLogger.component("mcp-server");
18
+
19
+ const MAX_REQUEST_BODY_SIZE = 1_048_576; // 1 MB
15
20
 
16
21
  type JSONRPCParams = Record<string, unknown> | unknown[];
17
22
 
@@ -51,6 +56,10 @@ export class MCPServer {
51
56
 
52
57
  constructor(config: MCPServerConfig) {
53
58
  this.config = config;
59
+
60
+ if (!config.auth || config.auth.type === "none") {
61
+ logger.warn("MCP server has no authentication configured — all requests will be accepted");
62
+ }
54
63
  }
55
64
 
56
65
  /**
@@ -304,13 +313,27 @@ export class MCPServer {
304
313
 
305
314
  createHTTPHandler(): (request: dntShim.Request) => Promise<dntShim.Response> {
306
315
  return async (request: dntShim.Request) => {
307
- if (request.method === "OPTIONS") return this.handleCORS();
316
+ const requestOrigin = request.headers.get("Origin");
317
+ if (request.method === "OPTIONS") return this.handleCORS(requestOrigin);
308
318
 
309
319
  if (this.config.auth?.type && this.config.auth.type !== "none") {
310
320
  const authorized = await this.validateAuth(request);
311
321
  if (!authorized) return new dntShim.Response("Unauthorized", { status: 401 });
312
322
  }
313
323
 
324
+ // Enforce request body size limit (fast path via Content-Length header)
325
+ const contentLength = request.headers.get("content-length");
326
+ if (contentLength && Number(contentLength) > MAX_REQUEST_BODY_SIZE) {
327
+ return new dntShim.Response(
328
+ JSON.stringify({
329
+ jsonrpc: "2.0",
330
+ id: null,
331
+ error: { code: -32600, message: "Request body too large" },
332
+ }),
333
+ { status: 413, headers: { "Content-Type": "application/json" } },
334
+ );
335
+ }
336
+
314
337
  try {
315
338
  validateContentType(request, "application/json");
316
339
  } catch (error) {
@@ -327,7 +350,18 @@ export class MCPServer {
327
350
 
328
351
  let rpcRequest: JSONRPCRequest;
329
352
  try {
330
- rpcRequest = await request.json();
353
+ const bodyText = await request.text();
354
+ if (bodyText.length > MAX_REQUEST_BODY_SIZE) {
355
+ return new dntShim.Response(
356
+ JSON.stringify({
357
+ jsonrpc: "2.0",
358
+ id: null,
359
+ error: { code: -32600, message: "Request body too large" },
360
+ }),
361
+ { status: 413, headers: { "Content-Type": "application/json" } },
362
+ );
363
+ }
364
+ rpcRequest = JSON.parse(bodyText) as JSONRPCRequest;
331
365
  } catch (_) {
332
366
  // expected: malformed JSON in request body
333
367
  return new dntShim.Response(
@@ -350,7 +384,7 @@ export class MCPServer {
350
384
  return new dntShim.Response(JSON.stringify(rpcResponse), {
351
385
  headers: {
352
386
  "Content-Type": "application/json",
353
- ...this.getCORSHeaders(),
387
+ ...this.getCORSHeaders(requestOrigin),
354
388
  },
355
389
  });
356
390
  };
@@ -384,24 +418,38 @@ export class MCPServer {
384
418
  if (auth.type !== "bearer") return false;
385
419
 
386
420
  const token = authHeader.replace("Bearer ", "");
387
- if (!auth.validate) return false;
421
+
422
+ // When bearer auth is configured without a validate function, reject all requests
423
+ if (!auth.validate) {
424
+ logger.warn("Bearer auth configured without validate function — rejecting request");
425
+ return false;
426
+ }
388
427
 
389
428
  return await auth.validate(token);
390
429
  }
391
430
 
392
- private handleCORS(): dntShim.Response {
393
- return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders() });
431
+ private handleCORS(requestOrigin?: string | null): dntShim.Response {
432
+ return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders(requestOrigin) });
394
433
  }
395
434
 
396
- private getCORSHeaders(): Record<string, string> {
435
+ private getCORSHeaders(requestOrigin?: string | null): Record<string, string> {
397
436
  if (!this.config.cors?.enabled) return {};
398
437
 
399
- const origin = this.config.cors.origins?.[0] ?? "*";
438
+ const origins = this.config.cors.origins;
439
+ if (!origins || origins.length === 0) return {};
440
+
441
+ // Match request origin against the configured origins list
442
+ const matchedOrigin = requestOrigin && origins.includes(requestOrigin)
443
+ ? requestOrigin
444
+ : undefined;
445
+
446
+ if (!matchedOrigin) return {};
400
447
 
401
448
  return {
402
- "Access-Control-Allow-Origin": origin,
449
+ "Access-Control-Allow-Origin": matchedOrigin,
403
450
  "Access-Control-Allow-Methods": "POST, OPTIONS",
404
451
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-End-User-Id, X-Project-Id",
452
+ "Vary": "Origin",
405
453
  };
406
454
  }
407
455
 
@@ -7,7 +7,7 @@ import { cwd, getEnv } from "../platform/compat/process.js";
7
7
  import { join } from "../platform/compat/path/index.js";
8
8
  import { injectContext, ProxySpanNames, withSpan } from "./tracing.js";
9
9
  import { computeContentSourceId } from "../cache/keys.js";
10
- import { jwtVerify } from "jose";
10
+ import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
11
11
 
12
12
  export const INTERNAL_PROXY_HEADERS = [
13
13
  "x-token",
@@ -37,6 +37,29 @@ interface DomainLookupResult {
37
37
  }>;
38
38
  }
39
39
 
40
+ const remoteJwksByUrl = new Map<string, ReturnType<typeof createRemoteJWKSet>>();
41
+
42
+ function getApiJwks(apiBaseUrl: string, logger?: ProxyLogger) {
43
+ try {
44
+ const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
45
+ const jwksUrl = new URL(".well-known/jwks.json", normalizedBaseUrl);
46
+ const cacheKey = jwksUrl.toString();
47
+ let jwks = remoteJwksByUrl.get(cacheKey);
48
+
49
+ if (!jwks) {
50
+ jwks = createRemoteJWKSet(jwksUrl);
51
+ remoteJwksByUrl.set(cacheKey, jwks);
52
+ }
53
+
54
+ return jwks;
55
+ } catch (error) {
56
+ logger?.error("Invalid API base URL for JWKS lookup", error as Error, {
57
+ apiBaseUrl,
58
+ });
59
+ return undefined;
60
+ }
61
+ }
62
+
40
63
  async function lookupProjectByDomain(
41
64
  domain: string,
42
65
  apiBaseUrl: string,
@@ -154,8 +177,42 @@ function extractUserToken(cookieHeader: string): string | undefined {
154
177
 
155
178
  async function extractUserIdFromToken(
156
179
  token: string,
180
+ apiBaseUrl: string,
157
181
  log?: ProxyLogger,
158
182
  ): Promise<string | undefined> {
183
+ let algorithm: string | undefined;
184
+
185
+ try {
186
+ algorithm = decodeProtectedHeader(token).alg;
187
+ } catch (error) {
188
+ log?.debug("Failed to decode JWT header", {
189
+ error: error instanceof Error ? error.message : String(error),
190
+ });
191
+ return undefined;
192
+ }
193
+
194
+ if (algorithm === "RS256") {
195
+ const jwks = getApiJwks(apiBaseUrl, log);
196
+ if (!jwks) return undefined;
197
+
198
+ try {
199
+ const { payload } = await jwtVerify(token, jwks, {
200
+ algorithms: ["RS256"],
201
+ });
202
+ return (payload as { userId?: string }).userId;
203
+ } catch (error) {
204
+ log?.debug("RS256 JWT verification failed", {
205
+ error: error instanceof Error ? error.message : String(error),
206
+ });
207
+ return undefined;
208
+ }
209
+ }
210
+
211
+ if (algorithm !== "HS256") {
212
+ log?.debug("Unsupported JWT algorithm", { algorithm: algorithm ?? null });
213
+ return undefined;
214
+ }
215
+
159
216
  const jwtSecret = getEnv("JWT_SECRET");
160
217
 
161
218
  if (!jwtSecret) {
@@ -304,7 +361,11 @@ export function createProxyHandler(options: ProxyHandlerOptions) {
304
361
  return { status: 302, message: "Authentication required", redirectUrl };
305
362
  }
306
363
 
307
- const userId = await extractUserIdFromToken(userToken, logger);
364
+ const userId = await extractUserIdFromToken(
365
+ userToken,
366
+ config.apiBaseUrl,
367
+ logger,
368
+ );
308
369
  if (!userId) {
309
370
  const redirectUrl = makeAuthRedirectUrl(req);
310
371
  logger?.info("Could not extract userId from token", {
@@ -21,8 +21,7 @@ import {
21
21
  setCachedTransformAsync,
22
22
  } from "../../../transforms/esm/transform-cache.js";
23
23
  import { TRANSFORM_DISTRIBUTED_TTL_SEC } from "../../../utils/constants/cache.js";
24
- import { ensureHttpBundlesExist } from "../../../transforms/esm/http-cache.js";
25
- import { validateBundleGroup } from "../../../transforms/esm/bundle-manifest.js";
24
+ import { validateCachedBundlesByManifestOrCode } from "../../../transforms/esm/cached-bundle-validation.js";
26
25
  import { getHttpBundleCacheDir, getMdxEsmCacheDir } from "../../../utils/cache-dir.js";
27
26
  import { dirname, join, normalize } from "../../../platform/compat/path/index.js";
28
27
  import { hashCodeHex } from "../../../utils/hash-utils.js";
@@ -32,7 +31,6 @@ import {
32
31
  lookupMdxEsmCache,
33
32
  saveModulePathCache,
34
33
  } from "../../../transforms/mdx/esm-module-loader/cache/index.js";
35
- import { extractHttpBundlePaths } from "../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
36
34
 
37
35
  const logger = rendererLogger.component("module-loader");
38
36
 
@@ -344,28 +342,22 @@ export async function transformModuleWithDeps(
344
342
  const cacheDir = getHttpBundleCacheDir();
345
343
  let bundlesValid = true;
346
344
 
347
- if (transformResult.cacheHit && transformResult.bundleManifestId) {
348
- const validation = await validateBundleGroup(transformResult.bundleManifestId, cacheDir);
345
+ if (transformResult.cacheHit) {
346
+ const validation = await validateCachedBundlesByManifestOrCode(
347
+ transformedCode,
348
+ transformResult.bundleManifestId,
349
+ cacheDir,
350
+ );
349
351
  if (!validation.valid) {
350
- logger.warn("Bundle manifest validation failed, re-transforming", {
352
+ logger.warn("Cached HTTP bundle validation failed, re-transforming", {
351
353
  filePath,
352
- manifestId: transformResult.bundleManifestId.slice(0, 12),
354
+ manifestId: transformResult.bundleManifestId?.slice(0, 12),
353
355
  failedHashes: validation.failedHashes,
356
+ reason: validation.reason,
357
+ source: validation.source,
354
358
  });
355
359
  bundlesValid = false;
356
360
  }
357
- } else {
358
- const bundlePaths = extractHttpBundlePaths(transformedCode);
359
- if (bundlePaths.length > 0) {
360
- const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
361
- if (failed.length > 0) {
362
- logger.warn("HTTP bundle recovery failed, re-transforming", {
363
- filePath,
364
- failed,
365
- });
366
- bundlesValid = false;
367
- }
368
- }
369
361
  }
370
362
 
371
363
  if (!bundlesValid) {
@@ -38,10 +38,13 @@ interface BundleManifest {
38
38
  ttlSeconds: number;
39
39
  }
40
40
 
41
+ export type ManifestValidationReason = "manifest_missing" | "bundle_missing";
42
+
41
43
  /** Result of manifest validation. */
42
- interface ManifestValidationResult {
44
+ export interface ManifestValidationResult {
43
45
  valid: boolean;
44
46
  failedHashes: string[];
47
+ reason?: ManifestValidationReason;
45
48
  }
46
49
 
47
50
  /**
@@ -148,7 +151,7 @@ export async function validateBundleGroup(
148
151
  logger.debug(`${LOG_PREFIX} Manifest not found in distributed cache`, {
149
152
  manifestId: manifestId.slice(0, 12),
150
153
  });
151
- return { valid: false, failedHashes: [] };
154
+ return { valid: false, failedHashes: [], reason: "manifest_missing" };
152
155
  }
153
156
 
154
157
  const missingBundles: Array<{ path: string; hash: string }> = [];
@@ -178,7 +181,7 @@ export async function validateBundleGroup(
178
181
  manifestId: manifestId.slice(0, 12),
179
182
  unrecoverable: unrecoverableHashes,
180
183
  });
181
- return { valid: false, failedHashes: unrecoverableHashes };
184
+ return { valid: false, failedHashes: unrecoverableHashes, reason: "bundle_missing" };
182
185
  }
183
186
 
184
187
  logger.info(`${LOG_PREFIX} All missing bundles recovered successfully`, {
@@ -0,0 +1,40 @@
1
+ import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
2
+ import { ensureHttpBundlesExist } from "./http-cache.js";
3
+ import { type ManifestValidationReason, validateBundleGroup } from "./bundle-manifest.js";
4
+
5
+ export interface CachedBundleValidationResult {
6
+ valid: boolean;
7
+ failedHashes: string[];
8
+ reason?: ManifestValidationReason;
9
+ source: "manifest" | "code";
10
+ }
11
+
12
+ export async function validateCachedBundlesByManifestOrCode(
13
+ code: string,
14
+ bundleManifestId: string | undefined,
15
+ cacheDir: string,
16
+ ): Promise<CachedBundleValidationResult> {
17
+ if (bundleManifestId) {
18
+ const validation = await validateBundleGroup(bundleManifestId, cacheDir);
19
+ if (validation.valid || validation.reason === "bundle_missing") {
20
+ return { ...validation, source: "manifest" };
21
+ }
22
+ }
23
+
24
+ const bundlePaths = extractHttpBundlePaths(code);
25
+ if (bundlePaths.length === 0) {
26
+ return { valid: true, failedHashes: [], source: "code" };
27
+ }
28
+
29
+ const failedHashes = await ensureHttpBundlesExist(bundlePaths, cacheDir);
30
+ if (failedHashes.length === 0) {
31
+ return { valid: true, failedHashes: [], source: "code" };
32
+ }
33
+
34
+ return {
35
+ valid: false,
36
+ failedHashes,
37
+ reason: "bundle_missing",
38
+ source: "code",
39
+ };
40
+ }
@@ -9,14 +9,11 @@
9
9
 
10
10
  import type { Logger } from "../../../../utils/logger/logger.js";
11
11
  import { detokenizeAllCachePaths, tokenizeAllVeryFrontPaths } from "../../../../cache/index.js";
12
- import { cacheHttpImportsToLocal, ensureHttpBundlesExist } from "../../../esm/http-cache.js";
12
+ import { cacheHttpImportsToLocal } from "../../../esm/http-cache.js";
13
13
  import { loadImportMap } from "../../../../modules/import-map/index.js";
14
14
  import { extractHttpBundlePaths } from "../../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
15
- import {
16
- createBundleManifest,
17
- storeBundleManifest,
18
- validateBundleGroup,
19
- } from "../../../esm/bundle-manifest.js";
15
+ import { createBundleManifest, storeBundleManifest } from "../../../esm/bundle-manifest.js";
16
+ import { validateCachedBundlesByManifestOrCode } from "../../../esm/cached-bundle-validation.js";
20
17
  import { getHttpBundleCacheDir } from "../../../../utils/cache-dir.js";
21
18
  import { FRAMEWORK_ROOT, LOG_PREFIX_MDX_LOADER } from "../constants.js";
22
19
  import { getDistributedTransformBackend } from "../../../esm/transform-cache.js";
@@ -86,31 +83,23 @@ export async function readDistributedCache(
86
83
  const bundleManifestKey = `${transformCacheKey}:bm`;
87
84
  const manifestId = await distributedCache.get(bundleManifestKey).catch(() => null);
88
85
 
89
- if (manifestId) {
86
+ if (moduleCode) {
90
87
  const cacheDir = getHttpBundleCacheDir();
91
- const validation = await validateBundleGroup(manifestId, cacheDir);
88
+ const validation = await validateCachedBundlesByManifestOrCode(
89
+ moduleCode,
90
+ manifestId ?? undefined,
91
+ cacheDir,
92
+ );
92
93
  if (!validation.valid) {
93
- log.warn(`${LOG_PREFIX_MDX_LOADER} Bundle manifest validation failed`, {
94
+ log.warn(`${LOG_PREFIX_MDX_LOADER} Cached HTTP bundle validation failed`, {
94
95
  normalizedPath,
95
- manifestId: manifestId.slice(0, 12),
96
+ manifestId: manifestId?.slice(0, 12),
96
97
  failedHashes: validation.failedHashes,
98
+ reason: validation.reason,
99
+ source: validation.source,
97
100
  });
98
101
  moduleCode = null;
99
102
  }
100
- } else {
101
- // Use detokenized code for bundle path extraction
102
- const bundlePaths = extractHttpBundlePaths(moduleCode);
103
- if (bundlePaths.length > 0) {
104
- const cacheDir = getHttpBundleCacheDir();
105
- const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
106
- if (failed.length > 0) {
107
- log.warn(`${LOG_PREFIX_MDX_LOADER} Some HTTP bundles could not be recovered`, {
108
- normalizedPath,
109
- failed,
110
- });
111
- moduleCode = null;
112
- }
113
- }
114
103
  }
115
104
 
116
105
  // Use detokenized code for framework path checks
@@ -31,10 +31,8 @@ import {
31
31
  ssrVfModulesPlugin,
32
32
  } from "./stages/index.js";
33
33
  import { createFileSystem, exists } from "../../platform/compat/fs.js";
34
- import { ensureHttpBundlesExist } from "../esm/http-cache.js";
35
- import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
36
34
  import { getHttpBundleCacheDir } from "../../utils/cache-dir.js";
37
- import { validateBundleGroup } from "../esm/bundle-manifest.js";
35
+ import { validateCachedBundlesByManifestOrCode } from "../esm/cached-bundle-validation.js";
38
36
  import { extractFrameworkBundlePaths } from "../shared/framework-bundle-paths.js";
39
37
 
40
38
  const SSR_PIPELINE: TransformPlugin[] = [
@@ -130,31 +128,15 @@ async function validateCachedBundles(
130
128
  cacheKey: string,
131
129
  ): Promise<boolean> {
132
130
  const cacheDir = getHttpBundleCacheDir();
131
+ const validation = await validateCachedBundlesByManifestOrCode(code, bundleManifestId, cacheDir);
132
+ if (validation.valid) return true;
133
133
 
134
- // If we have a manifest ID, use the faster manifest-based validation
135
- if (bundleManifestId) {
136
- const validation = await validateBundleGroup(bundleManifestId, cacheDir);
137
- if (validation.valid) return true;
138
-
139
- logger.debug("Bundle manifest validation failed", {
140
- cacheKey: cacheKey.slice(-40),
141
- manifestId: bundleManifestId.slice(0, 12),
142
- failedCount: validation.failedHashes.length,
143
- });
144
- return false;
145
- }
146
-
147
- // Fall back to extracting bundle paths from code and validating each
148
- const bundlePaths = extractHttpBundlePaths(code);
149
- if (bundlePaths.length === 0) return true;
150
-
151
- const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
152
- if (failed.length === 0) return true;
153
-
154
- logger.debug("HTTP bundle validation failed", {
134
+ logger.debug("Cached HTTP bundle validation failed", {
155
135
  cacheKey: cacheKey.slice(-40),
156
- failedCount: failed.length,
157
- totalBundles: bundlePaths.length,
136
+ manifestId: bundleManifestId?.slice(0, 12),
137
+ failedCount: validation.failedHashes.length,
138
+ reason: validation.reason,
139
+ source: validation.source,
158
140
  });
159
141
  return false;
160
142
  }