theokit 0.48.7 → 0.48.8

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 (34) hide show
  1. package/dist/{actions-virtual-module-GB5FWUPS.js → actions-virtual-module-IICWQK43.js} +2 -3
  2. package/dist/{actions-virtual-module-GB5FWUPS.js.map → actions-virtual-module-IICWQK43.js.map} +1 -1
  3. package/dist/{agent-47XWDGIR.js → agent-WZLCEXD4.js} +2 -2
  4. package/dist/{app-typed-client-5QDNHANF.js → app-typed-client-4GDSR5T3.js} +2 -3
  5. package/dist/{app-typed-client-5QDNHANF.js.map → app-typed-client-4GDSR5T3.js.map} +1 -1
  6. package/dist/{build-4CAGKYYT.js → build-5TKAROT4.js} +2 -2
  7. package/dist/{chunk-H6YBKIUN.js → chunk-ABUVJU3P.js} +574 -161
  8. package/dist/chunk-ABUVJU3P.js.map +1 -0
  9. package/dist/{chunk-JZ25BBLP.js → chunk-HENNMZUH.js} +2 -2
  10. package/dist/{chunk-3GUXSNHY.js → chunk-RPIL7VRZ.js} +86 -3
  11. package/dist/chunk-RPIL7VRZ.js.map +1 -0
  12. package/dist/{chunk-AXQF3I4L.js → chunk-ZGNA66JK.js} +39 -122
  13. package/dist/chunk-ZGNA66JK.js.map +1 -0
  14. package/dist/cli/index.js +5 -5
  15. package/dist/{dev-F67X4F5E.js → dev-XGAJQ7DH.js} +4 -5
  16. package/dist/{dev-F67X4F5E.js.map → dev-XGAJQ7DH.js.map} +1 -1
  17. package/dist/{internal-api-ICU7ITJD.js → internal-api-AKYIRMT2.js} +7 -9
  18. package/dist/{mcp-AJHKPAU4.js → mcp-D7K6NJMW.js} +2 -2
  19. package/dist/{start-IITIEJTY.js → start-YR4TQQP2.js} +10 -8
  20. package/dist/start-YR4TQQP2.js.map +1 -0
  21. package/dist/{vite-plugin-MXHY6MSV.js → vite-plugin-PUUCR6LR.js} +4 -5
  22. package/package.json +1 -1
  23. package/dist/chunk-3GUXSNHY.js.map +0 -1
  24. package/dist/chunk-7CQ6DEWZ.js +0 -421
  25. package/dist/chunk-7CQ6DEWZ.js.map +0 -1
  26. package/dist/chunk-AXQF3I4L.js.map +0 -1
  27. package/dist/chunk-H6YBKIUN.js.map +0 -1
  28. package/dist/start-IITIEJTY.js.map +0 -1
  29. /package/dist/{agent-47XWDGIR.js.map → agent-WZLCEXD4.js.map} +0 -0
  30. /package/dist/{build-4CAGKYYT.js.map → build-5TKAROT4.js.map} +0 -0
  31. /package/dist/{chunk-JZ25BBLP.js.map → chunk-HENNMZUH.js.map} +0 -0
  32. /package/dist/{internal-api-ICU7ITJD.js.map → internal-api-AKYIRMT2.js.map} +0 -0
  33. /package/dist/{mcp-AJHKPAU4.js.map → mcp-D7K6NJMW.js.map} +0 -0
  34. /package/dist/{vite-plugin-MXHY6MSV.js.map → vite-plugin-PUUCR6LR.js.map} +0 -0
