@rogatio/cli 5.20.1 → 6.0.1

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.
@@ -186,7 +186,6 @@ var LIMITS = Object.freeze({
186
186
  maxGroups: 64,
187
187
  maxRulesPerGroup: 256,
188
188
  maxRulesPerProject: 4096,
189
- maxOriginsPerScope: 32,
190
189
  maxIdLength: 64,
191
190
  maxLabelLength: 100,
192
191
  maxDescriptionLength: 1e3,
@@ -242,16 +241,7 @@ function normalizeSiteOrigin(value) {
242
241
  function isSiteOrigin(value) {
243
242
  return typeof value === "string" && normalizeSiteOrigin(value) !== null;
244
243
  }
245
- function compileUrlRegex(value) {
246
- if (typeof value !== "string" || value.length === 0 || value.length > LIMITS.maxUrlRegexLength)
247
- return null;
248
- try {
249
- return new RegExp(value);
250
- } catch {
251
- return null;
252
- }
253
- }
254
- var PROJECT_VERSION = 1;
244
+ var PROJECT_VERSION = 2;
255
245
  var RESOURCE_TYPES = Object.freeze([
256
246
  "main_frame",
257
247
  "sub_frame",
@@ -280,6 +270,296 @@ var HTTP_METHODS = Object.freeze([
280
270
  "CONNECT",
281
271
  "TRACE"
282
272
  ]);
273
+ function isPlainObject(value) {
274
+ const proto = Object.getPrototypeOf(value);
275
+ return value !== null && typeof value === "object" && !Array.isArray(value) && (proto === Object.prototype || proto === null);
276
+ }
277
+ function ownString(value) {
278
+ return typeof value === "string" ? value : null;
279
+ }
280
+ function ownStringArray(value) {
281
+ if (!Array.isArray(value)) return null;
282
+ const out = [];
283
+ for (let i = 0; i < value.length; i += 1) {
284
+ if (!Object.hasOwn(value, i)) return null;
285
+ const entry = value[i];
286
+ if (typeof entry !== "string") return null;
287
+ out.push(entry);
288
+ }
289
+ return out;
290
+ }
291
+ var INVALID_INPUT = "migration.invalid-input";
292
+ var MigrationError = class extends Error {
293
+ constructor() {
294
+ super(INVALID_INPUT);
295
+ this.name = "MigrationError";
296
+ }
297
+ };
298
+ function regexProvenInsideOrigins(urlRegex, effectiveOrigins) {
299
+ if (effectiveOrigins.size === 0) return false;
300
+ if (urlRegex.includes("|")) return false;
301
+ if (!urlRegex.startsWith("^")) return false;
302
+ let index = 1;
303
+ let schemes;
304
+ if (urlRegex.startsWith("https?://", index)) {
305
+ schemes = ["http", "https"];
306
+ index += "https?://".length;
307
+ } else if (urlRegex.startsWith("https://", index)) {
308
+ schemes = ["https"];
309
+ index += "https://".length;
310
+ } else if (urlRegex.startsWith("http://", index)) {
311
+ schemes = ["http"];
312
+ index += "http://".length;
313
+ } else {
314
+ return false;
315
+ }
316
+ let host = "";
317
+ while (index < urlRegex.length) {
318
+ const char = urlRegex[index];
319
+ if (char === "\\") {
320
+ if (urlRegex[index + 1] !== ".") return false;
321
+ host += ".";
322
+ index += 2;
323
+ continue;
324
+ }
325
+ if (char === "/" || char === ":" || char === "$") break;
326
+ if (!/^[A-Za-z0-9-]$/.test(char ?? "")) return false;
327
+ host += char;
328
+ index += 1;
329
+ }
330
+ if (!isLiteralHost(host)) return false;
331
+ let port = null;
332
+ if (urlRegex[index] === "/") {
333
+ } else if (urlRegex[index] === "$") {
334
+ } else if (urlRegex[index] === ":") {
335
+ index += 1;
336
+ const start = index;
337
+ while (index < urlRegex.length && (urlRegex[index] ?? "") >= "0" && (urlRegex[index] ?? "") <= "9") {
338
+ index += 1;
339
+ }
340
+ if (index === start) return false;
341
+ port = urlRegex.slice(start, index);
342
+ const portNumber = Number(port);
343
+ if (!Number.isInteger(portNumber) || portNumber < 1 || portNumber > 65535) {
344
+ return false;
345
+ }
346
+ if (urlRegex[index] !== "/" && urlRegex[index] !== "$") return false;
347
+ } else {
348
+ return false;
349
+ }
350
+ for (const scheme of schemes) {
351
+ const origin = normalizeSiteOrigin(
352
+ port === null ? `${scheme}://${host}` : `${scheme}://${host}:${port}`
353
+ );
354
+ if (origin === null || !effectiveOrigins.has(origin)) return false;
355
+ }
356
+ return true;
357
+ }
358
+ function isLiteralHost(host) {
359
+ if (host.length === 0 || host.startsWith(".") || host.endsWith(".") || host.includes("..")) {
360
+ return false;
361
+ }
362
+ if (/^(?:\d{1,3}\.){3}\d{1,3}$/.test(host)) {
363
+ const parts = host.split(".");
364
+ for (const part of parts) {
365
+ const value = Number(part);
366
+ if (!Number.isInteger(value) || value < 0 || value > 255) return false;
367
+ }
368
+ return true;
369
+ }
370
+ return /^(?:[A-Za-z0-9-]+\.)*[A-Za-z0-9-]+$/.test(host);
371
+ }
372
+ function effectiveOriginSet(groupOrigins, ruleOrigins) {
373
+ const set = /* @__PURE__ */ new Set();
374
+ for (const raw of [...groupOrigins, ...ruleOrigins]) {
375
+ const normalized = normalizeSiteOrigin(raw);
376
+ if (normalized !== null) set.add(normalized);
377
+ }
378
+ return set;
379
+ }
380
+ function migrateRule(raw, groupOrigins, groupIndex, ruleIndex, notices) {
381
+ if (!isPlainObject(raw)) return null;
382
+ const id2 = ownString(raw.id);
383
+ const name = ownString(raw.name);
384
+ const urlRegex = ownString(raw.urlRegex);
385
+ if (id2 === null || name === null || urlRegex === null) return null;
386
+ const ruleOrigins = ownStringArray(raw.origins);
387
+ if (ruleOrigins === null) return null;
388
+ const resourceTypes = raw.resourceTypes;
389
+ if (!Array.isArray(resourceTypes) || resourceTypes.length === 0) return null;
390
+ const priority = raw.priority;
391
+ if (typeof priority !== "number" || !Number.isInteger(priority)) return null;
392
+ const source = {
393
+ key: "url",
394
+ operator: "regex",
395
+ value: urlRegex
396
+ };
397
+ const effective = effectiveOriginSet(groupOrigins, ruleOrigins);
398
+ const path2 = `/groups/${groupIndex}/rules/${ruleIndex}`;
399
+ if (!regexProvenInsideOrigins(urlRegex, effective)) {
400
+ notices.push({
401
+ code: "migration.possible-scope-widen",
402
+ path: path2,
403
+ message: "dropping origins may widen this rule; source keeps the previous urlRegex bytes"
404
+ });
405
+ }
406
+ const rule = {
407
+ id: id2,
408
+ name,
409
+ source,
410
+ resourceTypes,
411
+ priority
412
+ };
413
+ for (const key of [
414
+ "method",
415
+ "type",
416
+ "redirect",
417
+ "action",
418
+ "headerDirection",
419
+ "headerOperation",
420
+ "headerName",
421
+ "headerValue",
422
+ "responseBody",
423
+ "requestBody",
424
+ "redactSensitiveInLogs"
425
+ ]) {
426
+ if (Object.hasOwn(raw, key)) {
427
+ const value = raw[key];
428
+ rule[key] = value === void 0 ? void 0 : structuredClone(value);
429
+ }
430
+ }
431
+ return rule;
432
+ }
433
+ function assertAcyclic(value, seen) {
434
+ if (value === null || typeof value !== "object") return;
435
+ if (seen.has(value)) throw new MigrationError();
436
+ seen.add(value);
437
+ if (Object.getOwnPropertySymbols(value).length > 0)
438
+ throw new MigrationError();
439
+ if (Array.isArray(value)) {
440
+ const lengthDesc = Object.getOwnPropertyDescriptor(value, "length");
441
+ if (lengthDesc === void 0 || !("value" in lengthDesc) || typeof lengthDesc.value !== "number" || !Number.isSafeInteger(lengthDesc.value) || lengthDesc.value < 0) {
442
+ throw new MigrationError();
443
+ }
444
+ const length = lengthDesc.value;
445
+ for (const key of Object.getOwnPropertyNames(value)) {
446
+ if (key === "length") continue;
447
+ if (!/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= length) {
448
+ throw new MigrationError();
449
+ }
450
+ }
451
+ for (let index = 0; index < length; index += 1) {
452
+ const desc = Object.getOwnPropertyDescriptor(value, String(index));
453
+ if (desc === void 0 || !("value" in desc) || desc.get || desc.set) {
454
+ throw new MigrationError();
455
+ }
456
+ assertAcyclic(desc.value, seen);
457
+ }
458
+ return;
459
+ }
460
+ const prototype = Object.getPrototypeOf(value);
461
+ if (prototype !== Object.prototype && prototype !== null) {
462
+ throw new MigrationError();
463
+ }
464
+ for (const key of Object.getOwnPropertyNames(value)) {
465
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
466
+ throw new MigrationError();
467
+ }
468
+ const desc = Object.getOwnPropertyDescriptor(value, key);
469
+ if (desc === void 0 || !("value" in desc) || desc.get || desc.set || !desc.enumerable) {
470
+ throw new MigrationError();
471
+ }
472
+ assertAcyclic(desc.value, seen);
473
+ }
474
+ }
475
+ function migrateV1Project(input) {
476
+ try {
477
+ assertAcyclic(input, /* @__PURE__ */ new WeakSet());
478
+ return migrateOwned(input);
479
+ } catch {
480
+ return { ok: false, error: INVALID_INPUT };
481
+ }
482
+ }
483
+ function migrateOwned(input) {
484
+ if (!isPlainObject(input)) {
485
+ return { ok: false, error: "migration.invalid-project" };
486
+ }
487
+ if (Object.getOwnPropertySymbols(input).length > 0) {
488
+ return { ok: false, error: "migration.invalid-project" };
489
+ }
490
+ const version = input.version;
491
+ if (version !== 1) {
492
+ return { ok: false, error: "migration.not-v1" };
493
+ }
494
+ const name = ownString(input.name);
495
+ if (name === null) {
496
+ return { ok: false, error: "migration.invalid-project" };
497
+ }
498
+ const groupsRaw = input.groups;
499
+ if (!Array.isArray(groupsRaw)) {
500
+ return { ok: false, error: "migration.invalid-project" };
501
+ }
502
+ const notices = [];
503
+ const groups = [];
504
+ for (let gi = 0; gi < groupsRaw.length; gi += 1) {
505
+ if (!Object.hasOwn(groupsRaw, gi)) {
506
+ return { ok: false, error: "migration.invalid-project" };
507
+ }
508
+ const groupRaw = groupsRaw[gi];
509
+ if (!isPlainObject(groupRaw)) {
510
+ return { ok: false, error: "migration.invalid-project" };
511
+ }
512
+ const groupId = ownString(groupRaw.id);
513
+ const groupName = ownString(groupRaw.name);
514
+ const groupOrigins = ownStringArray(groupRaw.origins);
515
+ const rulesRaw = groupRaw.rules;
516
+ if (groupId === null || groupName === null || groupOrigins === null || !Array.isArray(rulesRaw)) {
517
+ return { ok: false, error: "migration.invalid-project" };
518
+ }
519
+ const rules = [];
520
+ for (let ri = 0; ri < rulesRaw.length; ri += 1) {
521
+ if (!Object.hasOwn(rulesRaw, ri)) {
522
+ return { ok: false, error: "migration.invalid-project" };
523
+ }
524
+ const migrated = migrateRule(rulesRaw[ri], groupOrigins, gi, ri, notices);
525
+ if (migrated === null) {
526
+ return { ok: false, error: "migration.invalid-rule" };
527
+ }
528
+ rules.push(migrated);
529
+ }
530
+ groups.push({ id: groupId, name: groupName, rules });
531
+ }
532
+ notices.push({
533
+ code: "migration.initiator-policy",
534
+ path: "/",
535
+ message: "initiator must now be a present http(s) origin; the old origins allowlist no longer applies"
536
+ });
537
+ const project = {
538
+ version: PROJECT_VERSION,
539
+ name,
540
+ groups
541
+ };
542
+ if (Object.hasOwn(input, "description")) {
543
+ const description = ownString(input.description);
544
+ if (description === null) {
545
+ return { ok: false, error: "migration.invalid-project" };
546
+ }
547
+ project.description = description;
548
+ }
549
+ if (Object.hasOwn(input, "requestBodyPolicy")) {
550
+ project.requestBodyPolicy = input.requestBodyPolicy;
551
+ }
552
+ return { ok: true, project, notices };
553
+ }
554
+ function compileUrlRegex(value) {
555
+ if (typeof value !== "string" || value.length === 0 || value.length > LIMITS.maxUrlRegexLength)
556
+ return null;
557
+ try {
558
+ return new RegExp(value);
559
+ } catch {
560
+ return null;
561
+ }
562
+ }
283
563
  var label = {
284
564
  type: "string",
285
565
  minLength: 1,
@@ -294,7 +574,7 @@ var id = {
294
574
  };
295
575
  var projectSchemaDefinition = {
296
576
  $schema: "https://json-schema.org/draft/2020-12/schema",
297
- $id: "https://rogatio.dev/schema/project-v1.json",
577
+ $id: "https://rogatio.dev/schema/project-v2.json",
298
578
  type: "object",
299
579
  additionalProperties: false,
300
580
  required: ["version", "name", "groups"],
@@ -313,19 +593,28 @@ var projectSchemaDefinition = {
313
593
  requestBodyPolicy: { $ref: "#/$defs/requestBodyPolicyConfig" }
314
594
  },
315
595
  $defs: {
596
+ sourceCondition: {
597
+ type: "object",
598
+ additionalProperties: false,
599
+ required: ["key", "operator", "value"],
600
+ properties: {
601
+ key: { type: "string", enum: ["url", "host"] },
602
+ operator: { type: "string", const: "regex" },
603
+ value: {
604
+ type: "string",
605
+ minLength: 1,
606
+ maxLength: LIMITS.maxUrlRegexLength,
607
+ format: "rogatio-url-regex"
608
+ }
609
+ }
610
+ },
316
611
  group: {
317
612
  type: "object",
318
613
  additionalProperties: false,
319
- required: ["id", "name", "origins", "rules"],
614
+ required: ["id", "name", "rules"],
320
615
  properties: {
321
616
  id,
322
617
  name: label,
323
- origins: {
324
- type: "array",
325
- maxItems: LIMITS.maxOriginsPerScope,
326
- uniqueItems: true,
327
- items: { type: "string", format: "rogatio-origin" }
328
- },
329
618
  rules: {
330
619
  type: "array",
331
620
  maxItems: LIMITS.maxRulesPerGroup,
@@ -336,29 +625,11 @@ var projectSchemaDefinition = {
336
625
  rule: {
337
626
  type: "object",
338
627
  additionalProperties: false,
339
- required: [
340
- "id",
341
- "name",
342
- "urlRegex",
343
- "origins",
344
- "resourceTypes",
345
- "priority"
346
- ],
628
+ required: ["id", "name", "source", "resourceTypes", "priority"],
347
629
  properties: {
348
630
  id,
349
631
  name: label,
350
- urlRegex: {
351
- type: "string",
352
- minLength: 1,
353
- maxLength: LIMITS.maxUrlRegexLength,
354
- format: "rogatio-url-regex"
355
- },
356
- origins: {
357
- type: "array",
358
- maxItems: LIMITS.maxOriginsPerScope,
359
- uniqueItems: true,
360
- items: { type: "string", format: "rogatio-origin" }
361
- },
632
+ source: { $ref: "#/$defs/sourceCondition" },
362
633
  resourceTypes: {
363
634
  type: "array",
364
635
  minItems: 1,
@@ -855,7 +1126,7 @@ function semanticIssues(project) {
855
1126
  const destinationPath = `${rulePath}/redirect/destination`;
856
1127
  for (const issue of validateRedirectDestination(
857
1128
  destination,
858
- rule.urlRegex
1129
+ rule.source.value
859
1130
  )) {
860
1131
  issues.push({
861
1132
  instancePath: destinationPath,
@@ -867,29 +1138,12 @@ function semanticIssues(project) {
867
1138
  issues.push(
868
1139
  ...captureValidationIssues(
869
1140
  destination,
870
- rule.urlRegex,
1141
+ rule.source.value,
871
1142
  destinationPath
872
1143
  )
873
1144
  );
874
1145
  }
875
1146
  }
876
- const effectiveOrigins = /* @__PURE__ */ new Set();
877
- for (let originIndex = 0; originIndex < group.origins.length; originIndex += 1) {
878
- const origin = normalizeSiteOrigin(group.origins[originIndex]);
879
- if (origin !== null) effectiveOrigins.add(origin);
880
- }
881
- for (let originIndex = 0; originIndex < rule.origins.length; originIndex += 1) {
882
- const origin = normalizeSiteOrigin(rule.origins[originIndex]);
883
- if (origin !== null) effectiveOrigins.add(origin);
884
- }
885
- if (effectiveOrigins.size === 0) {
886
- issues.push({
887
- instancePath: `${rulePath}/origins`,
888
- keyword: "effectiveOrigin",
889
- message: "must combine with group origins to contain at least one origin",
890
- params: {}
891
- });
892
- }
893
1147
  const action = rule.action;
894
1148
  if (action && "type" in action && action.type === "query") {
895
1149
  const seenNames = /* @__PURE__ */ new Set();
@@ -920,7 +1174,7 @@ function semanticIssues(project) {
920
1174
  issues.push(
921
1175
  ...captureValidationIssues(
922
1176
  param.value,
923
- rule.urlRegex,
1177
+ rule.source.value,
924
1178
  `${rulePath}/action/params/${p}/value`
925
1179
  )
926
1180
  );
@@ -952,7 +1206,7 @@ function semanticIssues(project) {
952
1206
  issues.push(
953
1207
  ...captureValidationIssues(
954
1208
  rule.headerValue,
955
- rule.urlRegex,
1209
+ rule.source.value,
956
1210
  `${rulePath}/headerValue`
957
1211
  )
958
1212
  );
@@ -994,7 +1248,7 @@ function semanticIssues(project) {
994
1248
  issues.push(
995
1249
  ...captureValidationIssues(
996
1250
  action2.body,
997
- rule.urlRegex,
1251
+ rule.source.value,
998
1252
  `${actionPath}/body`
999
1253
  )
1000
1254
  );
@@ -1075,7 +1329,7 @@ function semanticIssues(project) {
1075
1329
  issues.push(
1076
1330
  ...captureValidationIssues(
1077
1331
  action2.body,
1078
- rule.urlRegex,
1332
+ rule.source.value,
1079
1333
  `${actionPath}/body`
1080
1334
  )
1081
1335
  );
@@ -1348,18 +1602,6 @@ function validateProjectDetailed(value) {
1348
1602
  return errors.length > 0 ? { valid: false, errors } : { valid: true, data: value };
1349
1603
  }
1350
1604
 
1351
- // packages/runtime/dist/node/index.js
1352
- import { isIP as isIP2 } from "node:net";
1353
- import { Buffer as Buffer2 } from "node:buffer";
1354
- import { existsSync } from "node:fs";
1355
- import { createServer as createServer2 } from "node:http";
1356
- import { join as join3 } from "node:path";
1357
- import {
1358
- createServer
1359
- } from "node:http";
1360
- import { connect } from "node:net";
1361
- import { Worker } from "node:worker_threads";
1362
-
1363
1605
  // packages/compiler/dist/node/index.js
1364
1606
  var ISSUE_CODES = {
1365
1607
  required: "schema.required",
@@ -1378,7 +1620,6 @@ var ISSUE_CODES = {
1378
1620
  uniqueItems: "schema.out-of-range",
1379
1621
  ownProperties: "schema.invalid-structure",
1380
1622
  uniqueId: "schema.duplicate-id",
1381
- effectiveOrigin: "schema.no-effective-origin",
1382
1623
  maxRulesPerProject: "schema.rule-limit"
1383
1624
  };
1384
1625
  var SAFE_PARAM_KEYS = /* @__PURE__ */ new Set([
@@ -1406,7 +1647,6 @@ var MESSAGES = {
1406
1647
  "schema.out-of-range": "The project contains a value outside its allowed bounds.",
1407
1648
  "schema.invalid-structure": "The project contains invalid structure.",
1408
1649
  "schema.duplicate-id": "Project and rule IDs must be unique.",
1409
- "schema.no-effective-origin": "Each rule must have at least one effective origin.",
1410
1650
  "schema.rule-limit": "The project contains too many rules.",
1411
1651
  "compiler.invariant": "The compiler could not normalize validated project data.",
1412
1652
  "compiler.forbidden-header": "The header name is forbidden for this direction.",
@@ -1472,11 +1712,6 @@ function invariantDiagnostic(path2 = "") {
1472
1712
  params: {}
1473
1713
  };
1474
1714
  }
1475
- function compareCodeUnits2(left, right) {
1476
- if (left < right) return -1;
1477
- if (left > right) return 1;
1478
- return 0;
1479
- }
1480
1715
  function snapshotOwnData2(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1481
1716
  if (value === null || typeof value !== "object") {
1482
1717
  return { valid: true, value };
@@ -1537,33 +1772,6 @@ function snapshotOwnData2(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1537
1772
  ancestors.delete(value);
1538
1773
  }
1539
1774
  }
1540
- var CompilerInvariantError = class extends Error {
1541
- constructor(path2) {
1542
- super("invalid normalized origin");
1543
- this.path = path2;
1544
- this.name = "CompilerInvariantError";
1545
- }
1546
- path;
1547
- };
1548
- function normalizeOrigins(group, rule, groupIndex, ruleIndex) {
1549
- const origins = /* @__PURE__ */ new Set();
1550
- const sourceArrays = [group.origins, rule.origins];
1551
- for (let sourceIndex = 0; sourceIndex < sourceArrays.length; sourceIndex += 1) {
1552
- const source = sourceArrays[sourceIndex];
1553
- for (let originIndex = 0; originIndex < source.length; originIndex += 1) {
1554
- const normalized = normalizeSiteOrigin(source[originIndex]);
1555
- if (normalized === null) {
1556
- const path2 = sourceIndex === 0 ? `/groups/${groupIndex}/origins/${originIndex}` : `/groups/${groupIndex}/rules/${ruleIndex}/origins/${originIndex}`;
1557
- throw new CompilerInvariantError(path2);
1558
- }
1559
- origins.add(normalized);
1560
- }
1561
- }
1562
- const normalizedOrigins = [];
1563
- for (const origin of origins) normalizedOrigins.push(origin);
1564
- normalizedOrigins.sort(compareCodeUnits2);
1565
- return normalizedOrigins;
1566
- }
1567
1775
  function canonicalResourceTypes(resourceTypes) {
1568
1776
  const ordered = [];
1569
1777
  for (let canonicalIndex = 0; canonicalIndex < RESOURCE_TYPES.length; canonicalIndex += 1) {
@@ -1577,10 +1785,13 @@ function canonicalResourceTypes(resourceTypes) {
1577
1785
  function resolveRedactSensitiveInLogs(rule) {
1578
1786
  return rule.redactSensitiveInLogs === true;
1579
1787
  }
1580
- function compileMatcher(group, rule, groupIndex, ruleIndex) {
1788
+ function compileMatcher(rule) {
1581
1789
  const matcher = {
1582
- urlRegex: { source: rule.urlRegex, flags: "" },
1583
- origins: normalizeOrigins(group, rule, groupIndex, ruleIndex),
1790
+ source: {
1791
+ key: rule.source.key,
1792
+ operator: "regex",
1793
+ value: rule.source.value
1794
+ },
1584
1795
  resourceTypes: canonicalResourceTypes(rule.resourceTypes),
1585
1796
  priority: rule.priority
1586
1797
  };
@@ -1593,7 +1804,7 @@ function compileOperations(project) {
1593
1804
  const group = project.groups[groupIndex];
1594
1805
  for (let ruleIndex = 0; ruleIndex < group.rules.length; ruleIndex += 1) {
1595
1806
  const rule = group.rules[ruleIndex];
1596
- const matcher = compileMatcher(group, rule, groupIndex, ruleIndex);
1807
+ const matcher = compileMatcher(rule);
1597
1808
  const redactSensitiveInLogs = resolveRedactSensitiveInLogs(rule);
1598
1809
  if (rule.type === "redirect") {
1599
1810
  const operation = {
@@ -1706,17 +1917,46 @@ function compileProject(value) {
1706
1917
  operations: compileOperations(validation.data),
1707
1918
  diagnostics: []
1708
1919
  };
1709
- } catch (error) {
1710
- const path2 = error instanceof CompilerInvariantError ? error.path : "";
1920
+ } catch {
1711
1921
  return {
1712
1922
  ok: false,
1713
1923
  operations: [],
1714
- diagnostics: [invariantDiagnostic(path2)]
1924
+ diagnostics: [invariantDiagnostic()]
1715
1925
  };
1716
1926
  }
1717
1927
  }
1928
+ function sourceMatches(source, url) {
1929
+ const regex = compileUrlRegex(source.value);
1930
+ if (regex === null) return false;
1931
+ if (source.key === "url") {
1932
+ return regex.test(url);
1933
+ }
1934
+ try {
1935
+ const hostname = new URL(url).hostname;
1936
+ return regex.test(hostname);
1937
+ } catch {
1938
+ return false;
1939
+ }
1940
+ }
1941
+ function sameOrigin(a, b) {
1942
+ try {
1943
+ return new URL(a).origin === new URL(b).origin;
1944
+ } catch {
1945
+ return false;
1946
+ }
1947
+ }
1718
1948
 
1719
1949
  // packages/runtime/dist/node/index.js
1950
+ import { isIP as isIP2 } from "node:net";
1951
+ import { Buffer as Buffer2 } from "node:buffer";
1952
+ import { existsSync } from "node:fs";
1953
+ import { createServer as createServer2 } from "node:http";
1954
+ import { join as join3 } from "node:path";
1955
+ import {
1956
+ createServer
1957
+ } from "node:http";
1958
+ import { connect } from "node:net";
1959
+ import { Worker } from "node:worker_threads";
1720
1960
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
1721
1961
  import { randomBytes as randomBytes2 } from "node:crypto";
1722
1962
  import { readFile as readFile2, realpath as realpath2, stat } from "node:fs/promises";
@@ -20687,19 +20927,15 @@ function buildSystemPrompt(project) {
20687
20927
  const projectJson = formatProjectContext(project);
20688
20928
  return `You are an expert Rogatio rule author. Rogatio is a local-first browser request/response rule engine.
20689
20929
 
20690
- ## Schema Constraants
20930
+ ## Schema Constraints (version 2)
20691
20931
 
20692
- ### URL Regular Expression
20693
- - Maximum 2048 characters
20694
- - Case-sensitive, no flags
20695
- - Must be valid ECMAScript regex
20696
- - Used to match request URLs
20932
+ ### Source condition
20933
+ Each rule matches with \`source: { key, operator, value }\`.
20934
+ - \`key\`: \`"url"\` tests the full request URL; \`"host"\` tests the hostname only.
20935
+ - \`operator\`: must be the constant \`"regex"\`.
20936
+ - \`value\`: non-empty ECMAScript regex, max 2048 characters, case-sensitive, no flags.
20697
20937
 
20698
- ### Origins
20699
- - Only explicit http:// and https:// origins with hostname
20700
- - Optional port (e.g., https://example.com:8080)
20701
- - No wildcards, credentials, paths, query strings, or fragments
20702
- - Effective origins are union of project, group, and rule origins
20938
+ There are no group or rule \`origins\` fields and no \`urlRegex\` field.
20703
20939
 
20704
20940
  ### Forbidden Headers
20705
20941
  Cannot set/remove these headers:
@@ -20715,14 +20951,13 @@ ${formatHttpMethods()}
20715
20951
  - Max 64 projects per profile
20716
20952
  - Max 100 groups per project
20717
20953
  - Max 500 rules per project
20718
- - Max 10 origins per group/rule
20719
20954
 
20720
20955
  ## Rule Types
20721
20956
 
20722
- Each rule has: id, name, urlRegex, origins[], resourceTypes[], priority, method?, action
20957
+ Each rule has: id, name, source, resourceTypes[], priority, method?, action
20723
20958
 
20724
20959
  ### redirect
20725
- Action: destination string. Use $1 through $9 for URL regex captures. $$ is a literal dollar sign. Existing redirect \\1 references remain compatible.
20960
+ Action: destination string. Use $1 through $9 for URL source captures when \`source.key\` is \`"url"\`. $$ is a literal dollar sign. Existing redirect \\\\1 references remain compatible.
20726
20961
 
20727
20962
  ### query
20728
20963
  Action: query parameters with name, operation, and value. Set values may use URL captures.
@@ -20731,15 +20966,14 @@ Action: query parameters with name, operation, and value. Set values may use URL
20731
20966
  Action: header direction, operation, name, and value. Set and append values may use URL captures.
20732
20967
 
20733
20968
  ### response-body
20734
- Replace-mode body may use URL captures. Regex-mode replacement strings use captures from the body pattern, not the URL pattern.
20969
+ Replace-mode body may use URL captures. Regex-mode replacement strings use captures from the body pattern, not the source pattern.
20735
20970
 
20736
20971
  ### request-body
20737
- Replace-mode body may use URL captures. Regex-mode replacement strings use captures from the body pattern, not the URL pattern.
20972
+ Replace-mode body may use URL captures. Regex-mode replacement strings use captures from the body pattern, not the source pattern.
20738
20973
 
20739
20974
  ## Compiler Diagnostic Codes (stable)
20740
20975
  - schema.invalid-regex - Invalid regex syntax
20741
20976
  - schema.duplicate-id - Duplicate group/rule ID
20742
- - schema.invalid-origin - Invalid origin format
20743
20977
  - schema.invalid-resource-type - Unknown resource type
20744
20978
  - schema.invalid-method - Unknown HTTP method
20745
20979
  - schema.regex-too-long - Regex exceeds 2048 chars
@@ -20753,12 +20987,17 @@ ${projectJson}
20753
20987
  Return ONLY valid JSON matching this TypeScript interface:
20754
20988
 
20755
20989
  \`\`\`typescript
20990
+ interface SourceProposal {
20991
+ key: "url" | "host";
20992
+ operator: "regex";
20993
+ value: string;
20994
+ }
20995
+
20756
20996
  interface RuleProposal {
20757
20997
  kind: "redirect" | "query" | "header" | "response-body" | "request-body";
20758
20998
  groupId: string;
20759
20999
  name: string;
20760
- urlRegex: string;
20761
- origins?: string[];
21000
+ source: SourceProposal;
20762
21001
  resourceTypes?: string[];
20763
21002
  priority?: number;
20764
21003
  method?: string;
@@ -20775,13 +21014,13 @@ interface AIProposal {
20775
21014
 
20776
21015
  1. Propose MINIMAL changes - only what the user asked for
20777
21016
  2. Use existing groups when possible; create new only if needed
20778
- 3. All regex must be valid ECMAScript
20779
- 4. All origins must be valid http/https with hostname
21017
+ 3. All source.value regex must be valid ECMAScript
21018
+ 4. source.operator must be exactly "regex"
20780
21019
  5. Resource types must be from the allowed list
20781
21020
  6. Methods must be from the allowed list
20782
21021
  7. Priority: higher = more specific (100 default)
20783
21022
  8. Action object must match the rule kind schema exactly
20784
- 9. Use $1\u2013$9 only when urlRegex defines those capture groups; use $$ for a literal dollar sign
21023
+ 9. Use $1\u2013$9 only when source defines those capture groups; use $$ for a literal dollar sign
20785
21024
  10. Before proposing, mentally validate against dry-run: would this rule match the intended URLs?
20786
21025
  11. Explanation should be 1-2 sentences describing what the rules do
20787
21026
  `;
@@ -20790,7 +21029,7 @@ var MAX_FIX_ATTEMPTS = 3;
20790
21029
  function isRecord(value) {
20791
21030
  return typeof value === "object" && value !== null && !Array.isArray(value);
20792
21031
  }
20793
- function ownString(record, ...keys) {
21032
+ function ownString2(record, ...keys) {
20794
21033
  for (const key of keys) {
20795
21034
  if (!Object.hasOwn(record, key)) continue;
20796
21035
  const value = record[key];
@@ -20802,8 +21041,11 @@ function ruleFromProposal(proposal, ruleId) {
20802
21041
  const rule = {
20803
21042
  id: ruleId,
20804
21043
  name: proposal.name,
20805
- urlRegex: proposal.urlRegex,
20806
- origins: proposal.origins ? [...proposal.origins] : [],
21044
+ source: {
21045
+ key: proposal.source.key,
21046
+ operator: "regex",
21047
+ value: proposal.source.value
21048
+ },
20807
21049
  resourceTypes: proposal.resourceTypes ? [...proposal.resourceTypes] : ["main_frame"],
20808
21050
  priority: proposal.priority ?? 100
20809
21051
  };
@@ -20822,18 +21064,18 @@ function ruleFromProposal(proposal, ruleId) {
20822
21064
  case "header": {
20823
21065
  rule.type = "header";
20824
21066
  if (isRecord(proposal.action)) {
20825
- const direction = ownString(
21067
+ const direction = ownString2(
20826
21068
  proposal.action,
20827
21069
  "headerDirection",
20828
21070
  "direction"
20829
21071
  );
20830
- const operation = ownString(
21072
+ const operation = ownString2(
20831
21073
  proposal.action,
20832
21074
  "headerOperation",
20833
21075
  "operation"
20834
21076
  );
20835
- const name = ownString(proposal.action, "headerName", "name");
20836
- const value = ownString(proposal.action, "headerValue", "value");
21077
+ const name = ownString2(proposal.action, "headerName", "name");
21078
+ const value = ownString2(proposal.action, "headerValue", "value");
20837
21079
  if (direction !== void 0) rule.headerDirection = direction;
20838
21080
  if (operation !== void 0) rule.headerOperation = operation;
20839
21081
  if (name !== void 0) rule.headerName = name;
@@ -20859,7 +21101,7 @@ function ruleFromProposal(proposal, ruleId) {
20859
21101
  function mergeProposalIntoProject(project, proposal) {
20860
21102
  const groups = Array.isArray(project.groups) ? project.groups.map((group) => {
20861
21103
  if (!isRecord(group)) {
20862
- return { id: "invalid", name: "invalid", origins: [], rules: [] };
21104
+ return { id: "invalid", name: "invalid", rules: [] };
20863
21105
  }
20864
21106
  return {
20865
21107
  ...group,
@@ -20875,7 +21117,6 @@ function mergeProposalIntoProject(project, proposal) {
20875
21117
  group = {
20876
21118
  id: ruleProposal.groupId,
20877
21119
  name: "AI Group",
20878
- origins: [],
20879
21120
  rules: []
20880
21121
  };
20881
21122
  groups.push(group);
@@ -20923,18 +21164,29 @@ function stringArray(value) {
20923
21164
  );
20924
21165
  return items.length === value.length ? items : void 0;
20925
21166
  }
21167
+ function parseSourceProposal(value) {
21168
+ if (!isRecord(value)) return null;
21169
+ if (value.key !== "url" && value.key !== "host") return null;
21170
+ if (value.operator !== "regex") return null;
21171
+ if (typeof value.value !== "string" || value.value.length === 0) return null;
21172
+ return {
21173
+ key: value.key,
21174
+ operator: "regex",
21175
+ value: value.value
21176
+ };
21177
+ }
20926
21178
  function parseProposalRule(value) {
20927
21179
  if (!isRecord(value)) return null;
20928
21180
  const kind = value.kind;
20929
- if (typeof kind !== "string" || !RULE_KINDS.has(kind) || typeof value.groupId !== "string" || typeof value.name !== "string" || typeof value.urlRegex !== "string" || !Object.hasOwn(value, "action")) {
21181
+ const source = parseSourceProposal(value.source);
21182
+ if (typeof kind !== "string" || !RULE_KINDS.has(kind) || typeof value.groupId !== "string" || typeof value.name !== "string" || source === null || !Object.hasOwn(value, "action")) {
20930
21183
  return null;
20931
21184
  }
20932
21185
  return {
20933
21186
  kind,
20934
21187
  groupId: value.groupId,
20935
21188
  name: value.name,
20936
- urlRegex: value.urlRegex,
20937
- origins: stringArray(value.origins),
21189
+ source,
20938
21190
  resourceTypes: stringArray(value.resourceTypes),
20939
21191
  priority: typeof value.priority === "number" ? value.priority : void 0,
20940
21192
  method: typeof value.method === "string" ? value.method : void 0,
@@ -20944,7 +21196,7 @@ function parseProposalRule(value) {
20944
21196
  function repairProposalIntoProject(project, diagnostics, proposal) {
20945
21197
  const groups = Array.isArray(project.groups) ? project.groups.map((group) => {
20946
21198
  if (!isRecord(group)) {
20947
- return { id: "invalid", name: "invalid", origins: [], rules: [] };
21199
+ return { id: "invalid", name: "invalid", rules: [] };
20948
21200
  }
20949
21201
  return {
20950
21202
  ...group,
@@ -20985,7 +21237,6 @@ function repairProposalIntoProject(project, diagnostics, proposal) {
20985
21237
  group = {
20986
21238
  id: ruleProposal.groupId,
20987
21239
  name: "AI Group",
20988
- origins: [],
20989
21240
  rules: []
20990
21241
  };
20991
21242
  groups.push(group);
@@ -21334,14 +21585,6 @@ function canonicalizeOutboundTarget(value) {
21334
21585
  if (canonical.includes("#") || canonical.includes("@")) return null;
21335
21586
  return canonical;
21336
21587
  }
21337
- function targetOrigin(value) {
21338
- const origin = normalizeSiteOrigin(new URL(value).origin);
21339
- return origin;
21340
- }
21341
- function isOriginAllowed(target, origins) {
21342
- const origin = targetOrigin(target);
21343
- return origin !== null && origins.includes(origin);
21344
- }
21345
21588
  function validMethod(value) {
21346
21589
  return typeof value === "string" && HTTP_METHODS.includes(value);
21347
21590
  }
@@ -21373,9 +21616,12 @@ function canonicalTarget(kind, target) {
21373
21616
  function matchesGrant(descriptor, grant) {
21374
21617
  return descriptor.groupId === grant.groupId && descriptor.ruleId === grant.ruleId && descriptor.operationId === grant.operationId && descriptor.kind === grant.kind && descriptor.target === grant.target && descriptor.method === grant.method;
21375
21618
  }
21376
- function authorizeExact(preset, value) {
21619
+ function authorizeExact(preset, value, requestUrl) {
21377
21620
  const descriptor = exactDescriptor(value);
21378
21621
  if (descriptor === null) return failure("runtime.authorization-denied");
21622
+ if (typeof requestUrl !== "string" || requestUrl.length === 0) {
21623
+ return failure("runtime.authorization-denied");
21624
+ }
21379
21625
  const target = canonicalTarget(descriptor.kind, descriptor.target);
21380
21626
  if (target === null) return failure("runtime.authorization-denied");
21381
21627
  for (const grant of preset.grants) {
@@ -21383,7 +21629,10 @@ function authorizeExact(preset, value) {
21383
21629
  const matcher = preset.matchers.find(
21384
21630
  (candidate) => candidate.groupId === grant.groupId && candidate.ruleId === grant.ruleId
21385
21631
  );
21386
- if (matcher === void 0 || grant.kind === "outbound-http" && !isOriginAllowed(grant.target, matcher.matcher.origins)) {
21632
+ if (matcher === void 0) {
21633
+ return failure("runtime.authorization-denied");
21634
+ }
21635
+ if (grant.kind === "outbound-http" && !sameOrigin(grant.target, requestUrl)) {
21387
21636
  return failure("runtime.authorization-denied");
21388
21637
  }
21389
21638
  return {
@@ -21395,7 +21644,7 @@ function authorizeExact(preset, value) {
21395
21644
  kind: grant.kind,
21396
21645
  target: grant.target,
21397
21646
  method: grant.method,
21398
- urlRegex: matcher.matcher.urlRegex.source,
21647
+ sourceValue: matcher.matcher.source.value,
21399
21648
  presetDigest: preset.digest
21400
21649
  })
21401
21650
  };
@@ -21432,7 +21681,7 @@ var RUNTIME_LIMITS = Object.freeze({
21432
21681
  });
21433
21682
  var PROTOCOL = "v1";
21434
21683
  var ENVELOPE_MAX_BYTES = 64 * 1024;
21435
- var MAX_PAC_ORIGINS = 256;
21684
+ var MAX_PAC_ROUTES = 256;
21436
21685
  var FORBIDDEN_BODY_KEYS = /* @__PURE__ */ new Set(["body", "requestBody", "responseBody"]);
21437
21686
  var MOCK_BODY_KEY = "mockBody";
21438
21687
  var MOCK_BODY_ALLOWED_TYPES = /* @__PURE__ */ new Set(["mock.response"]);
@@ -21842,12 +22091,18 @@ function originOf(value) {
21842
22091
  return null;
21843
22092
  }
21844
22093
  }
22094
+ function isHttpOrigin(origin) {
22095
+ return origin.startsWith("http://") || origin.startsWith("https://");
22096
+ }
21845
22097
  function projectHasRule(project, groupId, ruleId) {
21846
22098
  if (!Array.isArray(project.groups)) return false;
21847
22099
  const group = project.groups.find((candidate) => candidate.id === groupId);
21848
22100
  if (group === void 0 || !Array.isArray(group.rules)) return false;
21849
22101
  return group.rules.some((rule) => rule.id === ruleId);
21850
22102
  }
22103
+ function isBodyOperation(operation) {
22104
+ return operation.kind === "request-body" || operation.kind === "response-body";
22105
+ }
21851
22106
  function revalidateAuthority(project, operations, request) {
21852
22107
  const projectSnap = snapshotOwnData3(project);
21853
22108
  if (!projectSnap.valid) return { allowed: false, reason: "project-invalid" };
@@ -21869,17 +22124,21 @@ function revalidateAuthority(project, operations, request) {
21869
22124
  return { allowed: false, reason: "project-inconsistent" };
21870
22125
  }
21871
22126
  const matcher = operation.matcher;
21872
- const regex = compileUrlRegex(matcher.urlRegex.source);
21873
- if (regex === null || !regex.test(request.url)) {
22127
+ if (!sourceMatches(matcher.source, request.url)) {
21874
22128
  return { allowed: false, reason: "url-mismatch" };
21875
22129
  }
21876
- const targetOrigin2 = request.target ? originOf(request.target) : originOf(request.url);
21877
- if (targetOrigin2 === null || !matcher.origins.includes(targetOrigin2)) {
22130
+ const requestOrigin = originOf(request.url);
22131
+ const targetUrl = request.target ?? request.url;
22132
+ const targetOrigin = originOf(targetUrl);
22133
+ if (requestOrigin === null || targetOrigin === null || !sameOrigin(request.url, targetUrl)) {
21878
22134
  return { allowed: false, reason: "target-unauthorized" };
21879
22135
  }
21880
- if (request.initiator !== void 0) {
22136
+ if (isBodyOperation(operation)) {
22137
+ if (request.initiator === void 0) {
22138
+ return { allowed: false, reason: "initiator-unauthorized" };
22139
+ }
21881
22140
  const initiatorOrigin = originOf(request.initiator);
21882
- if (initiatorOrigin === null || !matcher.origins.includes(initiatorOrigin)) {
22141
+ if (initiatorOrigin === null || !isHttpOrigin(initiatorOrigin)) {
21883
22142
  return { allowed: false, reason: "initiator-unauthorized" };
21884
22143
  }
21885
22144
  }
@@ -21902,21 +22161,11 @@ function revalidateAuthority(project, operations, request) {
21902
22161
  operation
21903
22162
  };
21904
22163
  }
21905
- function originOf2(url) {
21906
- try {
21907
- return normalizeSiteOrigin(new URL(url).origin);
21908
- } catch {
21909
- return null;
21910
- }
21911
- }
21912
22164
  function isBodyOp(op) {
21913
22165
  return op.kind === "request-body" || op.kind === "response-body";
21914
22166
  }
21915
22167
  function matcherMatches(op, url, method) {
21916
- const regex = compileUrlRegex(op.matcher.urlRegex.source);
21917
- if (regex === null || !regex.test(url)) return false;
21918
- const origin = originOf2(url);
21919
- if (origin === null || !op.matcher.origins.includes(origin)) return false;
22168
+ if (!sourceMatches(op.matcher.source, url)) return false;
21920
22169
  if (op.matcher.method !== void 0 && op.matcher.method !== method.toUpperCase()) {
21921
22170
  return false;
21922
22171
  }
@@ -22144,13 +22393,33 @@ function writeResponse(res, status, statusMessage, headers, body) {
22144
22393
  res.writeHead(status, statusMessage, out);
22145
22394
  res.end(body);
22146
22395
  }
22147
- function authorizeOp(policy, op, url, method) {
22396
+ function initiatorFromHeaders(headers, targetUrl) {
22397
+ const origin = headerValue2(headers, "origin");
22398
+ if (typeof origin === "string" && /^https?:\/\//i.test(origin)) {
22399
+ return origin.endsWith("/") ? origin.slice(0, -1) : origin;
22400
+ }
22401
+ const referer = headerValue2(headers, "referer");
22402
+ if (typeof referer === "string") {
22403
+ try {
22404
+ return new URL(referer).origin;
22405
+ } catch {
22406
+ }
22407
+ }
22408
+ try {
22409
+ return new URL(targetUrl).origin;
22410
+ } catch {
22411
+ return void 0;
22412
+ }
22413
+ }
22414
+ function authorizeOp(policy, op, url, method, headers) {
22148
22415
  const resourceType = op.matcher.resourceTypes[0];
22416
+ const initiator = initiatorFromHeaders(headers, url);
22149
22417
  const decision = revalidateAuthority(policy.project, policy.operations, {
22150
22418
  groupId: op.groupId,
22151
22419
  ruleId: op.ruleId,
22152
22420
  url,
22153
22421
  method,
22422
+ ...initiator !== void 0 ? { initiator } : {},
22154
22423
  ...resourceType !== void 0 ? { resourceType } : {}
22155
22424
  });
22156
22425
  return decision.allowed;
@@ -22212,7 +22481,7 @@ async function startInterceptProxy(options = {}) {
22212
22481
  }
22213
22482
  let requestBody = bodyResult.body;
22214
22483
  if (needsRequestBody && requestOp !== null && active !== null) {
22215
- if (!authorizeOp(active, requestOp, targetUrl, method)) {
22484
+ if (!authorizeOp(active, requestOp, targetUrl, method, req.headers)) {
22216
22485
  } else {
22217
22486
  const rewritten = await rewriteRequestBody(
22218
22487
  {
@@ -22223,7 +22492,7 @@ async function startInterceptProxy(options = {}) {
22223
22492
  requestOp.requestBody,
22224
22493
  {
22225
22494
  url: targetUrl,
22226
- urlRegex: requestOp.matcher.urlRegex.source
22495
+ urlRegex: requestOp.matcher.source.value
22227
22496
  }
22228
22497
  );
22229
22498
  if (rewritten.ok) {
@@ -22245,7 +22514,7 @@ async function startInterceptProxy(options = {}) {
22245
22514
  let responseBody = upstream.body;
22246
22515
  if (responseOp !== null && active !== null) {
22247
22516
  if (responseBody.byteLength > RUNTIME_LIMITS.maxResponseBodyBytes) {
22248
- } else if (!authorizeOp(active, responseOp, targetUrl, method)) {
22517
+ } else if (!authorizeOp(active, responseOp, targetUrl, method, req.headers)) {
22249
22518
  } else {
22250
22519
  const action = responseOp.responseBody;
22251
22520
  const isReplace = "mode" in action && action.mode === "replace";
@@ -22259,7 +22528,7 @@ async function startInterceptProxy(options = {}) {
22259
22528
  isReplace ? action : void 0,
22260
22529
  {
22261
22530
  url: targetUrl,
22262
- urlRegex: responseOp.matcher.urlRegex.source
22531
+ urlRegex: responseOp.matcher.source.value
22263
22532
  }
22264
22533
  );
22265
22534
  if (rewritten.ok) {
@@ -22325,33 +22594,29 @@ async function startInterceptProxy(options = {}) {
22325
22594
  }
22326
22595
  };
22327
22596
  }
22328
- function generatePacScript(origins, endpoint, options) {
22329
- if (!Array.isArray(origins)) {
22330
- throw new Error("pac origins must be an array");
22331
- }
22332
- const valid = [];
22333
- for (const origin of origins) {
22334
- const normalized = normalizeSiteOrigin(origin);
22335
- if (normalized !== null) valid.push(normalized);
22597
+ function generatePacScript(routes, endpoint, options) {
22598
+ if (!Array.isArray(routes)) {
22599
+ throw new Error("runtime.pac-route-limit");
22336
22600
  }
22337
- const unique = Array.from(new Set(valid)).sort();
22338
- if (unique.length > MAX_PAC_ORIGINS) {
22339
- throw new Error("pac origin count exceeds maximum");
22601
+ const unique = routes.map((route) => route.hostname).filter((hostname) => typeof hostname === "string" && hostname.length > 0);
22602
+ const sorted = [...new Set(unique)].sort();
22603
+ if (sorted.length > MAX_PAC_ROUTES) {
22604
+ throw new Error("runtime.pac-route-limit");
22340
22605
  }
22341
22606
  const proxyType = options?.proxyType ?? "PROXY";
22342
22607
  const proxy = `${proxyType} ${endpoint.host}:${endpoint.port}`;
22343
- const originsLiteral = JSON.stringify(unique);
22344
22608
  const proxyLiteral = JSON.stringify(proxy);
22609
+ const checks = sorted.map(
22610
+ (hostname) => ` if (host === ${JSON.stringify(hostname)}) {
22611
+ return ${proxyLiteral};
22612
+ }`
22613
+ );
22345
22614
  return [
22346
22615
  "function FindProxyForURL(url, host) {",
22347
- ` var origins = ${originsLiteral};`,
22348
- " var origin = null;",
22349
- " try { origin = new URL(url).origin; } catch (e) { origin = null; }",
22350
- " if (origin !== null && origins.indexOf(origin) !== -1) {",
22351
- ` return ${proxyLiteral};`,
22352
- " }",
22616
+ ...checks,
22353
22617
  " return 'DIRECT';",
22354
- "}"
22618
+ "}",
22619
+ ""
22355
22620
  ].join("\n");
22356
22621
  }
22357
22622
  var registeredProvider = null;
@@ -22362,7 +22627,7 @@ function registerInterceptionProvider(provider) {
22362
22627
  function getCurrentSession() {
22363
22628
  return currentSession;
22364
22629
  }
22365
- async function startInterception(activation, policyDigest, extensionId, pacOrigins, targetPolicy) {
22630
+ async function startInterception(activation, policyDigest, extensionId, pacRoutes, targetPolicy) {
22366
22631
  if (registeredProvider === null) {
22367
22632
  return { kind: "unsupported", reasons: ["no-interception-provider"] };
22368
22633
  }
@@ -22396,10 +22661,10 @@ async function startInterception(activation, policyDigest, extensionId, pacOrigi
22396
22661
  currentSession = {
22397
22662
  sessionId,
22398
22663
  provider: registeredProvider,
22399
- activation: { ...activation, proxy, pacOrigins },
22664
+ activation: { ...activation, proxy, pacRoutes },
22400
22665
  policyDigest,
22401
22666
  extensionId,
22402
- pacOrigins,
22667
+ pacRoutes,
22403
22668
  targetPolicy,
22404
22669
  startedAt: activation.startedAt
22405
22670
  };
@@ -22445,7 +22710,7 @@ function createPlatformInterceptionProvider(adapter) {
22445
22710
  reasons
22446
22711
  };
22447
22712
  },
22448
- async start(activation, origins) {
22713
+ async start(activation, pacRoutes) {
22449
22714
  const capabilities = adapter.detect();
22450
22715
  const reasons = unsupportedReasons(capabilities);
22451
22716
  if (!capabilities.supported || reasons.length > 0) {
@@ -22457,7 +22722,10 @@ function createPlatformInterceptionProvider(adapter) {
22457
22722
  throw new Error("runtime.platform-unsupported");
22458
22723
  }
22459
22724
  const endpoint = await adapter.startTlsProxy(activation);
22460
- const pac = generatePacScript(origins, endpoint);
22725
+ const pac = generatePacScript(
22726
+ pacRoutes.map((hostname) => ({ hostname })),
22727
+ endpoint
22728
+ );
22461
22729
  try {
22462
22730
  await adapter.installPac(pac);
22463
22731
  active = true;
@@ -22655,8 +22923,9 @@ function arrayValue(values) {
22655
22923
  }
22656
22924
  function matcherValue(operation) {
22657
22925
  const matcher = operation.matcher;
22926
+ const source = matcher.source;
22658
22927
  const method = matcher.method === void 0 ? "" : `,"method":${stringValue(matcher.method)}`;
22659
- return `{"kind":"matcher","groupId":${stringValue(operation.groupId)},"ruleId":${stringValue(operation.ruleId)},"matcher":{"urlRegex":{"source":${stringValue(matcher.urlRegex.source)},"flags":""},"origins":${arrayValue(matcher.origins)},"resourceTypes":${arrayValue(matcher.resourceTypes)},"priority":${String(matcher.priority)}${method}}}`;
22928
+ return `{"kind":"matcher","groupId":${stringValue(operation.groupId)},"ruleId":${stringValue(operation.ruleId)},"matcher":{"source":{"key":${stringValue(source.key)},"operator":${stringValue(source.operator)},"value":${stringValue(source.value)}},"resourceTypes":${arrayValue(matcher.resourceTypes)},"priority":${String(matcher.priority)}${method}}}`;
22660
22929
  }
22661
22930
  function limitsValue(limits) {
22662
22931
  const fields = [
@@ -22799,12 +23068,13 @@ function validResourceType(value) {
22799
23068
  return typeof value === "string" && RESOURCE_TYPES.includes(value);
22800
23069
  }
22801
23070
  function freezeMatcher(operation) {
23071
+ const source = Object.freeze({
23072
+ key: operation.matcher.source.key,
23073
+ operator: operation.matcher.source.operator,
23074
+ value: operation.matcher.source.value
23075
+ });
22802
23076
  const matcher = Object.freeze({
22803
- urlRegex: Object.freeze({
22804
- source: operation.matcher.urlRegex.source,
22805
- flags: ""
22806
- }),
22807
- origins: Object.freeze([...operation.matcher.origins]),
23077
+ source,
22808
23078
  resourceTypes: Object.freeze([...operation.matcher.resourceTypes]),
22809
23079
  priority: operation.matcher.priority,
22810
23080
  ...operation.matcher.method === void 0 ? {} : { method: operation.matcher.method }
@@ -22834,14 +23104,8 @@ function normalizeMatcher(value) {
22834
23104
  if (record.kind !== "matcher" || !validId2(record.groupId) || !validId2(record.ruleId))
22835
23105
  return null;
22836
23106
  const matcherValue2 = record.matcher;
22837
- if (matcherValue2 === null || typeof matcherValue2 !== "object" || Array.isArray(matcherValue2) || !exactKeys(matcherValue2, [
22838
- "urlRegex",
22839
- "origins",
22840
- "resourceTypes",
22841
- "priority"
22842
- ]) && !exactKeys(matcherValue2, [
22843
- "urlRegex",
22844
- "origins",
23107
+ if (matcherValue2 === null || typeof matcherValue2 !== "object" || Array.isArray(matcherValue2) || !exactKeys(matcherValue2, ["source", "resourceTypes", "priority"]) && !exactKeys(matcherValue2, [
23108
+ "source",
22845
23109
  "resourceTypes",
22846
23110
  "priority",
22847
23111
  "method"
@@ -22849,22 +23113,14 @@ function normalizeMatcher(value) {
22849
23113
  return null;
22850
23114
  }
22851
23115
  const matcher = matcherValue2;
22852
- const regexValue = matcher.urlRegex;
22853
- if (regexValue === null || typeof regexValue !== "object" || Array.isArray(regexValue) || !exactKeys(regexValue, ["source", "flags"])) {
23116
+ const sourceValue = matcher.source;
23117
+ if (sourceValue === null || typeof sourceValue !== "object" || Array.isArray(sourceValue) || !exactKeys(sourceValue, ["key", "operator", "value"])) {
22854
23118
  return null;
22855
23119
  }
22856
- const regex = regexValue;
22857
- if (typeof regex.source !== "string" || regex.source.length > LIMITS.maxUrlRegexLength || regex.flags !== "" || compileUrlRegex(regex.source) === null) {
23120
+ const source = sourceValue;
23121
+ if (source.key !== "url" && source.key !== "host" || source.operator !== "regex" || typeof source.value !== "string" || source.value.length === 0 || source.value.length > LIMITS.maxUrlRegexLength || compileUrlRegex(source.value) === null) {
22858
23122
  return null;
22859
23123
  }
22860
- if (!Array.isArray(matcher.origins) || matcher.origins.length === 0 || matcher.origins.length > LIMITS.maxOriginsPerScope)
22861
- return null;
22862
- const origins = [];
22863
- for (const value2 of matcher.origins) {
22864
- const normalized = normalizeSiteOrigin(value2);
22865
- if (normalized === null || origins.includes(normalized)) return null;
22866
- origins.push(normalized);
22867
- }
22868
23124
  if (!Array.isArray(matcher.resourceTypes) || matcher.resourceTypes.length === 0 || matcher.resourceTypes.length > LIMITS.maxResourceTypesPerRule)
22869
23125
  return null;
22870
23126
  const resourceTypes = [];
@@ -22893,8 +23149,11 @@ function normalizeMatcher(value) {
22893
23149
  name: resolveMatcherName(record.ruleId, record.name),
22894
23150
  redactSensitiveInLogs,
22895
23151
  matcher: {
22896
- urlRegex: { source: regex.source, flags: "" },
22897
- origins,
23152
+ source: {
23153
+ key: source.key,
23154
+ operator: "regex",
23155
+ value: source.value
23156
+ },
22898
23157
  resourceTypes,
22899
23158
  priority: matcher.priority,
22900
23159
  ...method === void 0 ? {} : { method }
@@ -22937,8 +23196,7 @@ function makeGrant(value, matcherById) {
22937
23196
  if (record.kind === "outbound-http") {
22938
23197
  if (record.method !== "GET" && record.method !== "HEAD") return null;
22939
23198
  target = canonicalizeOutboundTarget(record.target);
22940
- if (target === null || !isOriginAllowed(target, matcher.matcher.origins))
22941
- return null;
23199
+ if (target === null) return null;
22942
23200
  } else {
22943
23201
  target = normalizeLogicalPath(record.target);
22944
23202
  }
@@ -23060,11 +23318,11 @@ function createNativeRuntimeController(options) {
23060
23318
  aiClient = createAIClient(options.aiProviderConfig);
23061
23319
  }
23062
23320
  async function bindInterception(current, sessionConfig) {
23063
- if (sessionConfig.pacOrigins.length === 0) {
23321
+ if (sessionConfig.pacRoutes.length === 0) {
23064
23322
  return {
23065
- activation: { ...current, pacOrigins: [] },
23323
+ activation: { ...current, pacRoutes: [] },
23066
23324
  session: null,
23067
- interception: { active: false, reasons: ["no-pac-origins"] }
23325
+ interception: { active: false, reasons: ["no-pac-routes"] }
23068
23326
  };
23069
23327
  }
23070
23328
  if (hasActiveSession()) {
@@ -23082,7 +23340,7 @@ function createNativeRuntimeController(options) {
23082
23340
  current,
23083
23341
  sessionConfig.policyDigest,
23084
23342
  sessionConfig.extensionId,
23085
- sessionConfig.pacOrigins,
23343
+ sessionConfig.pacRoutes,
23086
23344
  {
23087
23345
  public: sessionConfig.targetPolicy.public,
23088
23346
  localOrigins: sessionConfig.targetPolicy.localOrigins
@@ -23098,7 +23356,7 @@ function createNativeRuntimeController(options) {
23098
23356
  const session = getCurrentSession();
23099
23357
  const next = {
23100
23358
  ...current,
23101
- pacOrigins: sessionConfig.pacOrigins,
23359
+ pacRoutes: sessionConfig.pacRoutes,
23102
23360
  proxy: result.proxy
23103
23361
  };
23104
23362
  return {
@@ -23135,7 +23393,7 @@ function createNativeRuntimeController(options) {
23135
23393
  state: "running",
23136
23394
  startedAt,
23137
23395
  presetDigest: preset.digest,
23138
- pacOrigins: sessionConfig?.pacOrigins ?? []
23396
+ pacRoutes: sessionConfig?.pacRoutes ?? []
23139
23397
  };
23140
23398
  let session = null;
23141
23399
  if (sessionConfig) {
@@ -23316,7 +23574,7 @@ function createNativeRuntimeController(options) {
23316
23574
  const sessionConfig = {
23317
23575
  policyDigest: typeof meta.policyDigest === "string" ? meta.policyDigest : "",
23318
23576
  extensionId: typeof meta.extensionId === "string" ? meta.extensionId : "",
23319
- pacOrigins: Array.isArray(meta.pacOrigins) ? meta.pacOrigins : [],
23577
+ pacRoutes: Array.isArray(meta.pacRoutes) ? meta.pacRoutes : [],
23320
23578
  targetPolicy: {
23321
23579
  public: meta.targetPolicy?.public === true || meta.targetPolicy?.publicAllowed === true,
23322
23580
  localOrigins: Array.isArray(meta.targetPolicy?.localOrigins) ? meta.targetPolicy.localOrigins : []
@@ -23333,7 +23591,7 @@ function createNativeRuntimeController(options) {
23333
23591
  ok: bound.interception.active,
23334
23592
  interception: bound.interception,
23335
23593
  ...activation.proxy !== void 0 ? { proxy: activation.proxy } : {},
23336
- pacOrigins: activation.pacOrigins
23594
+ pacRoutes: activation.pacRoutes
23337
23595
  }
23338
23596
  };
23339
23597
  }
@@ -23406,7 +23664,9 @@ function createNativeRuntimeController(options) {
23406
23664
  };
23407
23665
  }
23408
23666
  if (!preset) throw new Error("runtime not started");
23409
- const result = authorizeExact(preset, meta.descriptor);
23667
+ const descriptorRecord = typeof meta.descriptor === "object" && meta.descriptor !== null ? meta.descriptor : {};
23668
+ const requestUrl = typeof meta.requestUrl === "string" && meta.requestUrl.length > 0 ? meta.requestUrl : typeof descriptorRecord.target === "string" ? descriptorRecord.target : "";
23669
+ const result = authorizeExact(preset, meta.descriptor, requestUrl);
23410
23670
  op.value();
23411
23671
  if (!result.ok) {
23412
23672
  return {
@@ -24162,13 +24422,13 @@ function createNativeHost(options) {
24162
24422
  }
24163
24423
  };
24164
24424
  const platformProvider = createPlatformInterceptionProvider(platformAdapter);
24165
- let pendingOrigins = [];
24425
+ let pendingRoutes = [];
24166
24426
  registerInterceptionProvider({
24167
24427
  platform: platformProvider.platform,
24168
24428
  detect: () => platformProvider.detect(),
24169
24429
  async start(activation) {
24170
- const origins = pendingOrigins.length > 0 ? pendingOrigins : activation.pacOrigins;
24171
- const endpoint = await platformProvider.start(activation, origins);
24430
+ const routes = pendingRoutes.length > 0 ? pendingRoutes : activation.pacRoutes;
24431
+ const endpoint = await platformProvider.start(activation, routes);
24172
24432
  const policy = controller.getActivePolicy();
24173
24433
  interceptProxy?.setPolicy(
24174
24434
  policy ? { project: policy.project, operations: policy.operations } : null
@@ -24220,8 +24480,8 @@ function createNativeHost(options) {
24220
24480
  }
24221
24481
  }
24222
24482
  if (envelope.type === "runtime.start") {
24223
- const origins = envelope.metadata.pacOrigins;
24224
- pendingOrigins = Array.isArray(origins) ? origins.filter((o) => typeof o === "string") : [];
24483
+ const routes = envelope.metadata.pacRoutes;
24484
+ pendingRoutes = Array.isArray(routes) ? routes.filter((o) => typeof o === "string") : [];
24225
24485
  }
24226
24486
  console.error(
24227
24487
  "[rogatio-host] received envelope:",
@@ -25209,6 +25469,10 @@ function buildDimension(state, detail) {
25209
25469
  detail
25210
25470
  };
25211
25471
  }
25472
+ function sourceDetail(source, matched) {
25473
+ const subject = source.key === "host" ? "hostname" : "url";
25474
+ return matched ? `${subject} matched /${source.value}/ (${source.key})` : `${subject} did not match /${source.value}/ (${source.key})`;
25475
+ }
25212
25476
  function emptySummary() {
25213
25477
  return {
25214
25478
  caseCount: 0,
@@ -25310,15 +25574,6 @@ function dryRunProject(operations, cases, options) {
25310
25574
  errors.push(result.error);
25311
25575
  }
25312
25576
  }
25313
- const regexCache = /* @__PURE__ */ new Map();
25314
- const getRegex = (source) => {
25315
- let cached = regexCache.get(source);
25316
- if (cached === void 0) {
25317
- cached = compileUrlRegex(source);
25318
- regexCache.set(source, cached);
25319
- }
25320
- return cached;
25321
- };
25322
25577
  const results = [];
25323
25578
  let matchedUrlCount = 0;
25324
25579
  let matchedRuleTotal = 0;
@@ -25332,21 +25587,16 @@ function dryRunProject(operations, cases, options) {
25332
25587
  const rules = [];
25333
25588
  for (const op of operations) {
25334
25589
  const matcher = op.matcher;
25335
- const regex = getRegex(matcher.urlRegex.source);
25336
- const regexState = regex ? regex.test(testCase.url) ? "matched" : "unmatched" : "unmatched";
25337
- const originState = matcher.origins.includes(
25338
- parsed.value.origin
25590
+ const sourceState = sourceMatches(
25591
+ matcher.source,
25592
+ testCase.url
25339
25593
  ) ? "matched" : "unmatched";
25340
25594
  const methodState = testCase.method === void 0 ? "not-applicable" : matcher.method === void 0 || matcher.method === testCase.method ? "matched" : "unmatched";
25341
25595
  const resourceState = testCase.resourceType === void 0 ? "not-applicable" : matcher.resourceTypes.length === 0 || matcher.resourceTypes.includes(testCase.resourceType) ? "matched" : "unmatched";
25342
- const matched = regexState === "matched" && originState === "matched" && methodState !== "unmatched" && resourceState !== "unmatched";
25343
- const urlRegexDim = buildDimension(
25344
- regexState,
25345
- regexState === "matched" ? `matches /${matcher.urlRegex.source}/` : `does not match /${matcher.urlRegex.source}/`
25346
- );
25347
- const originDim = buildDimension(
25348
- originState,
25349
- originState === "matched" ? `origin ${parsed.value.origin} in [${matcher.origins.join(", ")}]` : `origin ${parsed.value.origin} not in [${matcher.origins.join(", ")}]`
25596
+ const matched = sourceState === "matched" && methodState !== "unmatched" && resourceState !== "unmatched";
25597
+ const sourceDim = buildDimension(
25598
+ sourceState,
25599
+ sourceDetail(matcher.source, sourceState === "matched")
25350
25600
  );
25351
25601
  const methodDim = buildDimension(
25352
25602
  methodState,
@@ -25366,8 +25616,7 @@ function dryRunProject(operations, cases, options) {
25366
25616
  groupId: op.groupId,
25367
25617
  ruleId: op.ruleId,
25368
25618
  matched,
25369
- urlRegex: urlRegexDim,
25370
- effectiveOrigin: originDim,
25619
+ source: sourceDim,
25371
25620
  method: methodDim,
25372
25621
  resourceType: resourceDim,
25373
25622
  actionPreview
@@ -26106,7 +26355,7 @@ var ProjectFileError = class extends ProjectStorageError {
26106
26355
  }
26107
26356
  };
26108
26357
  function emptyProjectDocument() {
26109
- return { version: 1, name: randomCivilizationProjectName(), groups: [] };
26358
+ return { version: 2, name: randomCivilizationProjectName(), groups: [] };
26110
26359
  }
26111
26360
  var PROJECT_NAME_LEADS = Object.freeze([
26112
26361
  "Apollo",
@@ -26201,6 +26450,18 @@ async function pathExists(path2) {
26201
26450
  );
26202
26451
  }
26203
26452
  }
26453
+ function migrateLoadedProject(parsed) {
26454
+ if (parsed.version === 2) return parsed;
26455
+ if (parsed.version !== 1) return parsed;
26456
+ const migrated = migrateV1Project(parsed);
26457
+ if (!migrated.ok) return parsed;
26458
+ for (const notice of migrated.notices) {
26459
+ console.error(
26460
+ `[rogatio] ${notice.code}: ${notice.message} (${notice.path})`
26461
+ );
26462
+ }
26463
+ return migrated.project;
26464
+ }
26204
26465
  async function readDocument(id2) {
26205
26466
  try {
26206
26467
  const content = await readFile5(id2, "utf-8");
@@ -26212,7 +26473,7 @@ async function readDocument(id2) {
26212
26473
  "Project file must contain a JSON object"
26213
26474
  );
26214
26475
  }
26215
- return parsed;
26476
+ return migrateLoadedProject(parsed);
26216
26477
  } catch (e) {
26217
26478
  if (e instanceof ProjectFileError) throw e;
26218
26479
  if (e.code === "ENOENT") {
@@ -27088,8 +27349,18 @@ function usageError(message) {
27088
27349
  return `Error: ${message}
27089
27350
  `;
27090
27351
  }
27352
+ function captureSubject(matcher, url) {
27353
+ if (matcher.source.key === "url") return url;
27354
+ try {
27355
+ return new URL(url).hostname;
27356
+ } catch {
27357
+ return null;
27358
+ }
27359
+ }
27091
27360
  function previewAction(operation, url) {
27092
- const captures = matchUrlCaptures(operation.matcher.urlRegex.source, url);
27361
+ const subject = captureSubject(operation.matcher, url);
27362
+ if (subject === null) return null;
27363
+ const captures = matchUrlCaptures(operation.matcher.source.value, subject);
27093
27364
  if (captures === null) return null;
27094
27365
  const expand = (value) => substituteUrlCaptures(value, captures);
27095
27366
  if (operation.kind === "redirect") {
@@ -27396,9 +27667,7 @@ URL: ${urlResult.url}
27396
27667
  for (const rule of urlResult.rules) {
27397
27668
  output += ` ${rule.groupId}/${rule.ruleId}: ${rule.matched ? "MATCHED" : "NOT MATCHED"}
27398
27669
  `;
27399
- output += ` urlRegex: ${rule.urlRegex.state} - ${rule.urlRegex.detail}
27400
- `;
27401
- output += ` effectiveOrigin: ${rule.effectiveOrigin.state} - ${rule.effectiveOrigin.detail}
27670
+ output += ` source: ${rule.source.state} - ${rule.source.detail}
27402
27671
  `;
27403
27672
  output += ` method: ${rule.method.state} - ${rule.method.detail}
27404
27673
  `;