@@ -8,6 +8,7 @@ import {
8
8
  enforceCsrf,
9
9
  incomingMessageToHandlerRequest,
10
10
  parseRequestBody,
11
+ safeAudit,
11
12
  sendError,
12
13
  sendJson,
13
14
  validateCsrfRequest,
@@ -18,6 +19,166 @@ import {
18
19
  serverErrorToEnvelope
19
20
  } from "./chunk-VQIUTCK2.js";
20
21
 
22
+ // src/core/contracts/action-protocol.ts
23
+ var CODE_TO_STATUS = {
24
+ VALIDATION_ERROR: 422,
25
+ BAD_REQUEST: 400,
26
+ UNAUTHORIZED: 401,
27
+ FORBIDDEN: 403,
28
+ NOT_FOUND: 404,
29
+ METHOD_NOT_ALLOWED: 405,
30
+ CONFLICT: 409,
31
+ CONTENT_TOO_LARGE: 413,
32
+ PAYLOAD_TOO_LARGE: 413,
33
+ UNSUPPORTED_MEDIA_TYPE: 415,
34
+ TOO_MANY_REQUESTS: 429,
35
+ INTERNAL_SERVER_ERROR: 500
36
+ };
37
+ var STATUS_TO_CODE = {
38
+ 400: "BAD_REQUEST",
39
+ 401: "UNAUTHORIZED",
40
+ 403: "FORBIDDEN",
41
+ 404: "NOT_FOUND",
42
+ 405: "METHOD_NOT_ALLOWED",
43
+ 409: "CONFLICT",
44
+ 413: "PAYLOAD_TOO_LARGE",
45
+ 415: "UNSUPPORTED_MEDIA_TYPE",
46
+ 422: "VALIDATION_ERROR",
47
+ 429: "TOO_MANY_REQUESTS",
48
+ 500: "INTERNAL_SERVER_ERROR"
49
+ };
50
+ var ActionError = class _ActionError extends Error {
51
+ // Discriminator widened to the full union so subclasses can narrow to their
52
+ // specific literal (TypeScript would otherwise reject the override). Concrete
53
+ // values are still always exact literals at runtime.
54
+ type = "TheoActionError";
55
+ code;
56
+ status;
57
+ constructor(params) {
58
+ super(params.message ?? params.code);
59
+ this.code = params.code;
60
+ this.status = _ActionError.codeToStatus(params.code);
61
+ if (params.stack) {
62
+ this.stack = params.stack;
63
+ }
64
+ }
65
+ /**
66
+ * G5 T2.4 — canonical envelope view of the action error. Maps the G3
67
+ * ActionErrorCode to a canonical TheoErrorCode (VALIDATION_ERROR ↔
68
+ * UNPROCESSABLE_ENTITY, CONTENT_TOO_LARGE ↔ PAYLOAD_TOO_LARGE) so consumer
69
+ * UI / SDK code can switch on the unified envelope.
70
+ *
71
+ * Subclasses override to populate `ext` (see `ActionInputError.envelope`).
72
+ */
73
+ get envelope() {
74
+ return {
75
+ code: _ActionError.toTheoErrorCode(this.code),
76
+ message: this.message
77
+ };
78
+ }
79
+ /**
80
+ * Translate G3 ActionErrorCode → canonical TheoErrorCode (blueprint
81
+ * Recommendations § "G3 ActionError becomes inaugural envelope user").
82
+ */
83
+ static toTheoErrorCode(code) {
84
+ if (code === "VALIDATION_ERROR") return "UNPROCESSABLE_ENTITY";
85
+ if (code === "CONTENT_TOO_LARGE") return "PAYLOAD_TOO_LARGE";
86
+ return code;
87
+ }
88
+ static codeToStatus(code) {
89
+ return CODE_TO_STATUS[code];
90
+ }
91
+ static statusToCode(status) {
92
+ return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
93
+ }
94
+ /**
95
+ * Parse a serialized error JSON back into the typed class hierarchy.
96
+ * Distinguishes `TheoActionInputError` (with `issues` array) from
97
+ * `TheoActionError` via the `type` discriminator. Falls back to
98
+ * `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
99
+ * `type`, unknown `code`).
100
+ */
101
+ static fromJson(body) {
102
+ if (typeof body !== "object" || body === null) {
103
+ return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
104
+ }
105
+ const obj = body;
106
+ if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
107
+ return new ActionInputError(obj.issues);
108
+ }
109
+ if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
110
+ return new _ActionError({
111
+ code: obj.code,
112
+ message: typeof obj.message === "string" ? obj.message : void 0
113
+ });
114
+ }
115
+ return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
116
+ }
117
+ };
118
+ var ActionInputError = class extends ActionError {
119
+ type = "TheoActionInputError";
120
+ issues;
121
+ fields;
122
+ constructor(rawIssues) {
123
+ super({ code: "VALIDATION_ERROR", message: "Validation failed" });
124
+ this.issues = extractUniversalIssues(rawIssues);
125
+ this.fields = buildFieldsMap(this.issues);
126
+ }
127
+ /**
128
+ * G5 T2.4 — envelope view with ValidationFieldsExt populated from .fields.
129
+ * UI consumers can `switch (env.code)` on `UNPROCESSABLE_ENTITY` and read
130
+ * `(env.ext as ValidationFieldsExt).fields` for field-level rendering.
131
+ */
132
+ get envelope() {
133
+ return {
134
+ code: "UNPROCESSABLE_ENTITY",
135
+ message: this.message,
136
+ ext: { fields: this.fields }
137
+ };
138
+ }
139
+ };
140
+ function buildFieldsMap(issues) {
141
+ const fields = {};
142
+ const seen = /* @__PURE__ */ new Set();
143
+ for (const issue of issues) {
144
+ const key = issue.path.length === 0 ? "" : issue.path.join(".");
145
+ const dedupeKey = `${key}\0${issue.message}`;
146
+ if (seen.has(dedupeKey)) continue;
147
+ seen.add(dedupeKey);
148
+ const bucket = fields[key] ?? [];
149
+ bucket.push(issue.message);
150
+ fields[key] = bucket;
151
+ }
152
+ return fields;
153
+ }
154
+ function extractUniversalIssues(raw) {
155
+ if (!Array.isArray(raw)) return [];
156
+ const out = [];
157
+ for (const entry of raw) {
158
+ if (typeof entry !== "object" || entry === null) continue;
159
+ const obj = entry;
160
+ if (!Array.isArray(obj.path)) continue;
161
+ if (typeof obj.message !== "string") continue;
162
+ const path = [];
163
+ let pathValid = true;
164
+ for (const seg of obj.path) {
165
+ if (typeof seg === "string" || typeof seg === "number") {
166
+ path.push(seg);
167
+ } else {
168
+ pathValid = false;
169
+ break;
170
+ }
171
+ }
172
+ if (!pathValid) continue;
173
+ out.push({
174
+ path,
175
+ message: obj.message,
176
+ code: typeof obj.code === "string" ? obj.code : void 0
177
+ });
178
+ }
179
+ return out;
180
+ }
181
+
21
182
  // src/core/contracts/auth-error-guard.ts
22
183
  function isAuthRequiredError(err) {
23
184
  if (err == null || typeof err !== "object") return false;
@@ -514,166 +675,6 @@ async function executeRoute(ctx) {
514
675
  }
515
676
  }
516
677
 
517
- // src/core/contracts/action-protocol.ts
518
- var CODE_TO_STATUS = {
519
- VALIDATION_ERROR: 422,
520
- BAD_REQUEST: 400,
521
- UNAUTHORIZED: 401,
522
- FORBIDDEN: 403,
523
- NOT_FOUND: 404,
524
- METHOD_NOT_ALLOWED: 405,
525
- CONFLICT: 409,
526
- CONTENT_TOO_LARGE: 413,
527
- PAYLOAD_TOO_LARGE: 413,
528
- UNSUPPORTED_MEDIA_TYPE: 415,
529
- TOO_MANY_REQUESTS: 429,
530
- INTERNAL_SERVER_ERROR: 500
531
- };
532
- var STATUS_TO_CODE = {
533
- 400: "BAD_REQUEST",
534
- 401: "UNAUTHORIZED",
535
- 403: "FORBIDDEN",
536
- 404: "NOT_FOUND",
537
- 405: "METHOD_NOT_ALLOWED",
538
- 409: "CONFLICT",
539
- 413: "PAYLOAD_TOO_LARGE",
540
- 415: "UNSUPPORTED_MEDIA_TYPE",
541
- 422: "VALIDATION_ERROR",
542
- 429: "TOO_MANY_REQUESTS",
543
- 500: "INTERNAL_SERVER_ERROR"
544
- };
545
- var ActionError = class _ActionError extends Error {
546
- // Discriminator widened to the full union so subclasses can narrow to their
547
- // specific literal (TypeScript would otherwise reject the override). Concrete
548
- // values are still always exact literals at runtime.
549
- type = "TheoActionError";
550
- code;
551
- status;
552
- constructor(params) {
553
- super(params.message ?? params.code);
554
- this.code = params.code;
555
- this.status = _ActionError.codeToStatus(params.code);
556
- if (params.stack) {
557
- this.stack = params.stack;
558
- }
559
- }
560
- /**
561
- * G5 T2.4 — canonical envelope view of the action error. Maps the G3
562
- * ActionErrorCode to a canonical TheoErrorCode (VALIDATION_ERROR ↔
563
- * UNPROCESSABLE_ENTITY, CONTENT_TOO_LARGE ↔ PAYLOAD_TOO_LARGE) so consumer
564
- * UI / SDK code can switch on the unified envelope.
565
- *
566
- * Subclasses override to populate `ext` (see `ActionInputError.envelope`).
567
- */
568
- get envelope() {
569
- return {
570
- code: _ActionError.toTheoErrorCode(this.code),
571
- message: this.message
572
- };
573
- }
574
- /**
575
- * Translate G3 ActionErrorCode → canonical TheoErrorCode (blueprint
576
- * Recommendations § "G3 ActionError becomes inaugural envelope user").
577
- */
578
- static toTheoErrorCode(code) {
579
- if (code === "VALIDATION_ERROR") return "UNPROCESSABLE_ENTITY";
580
- if (code === "CONTENT_TOO_LARGE") return "PAYLOAD_TOO_LARGE";
581
- return code;
582
- }
583
- static codeToStatus(code) {
584
- return CODE_TO_STATUS[code];
585
- }
586
- static statusToCode(status) {
587
- return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
588
- }
589
- /**
590
- * Parse a serialized error JSON back into the typed class hierarchy.
591
- * Distinguishes `TheoActionInputError` (with `issues` array) from
592
- * `TheoActionError` via the `type` discriminator. Falls back to
593
- * `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
594
- * `type`, unknown `code`).
595
- */
596
- static fromJson(body) {
597
- if (typeof body !== "object" || body === null) {
598
- return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
599
- }
600
- const obj = body;
601
- if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
602
- return new ActionInputError(obj.issues);
603
- }
604
- if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
605
- return new _ActionError({
606
- code: obj.code,
607
- message: typeof obj.message === "string" ? obj.message : void 0
608
- });
609
- }
610
- return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
611
- }
612
- };
613
- var ActionInputError = class extends ActionError {
614
- type = "TheoActionInputError";
615
- issues;
616
- fields;
617
- constructor(rawIssues) {
618
- super({ code: "VALIDATION_ERROR", message: "Validation failed" });
619
- this.issues = extractUniversalIssues(rawIssues);
620
- this.fields = buildFieldsMap(this.issues);
621
- }
622
- /**
623
- * G5 T2.4 — envelope view with ValidationFieldsExt populated from .fields.
624
- * UI consumers can `switch (env.code)` on `UNPROCESSABLE_ENTITY` and read
625
- * `(env.ext as ValidationFieldsExt).fields` for field-level rendering.
626
- */
627
- get envelope() {
628
- return {
629
- code: "UNPROCESSABLE_ENTITY",
630
- message: this.message,
631
- ext: { fields: this.fields }
632
- };
633
- }
634
- };
635
- function buildFieldsMap(issues) {
636
- const fields = {};
637
- const seen = /* @__PURE__ */ new Set();
638
- for (const issue of issues) {
639
- const key = issue.path.length === 0 ? "" : issue.path.join(".");
640
- const dedupeKey = `${key}\0${issue.message}`;
641
- if (seen.has(dedupeKey)) continue;
642
- seen.add(dedupeKey);
643
- const bucket = fields[key] ?? [];
644
- bucket.push(issue.message);
645
- fields[key] = bucket;
646
- }
647
- return fields;
648
- }
649
- function extractUniversalIssues(raw) {
650
- if (!Array.isArray(raw)) return [];
651
- const out = [];
652
- for (const entry of raw) {
653
- if (typeof entry !== "object" || entry === null) continue;
654
- const obj = entry;
655
- if (!Array.isArray(obj.path)) continue;
656
- if (typeof obj.message !== "string") continue;
657
- const path = [];
658
- let pathValid = true;
659
- for (const seg of obj.path) {
660
- if (typeof seg === "string" || typeof seg === "number") {
661
- path.push(seg);
662
- } else {
663
- pathValid = false;
664
- break;
665
- }
666
- }
667
- if (!pathValid) continue;
668
- out.push({
669
- path,
670
- message: obj.message,
671
- code: typeof obj.code === "string" ? obj.code : void 0
672
- });
673
- }
674
- return out;
675
- }
676
-
677
678
  // src/server/http/form-data-to-object.ts
678
679
  import { z } from "zod";
679
680
  function formDataToObject(formData, schema, prefix = "") {
@@ -1183,6 +1184,188 @@ async function handleActionError(err, c) {
1183
1184
  });
1184
1185
  }
1185
1186
 
1187
+ // src/server/http/batch-handler.ts
1188
+ import { z as z2 } from "zod";
1189
+ var STRIPPED_HEADERS = [
1190
+ "authorization",
1191
+ "cookie",
1192
+ "x-forwarded-for",
1193
+ "x-forwarded-host",
1194
+ "x-forwarded-proto",
1195
+ "x-real-ip",
1196
+ "host"
1197
+ ];
1198
+ var BATCH_PATH = "/api/__theo_batch__";
1199
+ var DEFAULT_MAX_BATCH = 32;
1200
+ var batchRequestSchema = z2.object({
1201
+ path: z2.string().min(1),
1202
+ method: z2.string().min(1),
1203
+ query: z2.record(z2.string(), z2.unknown()).optional(),
1204
+ body: z2.unknown().optional(),
1205
+ headers: z2.record(z2.string(), z2.string()).optional()
1206
+ });
1207
+ var batchPayloadSchema = z2.object({
1208
+ requests: z2.array(batchRequestSchema).min(1)
1209
+ });
1210
+ function sanitizeItemHeaders(itemHeaders, outerHeaders) {
1211
+ const out = {};
1212
+ if (itemHeaders) {
1213
+ for (const [k, v] of Object.entries(itemHeaders)) {
1214
+ const lower = k.toLowerCase();
1215
+ if (!STRIPPED_HEADERS.includes(lower)) {
1216
+ out[lower] = v;
1217
+ }
1218
+ }
1219
+ }
1220
+ if (outerHeaders) {
1221
+ for (const stripped of STRIPPED_HEADERS) {
1222
+ if (Object.hasOwn(outerHeaders, stripped)) {
1223
+ out[stripped] = outerHeaders[stripped];
1224
+ }
1225
+ }
1226
+ }
1227
+ return out;
1228
+ }
1229
+ async function handleBatchRequest(payload, options) {
1230
+ const parsed = batchPayloadSchema.parse(payload);
1231
+ const max = options.max ?? DEFAULT_MAX_BATCH;
1232
+ if (parsed.requests.length > max) {
1233
+ throw new Error(`Batch size ${parsed.requests.length} exceeds max ${max}`);
1234
+ }
1235
+ const results = [];
1236
+ for (const item of parsed.requests) {
1237
+ const sanitized = {
1238
+ ...item,
1239
+ headers: sanitizeItemHeaders(item.headers, options.outerHeaders)
1240
+ };
1241
+ try {
1242
+ const r = await options.execute(sanitized);
1243
+ results.push(r);
1244
+ } catch (err) {
1245
+ const message = err instanceof Error ? err.message : String(err);
1246
+ results.push({ error: { message } });
1247
+ }
1248
+ }
1249
+ return { results };
1250
+ }
1251
+
1252
+ // src/server/http/cors.ts
1253
+ var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
1254
+ var DEFAULT_ALLOWED_HEADERS = ["Content-Type", "X-Theo-Action", "Authorization"];
1255
+ var DEFAULT_MAX_AGE = 600;
1256
+ function readOrigin(req) {
1257
+ const raw = req.headers.origin;
1258
+ if (raw === void 0) return void 0;
1259
+ if (Array.isArray(raw)) {
1260
+ return raw.find((v) => typeof v === "string" && v.length > 0);
1261
+ }
1262
+ return raw;
1263
+ }
1264
+ function matchesOrigin(origin, allowed) {
1265
+ if (allowed === "*") return true;
1266
+ if (typeof allowed === "string") return origin === allowed;
1267
+ if (allowed instanceof RegExp) {
1268
+ allowed.lastIndex = 0;
1269
+ return allowed.test(origin);
1270
+ }
1271
+ if (Array.isArray(allowed)) {
1272
+ for (const entry of allowed) {
1273
+ if (typeof entry === "string" && origin === entry) return true;
1274
+ if (entry instanceof RegExp) {
1275
+ entry.lastIndex = 0;
1276
+ if (entry.test(origin)) return true;
1277
+ }
1278
+ }
1279
+ return false;
1280
+ }
1281
+ if (typeof allowed === "function") {
1282
+ try {
1283
+ return allowed(origin);
1284
+ } catch {
1285
+ return false;
1286
+ }
1287
+ }
1288
+ return false;
1289
+ }
1290
+ function createCorsHandler(config) {
1291
+ const methods = (config.methods ?? DEFAULT_METHODS).slice();
1292
+ const allowedHeaders = (config.allowedHeaders ?? DEFAULT_ALLOWED_HEADERS).slice();
1293
+ const maxAge = String(config.maxAge ?? DEFAULT_MAX_AGE);
1294
+ const credentials = config.credentials === true;
1295
+ return {
1296
+ handlePreflight(req, res) {
1297
+ if (req.method !== "OPTIONS") return false;
1298
+ const acMethod = req.headers["access-control-request-method"];
1299
+ if (!acMethod) return false;
1300
+ const origin = readOrigin(req);
1301
+ if (!origin) return false;
1302
+ if (!matchesOrigin(origin, config.origins)) {
1303
+ res.statusCode = 403;
1304
+ res.end();
1305
+ return true;
1306
+ }
1307
+ res.setHeader("Access-Control-Allow-Origin", origin);
1308
+ res.setHeader("Access-Control-Allow-Methods", methods.join(", "));
1309
+ res.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
1310
+ res.setHeader("Access-Control-Max-Age", maxAge);
1311
+ res.setHeader("Vary", "Origin");
1312
+ if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
1313
+ res.statusCode = 204;
1314
+ res.end();
1315
+ return true;
1316
+ },
1317
+ applyHeaders(req, res) {
1318
+ const origin = readOrigin(req);
1319
+ if (!origin) return;
1320
+ if (!matchesOrigin(origin, config.origins)) return;
1321
+ res.setHeader("Access-Control-Allow-Origin", origin);
1322
+ res.setHeader("Vary", "Origin");
1323
+ if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
1324
+ if (config.exposedHeaders && config.exposedHeaders.length > 0) {
1325
+ res.setHeader("Access-Control-Expose-Headers", config.exposedHeaders.join(", "));
1326
+ }
1327
+ }
1328
+ };
1329
+ }
1330
+
1331
+ // src/server/http/trace-context.ts
1332
+ var TRACE_HEADER = "x-trace-id";
1333
+ var TRACE_PARENT_HEADER = "traceparent";
1334
+ var REQUEST_ID_HEADER = "x-request-id";
1335
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
1336
+ function parseTraceparent(value) {
1337
+ if (!value) return null;
1338
+ const m = TRACEPARENT_RE.exec(value);
1339
+ if (!m) return null;
1340
+ const traceId = m[1];
1341
+ if (/^0+$/.test(traceId)) return null;
1342
+ return traceId;
1343
+ }
1344
+ function pickHeader(value) {
1345
+ if (Array.isArray(value)) {
1346
+ for (const v of value) {
1347
+ if (typeof v === "string" && v.length > 0) return v;
1348
+ }
1349
+ return null;
1350
+ }
1351
+ if (typeof value === "string" && value.length > 0) return value;
1352
+ return null;
1353
+ }
1354
+ function resolveTraceIdFromHeaders(traceparent, requestId) {
1355
+ if (traceparent !== null) {
1356
+ const parsed = parseTraceparent(traceparent);
1357
+ if (parsed !== null) return parsed;
1358
+ }
1359
+ if (requestId !== null) return requestId;
1360
+ return globalThis.crypto.randomUUID();
1361
+ }
1362
+ function extractTraceId(req) {
1363
+ return resolveTraceIdFromHeaders(
1364
+ pickHeader(req.headers[TRACE_PARENT_HEADER]),
1365
+ pickHeader(req.headers[REQUEST_ID_HEADER])
1366
+ );
1367
+ }
1368
+
1186
1369
  // src/server/observability/suggest.ts
1187
1370
  function levenshtein(a, b) {
1188
1371
  const m = a.length;
@@ -1818,6 +2001,227 @@ function createProductionLoader() {
1818
2001
  };
1819
2002
  }
1820
2003
 
2004
+ // src/server/security/csp-report.ts
2005
+ var CSP_REPORT_PATH = "/__theo/csp-report";
2006
+ var MAX_BODY = 16 * 1024;
2007
+ function pickHeader2(value) {
2008
+ if (typeof value === "string") return value;
2009
+ if (Array.isArray(value) && value.length > 0) return value[0];
2010
+ return "";
2011
+ }
2012
+ function toStringSafe(value, fallback = "(missing)") {
2013
+ if (typeof value === "string") return value;
2014
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2015
+ return fallback;
2016
+ }
2017
+ function normalizeLegacy(raw) {
2018
+ return {
2019
+ blockedUrl: toStringSafe(raw["blocked-uri"]),
2020
+ documentUrl: toStringSafe(raw["document-uri"]),
2021
+ violatedDirective: toStringSafe(raw["violated-directive"]),
2022
+ effectiveDirective: typeof raw["effective-directive"] === "string" ? raw["effective-directive"] : void 0,
2023
+ originalPolicy: typeof raw["original-policy"] === "string" ? raw["original-policy"] : void 0,
2024
+ disposition: raw.disposition === "enforce" || raw.disposition === "report" ? raw.disposition : void 0,
2025
+ statusCode: typeof raw["status-code"] === "number" ? raw["status-code"] : void 0,
2026
+ sourceFile: typeof raw["source-file"] === "string" ? raw["source-file"] : void 0,
2027
+ lineNumber: typeof raw["line-number"] === "number" ? raw["line-number"] : void 0,
2028
+ columnNumber: typeof raw["column-number"] === "number" ? raw["column-number"] : void 0
2029
+ };
2030
+ }
2031
+ function normalizeNew(entry) {
2032
+ if (!entry || typeof entry !== "object") return null;
2033
+ const body = entry.body;
2034
+ if (!body || typeof body !== "object") return null;
2035
+ const b = body;
2036
+ return {
2037
+ blockedUrl: toStringSafe(b.blockedURL),
2038
+ documentUrl: toStringSafe(b.documentURL),
2039
+ violatedDirective: toStringSafe(b.violatedDirective),
2040
+ effectiveDirective: typeof b.effectiveDirective === "string" ? b.effectiveDirective : void 0,
2041
+ originalPolicy: typeof b.originalPolicy === "string" ? b.originalPolicy : void 0,
2042
+ disposition: b.disposition === "enforce" || b.disposition === "report" ? b.disposition : void 0,
2043
+ statusCode: typeof b.statusCode === "number" ? b.statusCode : void 0,
2044
+ sourceFile: typeof b.sourceFile === "string" ? b.sourceFile : void 0,
2045
+ lineNumber: typeof b.lineNumber === "number" ? b.lineNumber : void 0,
2046
+ columnNumber: typeof b.columnNumber === "number" ? b.columnNumber : void 0
2047
+ };
2048
+ }
2049
+ async function readBody(req, maxBytes) {
2050
+ return await new Promise((resolve, reject) => {
2051
+ const chunks = [];
2052
+ let total = 0;
2053
+ req.on("data", (chunk) => {
2054
+ total += chunk.length;
2055
+ if (total > maxBytes) {
2056
+ reject(new Error("body too large"));
2057
+ req.destroy();
2058
+ return;
2059
+ }
2060
+ chunks.push(chunk);
2061
+ });
2062
+ req.on("end", () => {
2063
+ resolve(Buffer.concat(chunks).toString("utf8"));
2064
+ });
2065
+ req.on("error", reject);
2066
+ });
2067
+ }
2068
+ async function handleCspReport(req, res, opts) {
2069
+ const ctHeader = req.headers["content-type"];
2070
+ const ct = pickHeader2(ctHeader);
2071
+ let raw;
2072
+ try {
2073
+ raw = await readBody(req, MAX_BODY);
2074
+ } catch {
2075
+ res.statusCode = 413;
2076
+ res.end();
2077
+ return;
2078
+ }
2079
+ let violations;
2080
+ try {
2081
+ if (ct.startsWith("application/csp-report")) {
2082
+ const parsed = JSON.parse(raw);
2083
+ const inner = parsed["csp-report"];
2084
+ if (!inner || typeof inner !== "object") {
2085
+ res.statusCode = 204;
2086
+ res.end();
2087
+ return;
2088
+ }
2089
+ violations = [normalizeLegacy(inner)];
2090
+ } else if (ct.startsWith("application/reports+json")) {
2091
+ const parsed = JSON.parse(raw);
2092
+ const entries = Array.isArray(parsed) ? parsed : [];
2093
+ violations = entries.map((e) => normalizeNew(e)).filter((v) => v !== null);
2094
+ } else {
2095
+ res.statusCode = 415;
2096
+ res.end();
2097
+ return;
2098
+ }
2099
+ } catch {
2100
+ res.statusCode = 400;
2101
+ res.end();
2102
+ return;
2103
+ }
2104
+ for (const v of violations) {
2105
+ safeAudit(opts.auditLogger, {
2106
+ action: "csp.violation",
2107
+ metadata: v
2108
+ });
2109
+ try {
2110
+ opts.devtoolsDispatcher?.onCspViolation?.(v);
2111
+ } catch {
2112
+ }
2113
+ try {
2114
+ opts.onViolation?.(v);
2115
+ } catch {
2116
+ }
2117
+ }
2118
+ res.statusCode = 204;
2119
+ res.end();
2120
+ }
2121
+
2122
+ // src/server/security/csrf-readiness-endpoint.ts
2123
+ var CSRF_READINESS_PATH = "/__theo/csrf-readiness";
2124
+ var CSRF_READINESS_RESET_PATH = "/__theo/csrf-readiness/reset";
2125
+ function originMatchesHost(req) {
2126
+ const origin = req.headers.origin;
2127
+ if (typeof origin !== "string") return false;
2128
+ const host = req.headers.host;
2129
+ if (typeof host !== "string") return false;
2130
+ try {
2131
+ const parsed = new URL(origin);
2132
+ return parsed.host === host;
2133
+ } catch {
2134
+ return false;
2135
+ }
2136
+ }
2137
+ function sendJson2(res, status, body) {
2138
+ res.writeHead(status, { "content-type": "application/json" });
2139
+ res.end(JSON.stringify(body));
2140
+ }
2141
+ function sendNoContent(res) {
2142
+ res.writeHead(204);
2143
+ res.end();
2144
+ }
2145
+ function sendError2(res, status, code, message) {
2146
+ res.writeHead(status, { "content-type": "application/json" });
2147
+ res.end(JSON.stringify({ error: { code, message } }));
2148
+ }
2149
+ async function handleCsrfReadiness(req, res, store) {
2150
+ const url = (req.url ?? "").split("?")[0];
2151
+ const method = req.method ?? "GET";
2152
+ if (url === CSRF_READINESS_PATH) {
2153
+ if (method === "GET") {
2154
+ sendJson2(res, 200, store.summary());
2155
+ return true;
2156
+ }
2157
+ sendError2(res, 405, "METHOD_NOT_ALLOWED", `Use GET on ${CSRF_READINESS_PATH}`);
2158
+ return true;
2159
+ }
2160
+ if (url === CSRF_READINESS_RESET_PATH) {
2161
+ if (method !== "POST") {
2162
+ sendError2(res, 405, "METHOD_NOT_ALLOWED", `Use POST on ${CSRF_READINESS_RESET_PATH}`);
2163
+ return true;
2164
+ }
2165
+ const hasHeader = req.headers["x-theo-action"] === "1";
2166
+ if (!hasHeader || !originMatchesHost(req)) {
2167
+ sendError2(res, 403, "CSRF_INVALID", "Reset requires X-Theo-Action: 1 + same-origin");
2168
+ return true;
2169
+ }
2170
+ store.reset();
2171
+ sendNoContent(res);
2172
+ return true;
2173
+ }
2174
+ return false;
2175
+ }
2176
+
2177
+ // src/server/security/csrf-readiness-store.ts
2178
+ var CsrfReadinessStore = class _CsrfReadinessStore {
2179
+ static MAX_ENTRIES = 1e3;
2180
+ entries = /* @__PURE__ */ new Map();
2181
+ keyFor(event) {
2182
+ return `${event.method}\0${event.path}\0${event.reason}`;
2183
+ }
2184
+ record(event) {
2185
+ const key = this.keyFor(event);
2186
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2187
+ const existing = this.entries.get(key);
2188
+ if (existing) {
2189
+ existing.count++;
2190
+ existing.lastSeen = now;
2191
+ return;
2192
+ }
2193
+ if (this.entries.size >= _CsrfReadinessStore.MAX_ENTRIES) {
2194
+ const firstKey = this.entries.keys().next().value;
2195
+ if (firstKey !== void 0) this.entries.delete(firstKey);
2196
+ }
2197
+ this.entries.set(key, { count: 1, firstSeen: now, lastSeen: now });
2198
+ }
2199
+ summary() {
2200
+ let total = 0;
2201
+ const routes = [];
2202
+ for (const [key, entry] of this.entries) {
2203
+ const [method, path, reason] = key.split("\0");
2204
+ routes.push({
2205
+ method,
2206
+ path,
2207
+ reason,
2208
+ count: entry.count,
2209
+ firstSeen: entry.firstSeen,
2210
+ lastSeen: entry.lastSeen
2211
+ });
2212
+ total += entry.count;
2213
+ }
2214
+ return {
2215
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2216
+ totalEvents: total,
2217
+ routes
2218
+ };
2219
+ }
2220
+ reset() {
2221
+ this.entries.clear();
2222
+ }
2223
+ };
2224
+
1821
2225
  // src/server/security/security-headers.ts
1822
2226
  var DEFAULT_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; report-uri /__theo/csp-report";
1823
2227
  var DEFAULT_PERMISSIONS_POLICY = "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()";
@@ -1903,6 +2307,11 @@ function generateNonce() {
1903
2307
  export {
1904
2308
  executeRoute,
1905
2309
  executeAction,
2310
+ BATCH_PATH,
2311
+ handleBatchRequest,
2312
+ createCorsHandler,
2313
+ TRACE_HEADER,
2314
+ extractTraceId,
1906
2315
  findSuggestion,
1907
2316
  createPluginRunnerFromConfig,
1908
2317
  createRateLimiter,
@@ -1918,8 +2327,12 @@ export {
1918
2327
  mountAgent,
1919
2328
  createViteLoader,
1920
2329
  createProductionLoader,
2330
+ CSP_REPORT_PATH,
2331
+ handleCspReport,
2332
+ handleCsrfReadiness,
2333
+ CsrfReadinessStore,
1921
2334
  buildSecurityHeaders,
1922
2335
  applySecurityHeaders,
1923
2336
  generateNonce
1924
2337
  };
1925
- //# sourceMappingURL=chunk-H6YBKIUN.js.map
2338
+ //# sourceMappingURL=chunk-ABUVJU3P.js.map