@rogatio/cli 5.19.0 → 5.20.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.
@@ -1351,7 +1351,14 @@ function validateProjectDetailed(value) {
1351
1351
  // packages/runtime/dist/node/index.js
1352
1352
  import { isIP as isIP2 } from "node:net";
1353
1353
  import { Buffer as Buffer2 } from "node:buffer";
1354
- import { createServer } from "node:http";
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";
1355
1362
 
1356
1363
  // packages/compiler/dist/node/index.js
1357
1364
  var ISSUE_CODES = {
@@ -1717,11 +1724,11 @@ import { isAbsolute as isAbsolute2, relative as relative2, resolve, sep } from "
1717
1724
  import { createHash as createHash2 } from "node:crypto";
1718
1725
  import { mkdir as mkdir2, readFile as readFile3, rename, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
1719
1726
  import { basename, dirname as dirname2, isAbsolute as isAbsolute3, join as join2, relative as relative3 } from "node:path";
1720
- import { generateKeyPairSync as generateKeyPairSync2, webcrypto } from "node:crypto";
1727
+ import { generateKeyPairSync, webcrypto } from "node:crypto";
1721
1728
  import { spawn, spawnSync } from "node:child_process";
1722
1729
  import { accessSync, constants as constants2, unlinkSync, writeFileSync } from "node:fs";
1723
1730
  import { tmpdir } from "node:os";
1724
- import { dirname as dirname3, join as join3 } from "node:path";
1731
+ import { dirname as dirname3, join as join4 } from "node:path";
1725
1732
  import { join as posixJoin } from "node:path/posix";
1726
1733
  import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
1727
1734
  import { accessSync as accessSync2, constants as constants3, mkdirSync, unlinkSync as unlinkSync2 } from "node:fs";
@@ -1730,7 +1737,7 @@ import { join as posixJoin2 } from "node:path/posix";
1730
1737
  import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
1731
1738
  import { accessSync as accessSync3, constants as constants4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
1732
1739
  import { tmpdir as tmpdir2 } from "node:os";
1733
- import { join as join4 } from "node:path";
1740
+ import { join as join5 } from "node:path";
1734
1741
  import { join as posixJoin3 } from "node:path/posix";
1735
1742
  var __create = Object.create;
1736
1743
  var __defProp = Object.defineProperty;
@@ -21425,6 +21432,7 @@ var RUNTIME_LIMITS = Object.freeze({
21425
21432
  });
21426
21433
  var PROTOCOL = "v1";
21427
21434
  var ENVELOPE_MAX_BYTES = 64 * 1024;
21435
+ var MAX_PAC_ORIGINS = 256;
21428
21436
  var FORBIDDEN_BODY_KEYS = /* @__PURE__ */ new Set(["body", "requestBody", "responseBody"]);
21429
21437
  var MOCK_BODY_KEY = "mockBody";
21430
21438
  var MOCK_BODY_ALLOWED_TYPES = /* @__PURE__ */ new Set(["mock.response"]);
@@ -21434,6 +21442,8 @@ var ENVELOPE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
21434
21442
  "runtime.stop",
21435
21443
  "runtime.status",
21436
21444
  "runtime.project.set",
21445
+ "runtime.pac.install",
21446
+ "runtime.pac.remove",
21437
21447
  "authority.grant",
21438
21448
  "authority.revoke",
21439
21449
  "transform.request",
@@ -21539,210 +21549,1098 @@ function parseEnvelope(json) {
21539
21549
  };
21540
21550
  return envelope;
21541
21551
  }
21542
- var TOKEN_BYTES = 32;
21543
- var TOKEN_LENGTH = 43;
21544
- function createCapabilityState(preset, now) {
21545
- const bootstrap = randomBytes(TOKEN_BYTES).toString("base64url");
21546
- return {
21547
- presetDigest: preset.digest,
21548
- bootstrap,
21549
- bootstrapDigest: digestToken(bootstrap),
21550
- bootstrapExpiresAt: now + RUNTIME_LIMITS.bootstrapLifetimeMs,
21551
- sessions: [],
21552
- consumed: false,
21553
- closed: false
21554
- };
21555
- }
21556
- function digestToken(value) {
21557
- return createHash("sha256").update(value, "utf8").digest();
21558
- }
21559
- function isToken(value) {
21560
- if (typeof value !== "string" || value.length !== TOKEN_LENGTH) return false;
21561
- if (!/^[A-Za-z0-9_-]+$/.test(value)) return false;
21552
+ function supportedContentType(value) {
21553
+ if (value === void 0) return false;
21554
+ const mediaType = value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
21555
+ return mediaType === "application/json" || mediaType.startsWith("application/") && mediaType.endsWith("+json") || mediaType === "application/x-www-form-urlencoded" || mediaType.startsWith("text/");
21556
+ }
21557
+ var REGEX_WORKER_SOURCE = `
21558
+ const workerThreads =
21559
+ typeof process.getBuiltinModule === "function"
21560
+ ? process.getBuiltinModule("worker_threads")
21561
+ : require("worker_threads");
21562
+ const { parentPort } = workerThreads;
21563
+ parentPort.postMessage({ type: "ready" });
21564
+ parentPort.on("message", (msg) => {
21565
+ if (msg.type !== "replace") return;
21566
+ parentPort.postMessage({ id: msg.id, type: "ack" });
21562
21567
  try {
21563
- return Buffer.from(value, "base64url").length === TOKEN_BYTES;
21564
- } catch {
21565
- return false;
21568
+ const out = msg.text.replace(new RegExp(msg.pattern, "gu"), msg.replacement);
21569
+ parentPort.postMessage({ id: msg.id, type: "result", ok: true, out });
21570
+ } catch (err) {
21571
+ parentPort.postMessage({
21572
+ id: msg.id,
21573
+ type: "result",
21574
+ ok: false,
21575
+ error: String((err && err.message) || err),
21576
+ });
21566
21577
  }
21578
+ });
21579
+ `;
21580
+ var REGEX_WORKER_BOOT_TIMEOUT_MS = 5e3;
21581
+ var regexWorker = null;
21582
+ var regexWorkerBoot = null;
21583
+ var nextRegexJobId = 1;
21584
+ var regexWorkerBootDelayForTestsMs = 0;
21585
+ function discardRegexWorker(worker) {
21586
+ if (regexWorker === worker) {
21587
+ regexWorker = null;
21588
+ regexWorkerBoot = null;
21589
+ }
21590
+ worker.terminate().catch(() => {
21591
+ });
21567
21592
  }
21568
- function sameToken(value, expectedDigest) {
21569
- const actual = digestToken(value);
21570
- return actual.length === expectedDigest.length && timingSafeEqual(actual, expectedDigest);
21593
+ function bootRegexWorker() {
21594
+ if (regexWorker !== null) return Promise.resolve(regexWorker);
21595
+ if (regexWorkerBoot !== null) return regexWorkerBoot;
21596
+ const boot = new Promise((resolve22, reject) => {
21597
+ const start = () => {
21598
+ let worker;
21599
+ try {
21600
+ worker = new Worker(REGEX_WORKER_SOURCE, { eval: true });
21601
+ } catch (error) {
21602
+ reject(
21603
+ error instanceof Error ? error : new Error("regex-worker-boot-failed")
21604
+ );
21605
+ return;
21606
+ }
21607
+ let settled = false;
21608
+ const bootTimer = setTimeout(() => {
21609
+ if (settled) return;
21610
+ settled = true;
21611
+ regexWorkerBoot = null;
21612
+ worker.terminate().catch(() => {
21613
+ });
21614
+ reject(new Error("regex-worker-boot-timeout"));
21615
+ }, REGEX_WORKER_BOOT_TIMEOUT_MS);
21616
+ worker.once("message", () => {
21617
+ if (settled) return;
21618
+ settled = true;
21619
+ clearTimeout(bootTimer);
21620
+ worker.unref();
21621
+ worker.on("error", () => {
21622
+ });
21623
+ worker.once("exit", () => {
21624
+ if (regexWorker === worker) {
21625
+ regexWorker = null;
21626
+ regexWorkerBoot = null;
21627
+ }
21628
+ });
21629
+ regexWorker = worker;
21630
+ resolve22(worker);
21631
+ });
21632
+ worker.once("error", (error) => {
21633
+ if (settled) return;
21634
+ settled = true;
21635
+ clearTimeout(bootTimer);
21636
+ regexWorkerBoot = null;
21637
+ reject(error);
21638
+ });
21639
+ worker.once("exit", () => {
21640
+ if (settled) return;
21641
+ settled = true;
21642
+ clearTimeout(bootTimer);
21643
+ regexWorkerBoot = null;
21644
+ reject(new Error("regex-worker-exit-during-boot"));
21645
+ });
21646
+ };
21647
+ if (regexWorkerBootDelayForTestsMs > 0) {
21648
+ const delay2 = regexWorkerBootDelayForTestsMs;
21649
+ regexWorkerBootDelayForTestsMs = 0;
21650
+ setTimeout(start, delay2).unref();
21651
+ } else {
21652
+ start();
21653
+ }
21654
+ });
21655
+ regexWorkerBoot = boot;
21656
+ void boot.catch(() => {
21657
+ if (regexWorkerBoot === boot) regexWorkerBoot = null;
21658
+ });
21659
+ return boot;
21660
+ }
21661
+ function runOnRegexWorker(worker, id2, text, pattern, replacement, deadlineMs) {
21662
+ return new Promise((resolve22, reject) => {
21663
+ let timer = null;
21664
+ const cleanup = () => {
21665
+ if (timer !== null) clearTimeout(timer);
21666
+ worker.off("message", onMessage);
21667
+ worker.off("error", onError);
21668
+ worker.off("exit", onExit);
21669
+ };
21670
+ const onMessage = (msg) => {
21671
+ if (msg.id !== id2) return;
21672
+ if (msg.type === "ack") {
21673
+ timer = setTimeout(() => {
21674
+ cleanup();
21675
+ discardRegexWorker(worker);
21676
+ reject(new Error("deadline"));
21677
+ }, deadlineMs);
21678
+ return;
21679
+ }
21680
+ if (msg.type === "result") {
21681
+ cleanup();
21682
+ if (msg.ok && typeof msg.out === "string") resolve22(msg.out);
21683
+ else reject(new Error(msg.error || "regex-invalid"));
21684
+ }
21685
+ };
21686
+ const onError = (error) => {
21687
+ cleanup();
21688
+ reject(error);
21689
+ };
21690
+ const onExit = () => {
21691
+ cleanup();
21692
+ reject(new Error("regex-worker-exited"));
21693
+ };
21694
+ worker.on("message", onMessage);
21695
+ worker.once("error", onError);
21696
+ worker.once("exit", onExit);
21697
+ worker.postMessage({ id: id2, type: "replace", text, pattern, replacement });
21698
+ });
21571
21699
  }
21572
- function sameDigest(value, expected) {
21573
- if (!isSha256Digest(value) || value.length !== expected.length) return false;
21574
- const actual = Buffer.from(value, "ascii");
21575
- const wanted = Buffer.from(expected, "ascii");
21576
- return timingSafeEqual(actual, wanted);
21700
+ function runRegexReplace(text, pattern, replacement, deadlineMs) {
21701
+ const id2 = nextRegexJobId;
21702
+ nextRegexJobId += 1;
21703
+ return bootRegexWorker().then(
21704
+ (worker) => runOnRegexWorker(worker, id2, text, pattern, replacement, deadlineMs),
21705
+ () => {
21706
+ try {
21707
+ return text.replace(new RegExp(pattern, "gu"), replacement);
21708
+ } catch {
21709
+ throw new Error("regex-invalid");
21710
+ }
21711
+ }
21712
+ );
21577
21713
  }
21578
- function cleanSessions(state, now) {
21579
- for (let index = state.sessions.length - 1; index >= 0; index -= 1) {
21580
- if (state.sessions[index]?.expiresAt <= now)
21581
- state.sessions.splice(index, 1);
21714
+ async function rewriteRequestBody(input, action, captureContext) {
21715
+ if (!supportedContentType(input.contentType)) {
21716
+ return failure("runtime.request-body-unsupported-mime-type");
21582
21717
  }
21583
- }
21584
- function pairCapability(state, capability, digest, now) {
21585
- cleanSessions(state, now);
21586
- if (state.closed || state.consumed || !isToken(capability) || typeof digest !== "string" || !sameDigest(digest, state.presetDigest) || now >= state.bootstrapExpiresAt || !sameToken(capability, state.bootstrapDigest)) {
21587
- return failure("runtime.pairing-denied");
21718
+ if (input.contentEncoding !== void 0 && input.contentEncoding !== "identity") {
21719
+ return failure("runtime.request-body-unsupported-content-encoding");
21588
21720
  }
21589
- if (state.sessions.length >= RUNTIME_LIMITS.maxConcurrentSessions) {
21590
- return failure("runtime.overloaded");
21721
+ if (input.body.byteLength > RUNTIME_LIMITS.maxRequestBodyBytes) {
21722
+ return failure("runtime.body-too-large");
21723
+ }
21724
+ let text;
21725
+ try {
21726
+ text = new TextDecoder("utf-8", { fatal: true }).decode(input.body);
21727
+ } catch {
21728
+ return failure("runtime.request-body-invalid-utf8");
21729
+ }
21730
+ if (hasLoneSurrogate(text)) {
21731
+ return failure("runtime.request-body-lone-surrogate");
21732
+ }
21733
+ let outputText;
21734
+ if (action.mode === "replace") {
21735
+ if (hasLoneSurrogate(action.body)) {
21736
+ return failure("runtime.request-body-lone-surrogate");
21737
+ }
21738
+ outputText = action.body;
21739
+ if (captureContext !== void 0) {
21740
+ const captures = matchUrlCaptures(
21741
+ captureContext.urlRegex,
21742
+ captureContext.url
21743
+ );
21744
+ if (captures === null) return failure("runtime.url-capture-mismatch");
21745
+ outputText = substituteUrlCaptures(outputText, captures);
21746
+ }
21747
+ } else {
21748
+ if (action.pattern.length === 0) {
21749
+ return failure("runtime.request-body-regex-missing-pattern");
21750
+ }
21751
+ if (action.pattern.length > LIMITS.maxRequestBodyPatternLength) {
21752
+ return failure("runtime.request-body-regex-pattern-too-large");
21753
+ }
21754
+ if (action.replacement.length > LIMITS.maxRequestBodyReplacementLength) {
21755
+ return failure("runtime.request-body-regex-replacement-too-large");
21756
+ }
21757
+ if (hasLoneSurrogate(action.replacement)) {
21758
+ return failure("runtime.request-body-lone-surrogate");
21759
+ }
21760
+ try {
21761
+ outputText = await runRegexReplace(
21762
+ text,
21763
+ action.pattern,
21764
+ action.replacement,
21765
+ RUNTIME_LIMITS.maxRegexDeadlineMs
21766
+ );
21767
+ } catch (error) {
21768
+ if (error instanceof Error && error.message === "deadline") {
21769
+ return failure("runtime.request-body-regex-deadline-exceeded");
21770
+ }
21771
+ return failure("runtime.request-body-regex-invalid");
21772
+ }
21773
+ }
21774
+ const outputBytes = new TextEncoder().encode(outputText);
21775
+ if (outputBytes.byteLength > RUNTIME_LIMITS.maxRequestBodyBytes) {
21776
+ return failure("runtime.request-body-replace-too-large");
21591
21777
  }
21592
- state.consumed = true;
21593
- const sessionCapability = randomBytes(TOKEN_BYTES).toString("base64url");
21594
- const expiresAt = now + RUNTIME_LIMITS.sessionLifetimeMs;
21595
- state.sessions.push({
21596
- digest: Buffer.from(state.presetDigest, "ascii"),
21597
- expiresAt,
21598
- tokenDigest: digestToken(sessionCapability),
21599
- activeOperations: 0
21600
- });
21601
21778
  return {
21602
21779
  ok: true,
21603
21780
  value: {
21604
- sessionCapability,
21605
- expiresInMs: RUNTIME_LIMITS.sessionLifetimeMs
21781
+ body: outputBytes,
21782
+ contentType: input.contentType?.split(";", 1)[0]?.trim() ?? "application/octet-stream"
21606
21783
  }
21607
21784
  };
21608
21785
  }
21609
- function findSession(state, capability, digest, now) {
21610
- cleanSessions(state, now);
21611
- if (state.closed || !isToken(capability) || typeof digest !== "string")
21612
- return null;
21613
- if (!sameDigest(digest, state.presetDigest)) return null;
21614
- const candidate = digestToken(capability);
21615
- for (const session of state.sessions) {
21616
- if (candidate.length === session.tokenDigest.length && timingSafeEqual(candidate, session.tokenDigest) && sameDigest(digest, session.digest.toString("ascii"))) {
21617
- return session;
21786
+ function supportedContentType2(value) {
21787
+ if (value === void 0) return false;
21788
+ const mediaType = value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
21789
+ return mediaType.startsWith("text/") || mediaType === "application/json" || mediaType === "application/javascript" || mediaType === "text/javascript" || mediaType === "text/css" || mediaType === "application/xml" || mediaType === "text/xml";
21790
+ }
21791
+ async function rewriteResponseBody(input, replacements, replaceAction, captureContext) {
21792
+ if (!supportedContentType2(input.contentType) || input.contentType === void 0)
21793
+ return failure("runtime.size-limit");
21794
+ if (input.contentEncoding !== void 0 && input.contentEncoding !== "identity")
21795
+ return failure("runtime.size-limit");
21796
+ if (input.body.byteLength > RUNTIME_LIMITS.maxResponseBodyBytes || replaceAction === void 0 && replacements.length === 0 || replacements.length > LIMITS.maxResponseBodyReplacements)
21797
+ return failure("runtime.size-limit");
21798
+ let text;
21799
+ try {
21800
+ text = new TextDecoder("utf-8", { fatal: true }).decode(input.body);
21801
+ } catch {
21802
+ return failure("runtime.size-limit");
21803
+ }
21804
+ if (replaceAction !== void 0) {
21805
+ if (captureContext === void 0) {
21806
+ text = replaceAction.body;
21807
+ } else {
21808
+ const captures = matchUrlCaptures(
21809
+ captureContext.urlRegex,
21810
+ captureContext.url
21811
+ );
21812
+ if (captures === null) return failure("runtime.url-capture-mismatch");
21813
+ text = substituteUrlCaptures(replaceAction.body, captures);
21618
21814
  }
21619
21815
  }
21620
- return null;
21621
- }
21622
- function acquireOperation(_state, session) {
21623
- if (session.activeOperations >= RUNTIME_LIMITS.maxOperationsPerSession) {
21624
- return failure("runtime.overloaded");
21816
+ try {
21817
+ for (const entry of replacements) {
21818
+ if (entry.pattern.length === 0 || entry.pattern.length > LIMITS.maxResponseBodyPatternLength || entry.replacement.length > LIMITS.maxResponseBodyReplacementLength)
21819
+ return failure("runtime.size-limit");
21820
+ const regex = new RegExp(entry.pattern, "gu");
21821
+ text = text.replace(regex, entry.replacement);
21822
+ if (new TextEncoder().encode(text).byteLength > RUNTIME_LIMITS.maxResponseBodyBytes)
21823
+ return failure("runtime.size-limit");
21824
+ }
21825
+ } catch {
21826
+ return failure("runtime.invalid-canonical-value");
21625
21827
  }
21626
- session.activeOperations += 1;
21627
21828
  return {
21628
21829
  ok: true,
21629
- value: () => {
21630
- session.activeOperations = Math.max(0, session.activeOperations - 1);
21830
+ value: {
21831
+ body: new TextEncoder().encode(text),
21832
+ contentType: input.contentType.split(";", 1)[0]?.trim() ?? input.contentType
21631
21833
  }
21632
21834
  };
21633
21835
  }
21634
- function closeCapabilityState(state) {
21635
- state.closed = true;
21636
- state.consumed = true;
21637
- state.sessions.length = 0;
21638
- state.bootstrapDigest.fill(0);
21836
+ function originOf(value) {
21837
+ const direct = normalizeSiteOrigin(value);
21838
+ if (direct !== null) return direct;
21839
+ try {
21840
+ return normalizeSiteOrigin(new URL(value).origin);
21841
+ } catch {
21842
+ return null;
21843
+ }
21639
21844
  }
21640
- var registeredProvider = null;
21641
- var currentSession = null;
21642
- async function startInterception(activation, policyDigest, extensionId, pacOrigins, targetPolicy) {
21643
- if (registeredProvider === null) {
21644
- return { kind: "unsupported", reasons: ["no-interception-provider"] };
21845
+ function projectHasRule(project, groupId, ruleId) {
21846
+ if (!Array.isArray(project.groups)) return false;
21847
+ const group = project.groups.find((candidate) => candidate.id === groupId);
21848
+ if (group === void 0 || !Array.isArray(group.rules)) return false;
21849
+ return group.rules.some((rule) => rule.id === ruleId);
21850
+ }
21851
+ function revalidateAuthority(project, operations, request) {
21852
+ const projectSnap = snapshotOwnData3(project);
21853
+ if (!projectSnap.valid) return { allowed: false, reason: "project-invalid" };
21854
+ const opsSnap = snapshotOwnData3(operations);
21855
+ if (!opsSnap.valid) return { allowed: false, reason: "project-invalid" };
21856
+ const reqSnap = snapshotOwnData3(request);
21857
+ if (!reqSnap.valid) return { allowed: false, reason: "project-invalid" };
21858
+ const operation = operations.find(
21859
+ (candidate) => candidate.groupId === request.groupId && candidate.ruleId === request.ruleId && "matcher" in candidate
21860
+ );
21861
+ if (operation === void 0 || !("matcher" in operation)) {
21862
+ return { allowed: false, reason: "operation-unknown" };
21645
21863
  }
21646
- const capabilities = registeredProvider.detect();
21647
- if (!capabilities.supported) {
21648
- return { kind: "unsupported", reasons: capabilities.reasons };
21864
+ if (!projectHasRule(
21865
+ projectSnap.value,
21866
+ request.groupId,
21867
+ request.ruleId
21868
+ )) {
21869
+ return { allowed: false, reason: "project-inconsistent" };
21649
21870
  }
21650
- if (currentSession !== null) {
21651
- return { kind: "unsupported", reasons: ["session-collision"] };
21871
+ const matcher = operation.matcher;
21872
+ const regex = compileUrlRegex(matcher.urlRegex.source);
21873
+ if (regex === null || !regex.test(request.url)) {
21874
+ return { allowed: false, reason: "url-mismatch" };
21652
21875
  }
21653
- const sessionId = `session-${activation.startedAt}-${Math.random().toString(36).slice(2)}`;
21654
- try {
21655
- await registeredProvider.start(activation);
21656
- } catch (error) {
21657
- return {
21658
- kind: "unsupported",
21659
- reasons: ["provider-start-failed", String(error)]
21660
- };
21876
+ const targetOrigin2 = request.target ? originOf(request.target) : originOf(request.url);
21877
+ if (targetOrigin2 === null || !matcher.origins.includes(targetOrigin2)) {
21878
+ return { allowed: false, reason: "target-unauthorized" };
21661
21879
  }
21662
- currentSession = {
21663
- sessionId,
21664
- provider: registeredProvider,
21665
- activation,
21666
- policyDigest,
21667
- extensionId,
21668
- pacOrigins,
21669
- targetPolicy,
21670
- startedAt: activation.startedAt
21671
- };
21672
- return { kind: "active", provider: registeredProvider.platform };
21673
- }
21674
- async function stopInterception() {
21675
- if (currentSession === null && registeredProvider === null) return;
21676
- const session = currentSession;
21677
- currentSession = null;
21678
- if (session?.provider) {
21679
- try {
21680
- await session.provider.stop();
21681
- } catch {
21880
+ if (request.initiator !== void 0) {
21881
+ const initiatorOrigin = originOf(request.initiator);
21882
+ if (initiatorOrigin === null || !matcher.origins.includes(initiatorOrigin)) {
21883
+ return { allowed: false, reason: "initiator-unauthorized" };
21682
21884
  }
21683
21885
  }
21886
+ if (matcher.method !== void 0) {
21887
+ const method = typeof request.method === "string" ? request.method.toUpperCase() : void 0;
21888
+ if (method !== matcher.method) {
21889
+ return { allowed: false, reason: "method-mismatch" };
21890
+ }
21891
+ }
21892
+ if (matcher.resourceTypes.length > 0) {
21893
+ const resourceType = typeof request.resourceType === "string" ? request.resourceType : void 0;
21894
+ if (resourceType === void 0 || !matcher.resourceTypes.includes(resourceType)) {
21895
+ return { allowed: false, reason: "resource-type-unauthorized" };
21896
+ }
21897
+ }
21898
+ return {
21899
+ allowed: true,
21900
+ groupId: request.groupId,
21901
+ ruleId: request.ruleId,
21902
+ operation
21903
+ };
21684
21904
  }
21685
- function hasActiveSession() {
21686
- return currentSession !== null;
21687
- }
21688
- function withinRoot2(root, candidate) {
21689
- const rest = relative2(root, candidate);
21690
- return rest.length > 0 && rest !== ".." && !rest.startsWith(`..${sep}`) && !isAbsolute2(rest);
21691
- }
21692
- async function readMockFile(root, logicalPath) {
21693
- const normalized = normalizeLogicalPath(logicalPath);
21694
- if (normalized === null) return failure("runtime.file-denied");
21905
+ function originOf2(url) {
21695
21906
  try {
21696
- const canonicalRoot = await realpath2(root);
21697
- const candidate = resolve(canonicalRoot, ...normalized.split("/"));
21698
- const actualPath = await realpath2(candidate);
21699
- if (!withinRoot2(canonicalRoot, actualPath))
21700
- return failure("runtime.file-denied");
21701
- const metadata = await stat(actualPath);
21702
- if (!metadata.isFile()) return failure("runtime.file-denied");
21703
- if (metadata.size > RUNTIME_LIMITS.maxFileBytes)
21704
- return failure("runtime.size-limit");
21705
- const bytes = await readFile2(actualPath);
21706
- if (bytes.byteLength > RUNTIME_LIMITS.maxFileBytes)
21707
- return failure("runtime.size-limit");
21708
- new TextDecoder("utf-8", { fatal: true }).decode(bytes);
21709
- return { ok: true, value: bytes };
21907
+ return normalizeSiteOrigin(new URL(url).origin);
21710
21908
  } catch {
21711
- return failure("runtime.file-denied");
21909
+ return null;
21712
21910
  }
21713
21911
  }
21714
- function mintToken() {
21715
- return randomBytes2(32).toString("hex");
21912
+ function isBodyOp(op) {
21913
+ return op.kind === "request-body" || op.kind === "response-body";
21716
21914
  }
21717
- async function renderMockResponse(options) {
21718
- const { mock, fileRoot } = options;
21719
- let bodyBytes;
21720
- if (mock.body !== void 0) {
21721
- bodyBytes = new TextEncoder().encode(mock.body);
21722
- } else if (mock.file !== void 0) {
21723
- if (fileRoot === void 0) return failure("runtime.file-denied");
21724
- const read = await readMockFile(fileRoot, mock.file);
21725
- if (!read.ok) return failure("runtime.file-denied");
21726
- bodyBytes = read.value;
21727
- } else {
21728
- return failure("runtime.file-denied");
21729
- }
21730
- const headers = [];
21731
- let hasContentType = false;
21732
- for (const header of mock.headers ?? []) {
21733
- headers.push([header.name, header.value]);
21734
- if (header.name.toLowerCase() === "content-type") hasContentType = true;
21915
+ 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;
21920
+ if (op.matcher.method !== void 0 && op.matcher.method !== method.toUpperCase()) {
21921
+ return false;
21735
21922
  }
21736
- if (!hasContentType) {
21737
- headers.push(["Content-Type", "text/plain; charset=UTF-8"]);
21923
+ return true;
21924
+ }
21925
+ function pickHighest(operations, kind, url, method) {
21926
+ let best = null;
21927
+ for (const op of operations) {
21928
+ if (!isBodyOp(op) || op.kind !== kind) continue;
21929
+ if (!matcherMatches(op, url, method)) continue;
21930
+ if (best === null || op.matcher.priority > best.matcher.priority || op.matcher.priority === best.matcher.priority && op.ruleId.localeCompare(best.ruleId) < 0) {
21931
+ best = op;
21932
+ }
21738
21933
  }
21739
- headers.push(["Content-Length", String(bodyBytes.byteLength)]);
21740
- headers.push(["Cache-Control", "no-store"]);
21741
- return {
21742
- ok: true,
21743
- value: {
21744
- status: mock.status,
21745
- headers,
21934
+ return best;
21935
+ }
21936
+ function headerValue2(headers, name) {
21937
+ const raw = headers[name];
21938
+ if (Array.isArray(raw)) return raw[0];
21939
+ return raw;
21940
+ }
21941
+ function readBoundedBody(req, maxBytes) {
21942
+ return new Promise((resolve22) => {
21943
+ const chunks = [];
21944
+ let total = 0;
21945
+ let settled = false;
21946
+ const finish = (result) => {
21947
+ if (settled) return;
21948
+ settled = true;
21949
+ resolve22(result);
21950
+ };
21951
+ req.on("data", (chunk) => {
21952
+ total += chunk.byteLength;
21953
+ if (total > maxBytes) {
21954
+ req.destroy();
21955
+ finish({ ok: false, reason: "oversized" });
21956
+ return;
21957
+ }
21958
+ chunks.push(chunk);
21959
+ });
21960
+ req.on("end", () => {
21961
+ finish({ ok: true, body: Buffer.concat(chunks) });
21962
+ });
21963
+ req.on("error", () => {
21964
+ finish({ ok: true, body: Buffer.concat(chunks) });
21965
+ });
21966
+ });
21967
+ }
21968
+ function hopByHop() {
21969
+ return /* @__PURE__ */ new Set([
21970
+ "connection",
21971
+ "keep-alive",
21972
+ "proxy-authenticate",
21973
+ "proxy-authorization",
21974
+ "proxy-connection",
21975
+ "te",
21976
+ "trailer",
21977
+ "transfer-encoding",
21978
+ "upgrade",
21979
+ "content-length"
21980
+ ]);
21981
+ }
21982
+ function forwardRequestHeaders(headers, host, contentLength) {
21983
+ const skip = hopByHop();
21984
+ const out = {
21985
+ host,
21986
+ "content-length": String(contentLength),
21987
+ connection: "close"
21988
+ };
21989
+ for (const [key, value] of Object.entries(headers)) {
21990
+ if (value === void 0) continue;
21991
+ const lower = key.toLowerCase();
21992
+ if (skip.has(lower) || lower.startsWith("proxy-")) continue;
21993
+ if (lower.startsWith("x-rogatio-dispatch-")) continue;
21994
+ out[key] = Array.isArray(value) ? value.join(", ") : value;
21995
+ }
21996
+ return out;
21997
+ }
21998
+ function pipeSockets(a, b) {
21999
+ a.pipe(b);
22000
+ b.pipe(a);
22001
+ const closeBoth = () => {
22002
+ a.destroy();
22003
+ b.destroy();
22004
+ };
22005
+ a.on("error", closeBoth);
22006
+ b.on("error", closeBoth);
22007
+ a.on("close", () => b.destroy());
22008
+ b.on("close", () => a.destroy());
22009
+ }
22010
+ async function upstreamHttp(method, target, headers, body) {
22011
+ const port = target.port ? Number(target.port) : 80;
22012
+ const path2 = `${target.pathname}${target.search}`;
22013
+ return new Promise((resolve22, reject) => {
22014
+ const socket = connect(port, target.hostname);
22015
+ let buf = Buffer.alloc(0);
22016
+ let settled = false;
22017
+ const fail = (err) => {
22018
+ if (settled) return;
22019
+ settled = true;
22020
+ socket.destroy();
22021
+ reject(err);
22022
+ };
22023
+ socket.setTimeout(RUNTIME_LIMITS.operationTimeoutMs);
22024
+ socket.on("timeout", () => fail(new Error("upstream timeout")));
22025
+ socket.on("error", (err) => fail(err));
22026
+ socket.on("connect", () => {
22027
+ const lines = Object.entries(headers).map(([k, v]) => `${k}: ${v}`).join("\r\n");
22028
+ socket.write(`${method} ${path2} HTTP/1.1\r
22029
+ ${lines}\r
22030
+ \r
22031
+ `);
22032
+ if (body.byteLength > 0) socket.write(body);
22033
+ });
22034
+ socket.on("data", (chunk) => {
22035
+ const piece = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
22036
+ buf = Buffer.concat([buf, piece]);
22037
+ const sep2 = buf.indexOf("\r\n\r\n");
22038
+ if (sep2 < 0) return;
22039
+ const head = buf.subarray(0, sep2).toString("latin1");
22040
+ const statusMatch = /^HTTP\/1\.[01] (\d+)(?: (.*))?/.exec(
22041
+ head.split("\r\n")[0] ?? ""
22042
+ );
22043
+ if (!statusMatch) {
22044
+ fail(new Error("bad upstream status"));
22045
+ return;
22046
+ }
22047
+ const status = Number(statusMatch[1]);
22048
+ const statusMessage = statusMatch[2]?.trim() ?? "";
22049
+ const headerMap = {};
22050
+ for (const line of head.split("\r\n").slice(1)) {
22051
+ const i = line.indexOf(":");
22052
+ if (i <= 0) continue;
22053
+ const name = line.slice(0, i).trim().toLowerCase();
22054
+ const value = line.slice(i + 1).trim();
22055
+ const existing = headerMap[name];
22056
+ if (existing === void 0) headerMap[name] = value;
22057
+ else if (Array.isArray(existing)) existing.push(value);
22058
+ else headerMap[name] = [existing, value];
22059
+ }
22060
+ const clRaw = headerMap["content-length"];
22061
+ const hasContentLength = clRaw !== void 0;
22062
+ const cl = typeof clRaw === "string" ? Number(clRaw) : Array.isArray(clRaw) ? Number(clRaw[0]) : Number.NaN;
22063
+ const bodyStart = sep2 + 4;
22064
+ if (hasContentLength && Number.isFinite(cl) && cl >= 0) {
22065
+ if (buf.length - bodyStart < cl) return;
22066
+ if (settled) return;
22067
+ settled = true;
22068
+ socket.destroy();
22069
+ resolve22({
22070
+ status,
22071
+ statusMessage,
22072
+ headers: headerMap,
22073
+ body: buf.subarray(bodyStart, bodyStart + cl)
22074
+ });
22075
+ return;
22076
+ }
22077
+ });
22078
+ socket.on("end", () => {
22079
+ if (settled) return;
22080
+ const sep2 = buf.indexOf("\r\n\r\n");
22081
+ if (sep2 < 0) {
22082
+ fail(new Error("upstream closed early"));
22083
+ return;
22084
+ }
22085
+ const head = buf.subarray(0, sep2).toString("latin1");
22086
+ const statusMatch = /^HTTP\/1\.[01] (\d+)(?: (.*))?/.exec(
22087
+ head.split("\r\n")[0] ?? ""
22088
+ );
22089
+ if (!statusMatch) {
22090
+ fail(new Error("bad upstream status"));
22091
+ return;
22092
+ }
22093
+ const headerMap = {};
22094
+ for (const line of head.split("\r\n").slice(1)) {
22095
+ const i = line.indexOf(":");
22096
+ if (i <= 0) continue;
22097
+ headerMap[line.slice(0, i).trim().toLowerCase()] = line.slice(i + 1).trim();
22098
+ }
22099
+ let body2 = Buffer.from(buf.subarray(sep2 + 4));
22100
+ const te = headerMap["transfer-encoding"];
22101
+ const teValue = Array.isArray(te) ? te[0] : te;
22102
+ if (typeof teValue === "string" && teValue.toLowerCase().includes("chunked")) {
22103
+ body2 = Buffer.from(decodeChunkedBody(body2));
22104
+ delete headerMap["transfer-encoding"];
22105
+ headerMap["content-length"] = String(body2.byteLength);
22106
+ }
22107
+ settled = true;
22108
+ resolve22({
22109
+ status: Number(statusMatch[1]),
22110
+ statusMessage: statusMatch[2]?.trim() ?? "",
22111
+ headers: headerMap,
22112
+ body: body2
22113
+ });
22114
+ });
22115
+ });
22116
+ }
22117
+ function decodeChunkedBody(buf) {
22118
+ const parts = [];
22119
+ let offset = 0;
22120
+ while (offset < buf.length) {
22121
+ const lineEnd = buf.indexOf("\r\n", offset);
22122
+ if (lineEnd < 0) break;
22123
+ const size = Number.parseInt(
22124
+ buf.subarray(offset, lineEnd).toString("ascii"),
22125
+ 16
22126
+ );
22127
+ if (!Number.isFinite(size) || size < 0) break;
22128
+ offset = lineEnd + 2;
22129
+ if (size === 0) break;
22130
+ parts.push(buf.subarray(offset, offset + size));
22131
+ offset += size + 2;
22132
+ }
22133
+ return Buffer.from(Buffer.concat(parts));
22134
+ }
22135
+ function writeResponse(res, status, statusMessage, headers, body) {
22136
+ const skip = hopByHop();
22137
+ const out = {
22138
+ "content-length": body.byteLength
22139
+ };
22140
+ for (const [key, value] of Object.entries(headers)) {
22141
+ if (skip.has(key) || key.startsWith("proxy-")) continue;
22142
+ out[key] = value;
22143
+ }
22144
+ res.writeHead(status, statusMessage, out);
22145
+ res.end(body);
22146
+ }
22147
+ function authorizeOp(policy, op, url, method) {
22148
+ const resourceType = op.matcher.resourceTypes[0];
22149
+ const decision = revalidateAuthority(policy.project, policy.operations, {
22150
+ groupId: op.groupId,
22151
+ ruleId: op.ruleId,
22152
+ url,
22153
+ method,
22154
+ ...resourceType !== void 0 ? { resourceType } : {}
22155
+ });
22156
+ return decision.allowed;
22157
+ }
22158
+ async function startInterceptProxy(options = {}) {
22159
+ let policy = options.policy ?? null;
22160
+ const server = createServer((req, res) => {
22161
+ void handleHttp(req, res);
22162
+ });
22163
+ server.on("connect", (req, clientSocket, head) => {
22164
+ handleConnect(req, clientSocket, head);
22165
+ });
22166
+ async function handleHttp(req, res) {
22167
+ try {
22168
+ const targetUrl = req.url ?? "";
22169
+ if (!/^https?:\/\//i.test(targetUrl)) {
22170
+ res.writeHead(400);
22171
+ res.end();
22172
+ return;
22173
+ }
22174
+ let target;
22175
+ try {
22176
+ target = new URL(targetUrl);
22177
+ } catch {
22178
+ res.writeHead(400);
22179
+ res.end();
22180
+ return;
22181
+ }
22182
+ if (target.protocol !== "http:") {
22183
+ res.writeHead(501);
22184
+ res.end();
22185
+ return;
22186
+ }
22187
+ const method = (req.method ?? "GET").toUpperCase();
22188
+ const active = policy;
22189
+ const requestOp = active === null ? null : pickHighest(
22190
+ active.operations,
22191
+ "request-body",
22192
+ targetUrl,
22193
+ method
22194
+ );
22195
+ const responseOp = active === null ? null : pickHighest(
22196
+ active.operations,
22197
+ "response-body",
22198
+ targetUrl,
22199
+ method
22200
+ );
22201
+ const needsRequestBody = requestOp !== null;
22202
+ const bodyResult = await readBoundedBody(
22203
+ req,
22204
+ RUNTIME_LIMITS.maxRequestBodyBytes
22205
+ );
22206
+ if (!bodyResult.ok) {
22207
+ if (!res.headersSent) {
22208
+ res.writeHead(413);
22209
+ res.end();
22210
+ }
22211
+ return;
22212
+ }
22213
+ let requestBody = bodyResult.body;
22214
+ if (needsRequestBody && requestOp !== null && active !== null) {
22215
+ if (!authorizeOp(active, requestOp, targetUrl, method)) {
22216
+ } else {
22217
+ const rewritten = await rewriteRequestBody(
22218
+ {
22219
+ contentType: headerValue2(req.headers, "content-type"),
22220
+ contentEncoding: headerValue2(req.headers, "content-encoding") ?? "identity",
22221
+ body: new Uint8Array(requestBody)
22222
+ },
22223
+ requestOp.requestBody,
22224
+ {
22225
+ url: targetUrl,
22226
+ urlRegex: requestOp.matcher.urlRegex.source
22227
+ }
22228
+ );
22229
+ if (rewritten.ok) {
22230
+ requestBody = Buffer.from(rewritten.value.body);
22231
+ }
22232
+ }
22233
+ }
22234
+ const forwardHeaders = forwardRequestHeaders(
22235
+ req.headers,
22236
+ target.host,
22237
+ requestBody.byteLength
22238
+ );
22239
+ const upstream = await upstreamHttp(
22240
+ method,
22241
+ target,
22242
+ forwardHeaders,
22243
+ requestBody
22244
+ );
22245
+ let responseBody = upstream.body;
22246
+ if (responseOp !== null && active !== null) {
22247
+ if (responseBody.byteLength > RUNTIME_LIMITS.maxResponseBodyBytes) {
22248
+ } else if (!authorizeOp(active, responseOp, targetUrl, method)) {
22249
+ } else {
22250
+ const action = responseOp.responseBody;
22251
+ const isReplace = "mode" in action && action.mode === "replace";
22252
+ const rewritten = await rewriteResponseBody(
22253
+ {
22254
+ contentType: typeof upstream.headers["content-type"] === "string" ? upstream.headers["content-type"] : Array.isArray(upstream.headers["content-type"]) ? upstream.headers["content-type"][0] : void 0,
22255
+ contentEncoding: typeof upstream.headers["content-encoding"] === "string" ? upstream.headers["content-encoding"] : "identity",
22256
+ body: new Uint8Array(responseBody)
22257
+ },
22258
+ "replacements" in action && Array.isArray(action.replacements) ? action.replacements : [],
22259
+ isReplace ? action : void 0,
22260
+ {
22261
+ url: targetUrl,
22262
+ urlRegex: responseOp.matcher.urlRegex.source
22263
+ }
22264
+ );
22265
+ if (rewritten.ok) {
22266
+ responseBody = Buffer.from(rewritten.value.body);
22267
+ }
22268
+ }
22269
+ }
22270
+ writeResponse(
22271
+ res,
22272
+ upstream.status,
22273
+ upstream.statusMessage,
22274
+ upstream.headers,
22275
+ responseBody
22276
+ );
22277
+ } catch {
22278
+ if (!res.headersSent) {
22279
+ res.writeHead(502);
22280
+ res.end();
22281
+ }
22282
+ }
22283
+ }
22284
+ function handleConnect(req, clientSocket, head) {
22285
+ const authority = req.url ?? "";
22286
+ const [host, portText] = authority.split(":");
22287
+ const port = Number(portText);
22288
+ if (!host || !Number.isInteger(port) || port <= 0) {
22289
+ clientSocket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
22290
+ clientSocket.destroy();
22291
+ return;
22292
+ }
22293
+ const upstream = connect(port, host);
22294
+ upstream.on("connect", () => {
22295
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
22296
+ if (head.byteLength > 0) upstream.write(head);
22297
+ pipeSockets(clientSocket, upstream);
22298
+ });
22299
+ upstream.on("error", () => {
22300
+ clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
22301
+ clientSocket.destroy();
22302
+ });
22303
+ clientSocket.on("error", () => upstream.destroy());
22304
+ }
22305
+ const endpoint = await new Promise((resolve22, reject) => {
22306
+ server.once("error", reject);
22307
+ server.listen(0, "127.0.0.1", () => {
22308
+ const addr = server.address();
22309
+ if (!addr || typeof addr === "string") {
22310
+ reject(new Error("intercept-proxy bind failed"));
22311
+ return;
22312
+ }
22313
+ resolve22({ host: "127.0.0.1", port: addr.port });
22314
+ });
22315
+ });
22316
+ return {
22317
+ endpoint,
22318
+ setPolicy(next) {
22319
+ policy = next;
22320
+ },
22321
+ async stop() {
22322
+ await new Promise((resolve22, reject) => {
22323
+ server.close((err) => err ? reject(err) : resolve22());
22324
+ });
22325
+ }
22326
+ };
22327
+ }
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);
22336
+ }
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");
22340
+ }
22341
+ const proxyType = options?.proxyType ?? "PROXY";
22342
+ const proxy = `${proxyType} ${endpoint.host}:${endpoint.port}`;
22343
+ const originsLiteral = JSON.stringify(unique);
22344
+ const proxyLiteral = JSON.stringify(proxy);
22345
+ return [
22346
+ "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
+ " }",
22353
+ " return 'DIRECT';",
22354
+ "}"
22355
+ ].join("\n");
22356
+ }
22357
+ var registeredProvider = null;
22358
+ var currentSession = null;
22359
+ function registerInterceptionProvider(provider) {
22360
+ registeredProvider = provider;
22361
+ }
22362
+ function getCurrentSession() {
22363
+ return currentSession;
22364
+ }
22365
+ async function startInterception(activation, policyDigest, extensionId, pacOrigins, targetPolicy) {
22366
+ if (registeredProvider === null) {
22367
+ return { kind: "unsupported", reasons: ["no-interception-provider"] };
22368
+ }
22369
+ const capabilities = registeredProvider.detect();
22370
+ if (!capabilities.supported) {
22371
+ return { kind: "unsupported", reasons: capabilities.reasons };
22372
+ }
22373
+ if (currentSession !== null) {
22374
+ return { kind: "unsupported", reasons: ["session-collision"] };
22375
+ }
22376
+ const sessionId = `session-${activation.startedAt}-${Math.random().toString(36).slice(2)}`;
22377
+ let proxy;
22378
+ try {
22379
+ const started = await registeredProvider.start(activation);
22380
+ if (started && typeof started === "object" && typeof started.host === "string" && typeof started.port === "number") {
22381
+ proxy = started;
22382
+ } else if (activation.proxy) {
22383
+ proxy = activation.proxy;
22384
+ } else {
22385
+ return {
22386
+ kind: "unsupported",
22387
+ reasons: ["provider-start-failed", "missing-proxy-endpoint"]
22388
+ };
22389
+ }
22390
+ } catch (error) {
22391
+ return {
22392
+ kind: "unsupported",
22393
+ reasons: ["provider-start-failed", String(error)]
22394
+ };
22395
+ }
22396
+ currentSession = {
22397
+ sessionId,
22398
+ provider: registeredProvider,
22399
+ activation: { ...activation, proxy, pacOrigins },
22400
+ policyDigest,
22401
+ extensionId,
22402
+ pacOrigins,
22403
+ targetPolicy,
22404
+ startedAt: activation.startedAt
22405
+ };
22406
+ return { kind: "active", provider: registeredProvider.platform, proxy };
22407
+ }
22408
+ async function stopInterception() {
22409
+ if (currentSession === null && registeredProvider === null) return;
22410
+ const session = currentSession;
22411
+ currentSession = null;
22412
+ if (session?.provider) {
22413
+ try {
22414
+ await session.provider.stop();
22415
+ } catch {
22416
+ }
22417
+ }
22418
+ }
22419
+ function hasActiveSession() {
22420
+ return currentSession !== null;
22421
+ }
22422
+ function clearSession() {
22423
+ currentSession = null;
22424
+ }
22425
+ function unsupportedReasons(capabilities) {
22426
+ const reasons = [...capabilities.reasons];
22427
+ if (!capabilities.trustedDeviceLocalCa)
22428
+ reasons.push("device-local-ca-untrusted");
22429
+ if (capabilities.controllingProxy) reasons.push("controlling-proxy");
22430
+ if (capabilities.controllingPac) reasons.push("controlling-pac");
22431
+ if (capabilities.controllingExtension) reasons.push("controlling-extension");
22432
+ if (capabilities.enterprisePolicy) reasons.push("enterprise-policy");
22433
+ return [...new Set(reasons)].sort();
22434
+ }
22435
+ function createPlatformInterceptionProvider(adapter) {
22436
+ let state = "stopped";
22437
+ let active = false;
22438
+ return {
22439
+ platform: adapter.platform,
22440
+ detect() {
22441
+ const capabilities = adapter.detect();
22442
+ const reasons = unsupportedReasons(capabilities);
22443
+ return {
22444
+ supported: capabilities.supported && reasons.length === 0,
22445
+ reasons
22446
+ };
22447
+ },
22448
+ async start(activation, origins) {
22449
+ const capabilities = adapter.detect();
22450
+ const reasons = unsupportedReasons(capabilities);
22451
+ if (!capabilities.supported || reasons.length > 0) {
22452
+ state = "unsupported";
22453
+ throw new Error("runtime.platform-unsupported");
22454
+ }
22455
+ if (!await adapter.provisionOrVerifyCa()) {
22456
+ state = "unsupported";
22457
+ throw new Error("runtime.platform-unsupported");
22458
+ }
22459
+ const endpoint = await adapter.startTlsProxy(activation);
22460
+ const pac = generatePacScript(origins, endpoint);
22461
+ try {
22462
+ await adapter.installPac(pac);
22463
+ active = true;
22464
+ state = "running";
22465
+ return endpoint;
22466
+ } catch (error) {
22467
+ await adapter.stopTlsProxy();
22468
+ active = false;
22469
+ state = "stopped";
22470
+ throw error;
22471
+ }
22472
+ },
22473
+ async stop() {
22474
+ if (!active && state !== "running") {
22475
+ state = state === "unsupported" ? "unsupported" : "stopped";
22476
+ return;
22477
+ }
22478
+ await adapter.removePac();
22479
+ await adapter.stopTlsProxy();
22480
+ active = false;
22481
+ state = "stopped";
22482
+ },
22483
+ status() {
22484
+ return state;
22485
+ }
22486
+ };
22487
+ }
22488
+ var TOKEN_BYTES = 32;
22489
+ var TOKEN_LENGTH = 43;
22490
+ function createCapabilityState(preset, now) {
22491
+ const bootstrap = randomBytes(TOKEN_BYTES).toString("base64url");
22492
+ return {
22493
+ presetDigest: preset.digest,
22494
+ bootstrap,
22495
+ bootstrapDigest: digestToken(bootstrap),
22496
+ bootstrapExpiresAt: now + RUNTIME_LIMITS.bootstrapLifetimeMs,
22497
+ sessions: [],
22498
+ consumed: false,
22499
+ closed: false
22500
+ };
22501
+ }
22502
+ function digestToken(value) {
22503
+ return createHash("sha256").update(value, "utf8").digest();
22504
+ }
22505
+ function isToken(value) {
22506
+ if (typeof value !== "string" || value.length !== TOKEN_LENGTH) return false;
22507
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) return false;
22508
+ try {
22509
+ return Buffer.from(value, "base64url").length === TOKEN_BYTES;
22510
+ } catch {
22511
+ return false;
22512
+ }
22513
+ }
22514
+ function sameToken(value, expectedDigest) {
22515
+ const actual = digestToken(value);
22516
+ return actual.length === expectedDigest.length && timingSafeEqual(actual, expectedDigest);
22517
+ }
22518
+ function sameDigest(value, expected) {
22519
+ if (!isSha256Digest(value) || value.length !== expected.length) return false;
22520
+ const actual = Buffer.from(value, "ascii");
22521
+ const wanted = Buffer.from(expected, "ascii");
22522
+ return timingSafeEqual(actual, wanted);
22523
+ }
22524
+ function cleanSessions(state, now) {
22525
+ for (let index = state.sessions.length - 1; index >= 0; index -= 1) {
22526
+ if (state.sessions[index]?.expiresAt <= now)
22527
+ state.sessions.splice(index, 1);
22528
+ }
22529
+ }
22530
+ function pairCapability(state, capability, digest, now) {
22531
+ cleanSessions(state, now);
22532
+ if (state.closed || state.consumed || !isToken(capability) || typeof digest !== "string" || !sameDigest(digest, state.presetDigest) || now >= state.bootstrapExpiresAt || !sameToken(capability, state.bootstrapDigest)) {
22533
+ return failure("runtime.pairing-denied");
22534
+ }
22535
+ if (state.sessions.length >= RUNTIME_LIMITS.maxConcurrentSessions) {
22536
+ return failure("runtime.overloaded");
22537
+ }
22538
+ state.consumed = true;
22539
+ const sessionCapability = randomBytes(TOKEN_BYTES).toString("base64url");
22540
+ const expiresAt = now + RUNTIME_LIMITS.sessionLifetimeMs;
22541
+ state.sessions.push({
22542
+ digest: Buffer.from(state.presetDigest, "ascii"),
22543
+ expiresAt,
22544
+ tokenDigest: digestToken(sessionCapability),
22545
+ activeOperations: 0
22546
+ });
22547
+ return {
22548
+ ok: true,
22549
+ value: {
22550
+ sessionCapability,
22551
+ expiresInMs: RUNTIME_LIMITS.sessionLifetimeMs
22552
+ }
22553
+ };
22554
+ }
22555
+ function findSession(state, capability, digest, now) {
22556
+ cleanSessions(state, now);
22557
+ if (state.closed || !isToken(capability) || typeof digest !== "string")
22558
+ return null;
22559
+ if (!sameDigest(digest, state.presetDigest)) return null;
22560
+ const candidate = digestToken(capability);
22561
+ for (const session of state.sessions) {
22562
+ if (candidate.length === session.tokenDigest.length && timingSafeEqual(candidate, session.tokenDigest) && sameDigest(digest, session.digest.toString("ascii"))) {
22563
+ return session;
22564
+ }
22565
+ }
22566
+ return null;
22567
+ }
22568
+ function acquireOperation(_state, session) {
22569
+ if (session.activeOperations >= RUNTIME_LIMITS.maxOperationsPerSession) {
22570
+ return failure("runtime.overloaded");
22571
+ }
22572
+ session.activeOperations += 1;
22573
+ return {
22574
+ ok: true,
22575
+ value: () => {
22576
+ session.activeOperations = Math.max(0, session.activeOperations - 1);
22577
+ }
22578
+ };
22579
+ }
22580
+ function closeCapabilityState(state) {
22581
+ state.closed = true;
22582
+ state.consumed = true;
22583
+ state.sessions.length = 0;
22584
+ state.bootstrapDigest.fill(0);
22585
+ }
22586
+ function withinRoot2(root, candidate) {
22587
+ const rest = relative2(root, candidate);
22588
+ return rest.length > 0 && rest !== ".." && !rest.startsWith(`..${sep}`) && !isAbsolute2(rest);
22589
+ }
22590
+ async function readMockFile(root, logicalPath) {
22591
+ const normalized = normalizeLogicalPath(logicalPath);
22592
+ if (normalized === null) return failure("runtime.file-denied");
22593
+ try {
22594
+ const canonicalRoot = await realpath2(root);
22595
+ const candidate = resolve(canonicalRoot, ...normalized.split("/"));
22596
+ const actualPath = await realpath2(candidate);
22597
+ if (!withinRoot2(canonicalRoot, actualPath))
22598
+ return failure("runtime.file-denied");
22599
+ const metadata = await stat(actualPath);
22600
+ if (!metadata.isFile()) return failure("runtime.file-denied");
22601
+ if (metadata.size > RUNTIME_LIMITS.maxFileBytes)
22602
+ return failure("runtime.size-limit");
22603
+ const bytes = await readFile2(actualPath);
22604
+ if (bytes.byteLength > RUNTIME_LIMITS.maxFileBytes)
22605
+ return failure("runtime.size-limit");
22606
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
22607
+ return { ok: true, value: bytes };
22608
+ } catch {
22609
+ return failure("runtime.file-denied");
22610
+ }
22611
+ }
22612
+ function mintToken() {
22613
+ return randomBytes2(32).toString("hex");
22614
+ }
22615
+ async function renderMockResponse(options) {
22616
+ const { mock, fileRoot } = options;
22617
+ let bodyBytes;
22618
+ if (mock.body !== void 0) {
22619
+ bodyBytes = new TextEncoder().encode(mock.body);
22620
+ } else if (mock.file !== void 0) {
22621
+ if (fileRoot === void 0) return failure("runtime.file-denied");
22622
+ const read = await readMockFile(fileRoot, mock.file);
22623
+ if (!read.ok) return failure("runtime.file-denied");
22624
+ bodyBytes = read.value;
22625
+ } else {
22626
+ return failure("runtime.file-denied");
22627
+ }
22628
+ const headers = [];
22629
+ let hasContentType = false;
22630
+ for (const header of mock.headers ?? []) {
22631
+ headers.push([header.name, header.value]);
22632
+ if (header.name.toLowerCase() === "content-type") hasContentType = true;
22633
+ }
22634
+ if (!hasContentType) {
22635
+ headers.push(["Content-Type", "text/plain; charset=UTF-8"]);
22636
+ }
22637
+ headers.push(["Content-Length", String(bodyBytes.byteLength)]);
22638
+ headers.push(["Cache-Control", "no-store"]);
22639
+ return {
22640
+ ok: true,
22641
+ value: {
22642
+ status: mock.status,
22643
+ headers,
21746
22644
  bodyBytes
21747
22645
  }
21748
22646
  };
@@ -21865,8 +22763,8 @@ function normalizeMockConfig(value, matcherById) {
21865
22763
  if (!Array.isArray(record.headers) || record.headers.length > LIMITS.maxMockHeadersPerRule)
21866
22764
  return null;
21867
22765
  const normalizedHeaders = [];
21868
- for (const headerValue2 of record.headers) {
21869
- const header = normalizeMockHeader(headerValue2);
22766
+ for (const headerValue3 of record.headers) {
22767
+ const header = normalizeMockHeader(headerValue3);
21870
22768
  if (header === null) return null;
21871
22769
  normalizedHeaders.push(header);
21872
22770
  }
@@ -22155,14 +23053,72 @@ function createNativeRuntimeController(options) {
22155
23053
  let state = "idle";
22156
23054
  let activation;
22157
23055
  let capability;
23056
+ let activePolicy = null;
22158
23057
  const mockTokens = /* @__PURE__ */ new Map();
22159
23058
  let aiClient;
22160
23059
  if (options.aiProviderConfig) {
22161
23060
  aiClient = createAIClient(options.aiProviderConfig);
22162
23061
  }
23062
+ async function bindInterception(current, sessionConfig) {
23063
+ if (sessionConfig.pacOrigins.length === 0) {
23064
+ return {
23065
+ activation: { ...current, pacOrigins: [] },
23066
+ session: null,
23067
+ interception: { active: false, reasons: ["no-pac-origins"] }
23068
+ };
23069
+ }
23070
+ if (hasActiveSession()) {
23071
+ const session2 = getCurrentSession();
23072
+ return {
23073
+ activation: current,
23074
+ session: session2,
23075
+ interception: {
23076
+ active: session2 !== null,
23077
+ reasons: session2 !== null ? [] : ["session-missing"]
23078
+ }
23079
+ };
23080
+ }
23081
+ const result = await startInterception(
23082
+ current,
23083
+ sessionConfig.policyDigest,
23084
+ sessionConfig.extensionId,
23085
+ sessionConfig.pacOrigins,
23086
+ {
23087
+ public: sessionConfig.targetPolicy.public,
23088
+ localOrigins: sessionConfig.targetPolicy.localOrigins
23089
+ }
23090
+ );
23091
+ if (result.kind === "unsupported") {
23092
+ return {
23093
+ activation: current,
23094
+ session: null,
23095
+ interception: { active: false, reasons: [...result.reasons] }
23096
+ };
23097
+ }
23098
+ const session = getCurrentSession();
23099
+ const next = {
23100
+ ...current,
23101
+ pacOrigins: sessionConfig.pacOrigins,
23102
+ proxy: result.proxy
23103
+ };
23104
+ return {
23105
+ activation: next,
23106
+ session,
23107
+ interception: { active: true, reasons: [] }
23108
+ };
23109
+ }
22163
23110
  return {
22164
23111
  async start(sessionConfig) {
22165
23112
  if (state === "running" || state === "starting") {
23113
+ if (sessionConfig && activation) {
23114
+ const bound = await bindInterception(activation, sessionConfig);
23115
+ activation = bound.activation;
23116
+ return {
23117
+ state: "running",
23118
+ activation,
23119
+ session: bound.session
23120
+ };
23121
+ }
22166
23122
  if (activation) return { state: "running", activation };
22167
23123
  return { state };
22168
23124
  }
@@ -22179,22 +23135,13 @@ function createNativeRuntimeController(options) {
22179
23135
  state: "running",
22180
23136
  startedAt,
22181
23137
  presetDigest: preset.digest,
22182
- pacOrigins: []
23138
+ pacOrigins: sessionConfig?.pacOrigins ?? []
22183
23139
  };
22184
23140
  let session = null;
22185
23141
  if (sessionConfig) {
22186
- const result = await startInterception(
22187
- activation,
22188
- sessionConfig.policyDigest,
22189
- sessionConfig.extensionId,
22190
- sessionConfig.pacOrigins,
22191
- sessionConfig.targetPolicy
22192
- );
22193
- if (result.kind !== "unsupported") {
22194
- session = hasActiveSession() ? getCurrentSession2() : null;
22195
- if (session)
22196
- activation = { ...activation, pacOrigins: session.pacOrigins };
22197
- }
23142
+ const bound = await bindInterception(activation, sessionConfig);
23143
+ activation = bound.activation;
23144
+ session = bound.session;
22198
23145
  }
22199
23146
  if (options.onStart) await options.onStart(activation, session);
22200
23147
  state = "running";
@@ -22214,6 +23161,7 @@ function createNativeRuntimeController(options) {
22214
23161
  if (capability) closeCapabilityState(capability);
22215
23162
  mockTokens.clear();
22216
23163
  activation = void 0;
23164
+ activePolicy = null;
22217
23165
  state = "stopped";
22218
23166
  return { state: "stopped" };
22219
23167
  },
@@ -22221,7 +23169,10 @@ function createNativeRuntimeController(options) {
22221
23169
  return { state };
22222
23170
  },
22223
23171
  getSession() {
22224
- return hasActiveSession() ? getCurrentSession2() : null;
23172
+ return hasActiveSession() ? getCurrentSession() : null;
23173
+ },
23174
+ getActivePolicy() {
23175
+ return activePolicy;
22225
23176
  },
22226
23177
  async serveMock(token) {
22227
23178
  const mock = mockTokens.get(token);
@@ -22316,6 +23267,10 @@ function createNativeRuntimeController(options) {
22316
23267
  };
22317
23268
  }
22318
23269
  preset = normalized.value;
23270
+ activePolicy = {
23271
+ project: schemaResult.data,
23272
+ operations: compileResult.operations
23273
+ };
22319
23274
  const startResult = await this.start();
22320
23275
  if (startResult.state !== "running") {
22321
23276
  return {
@@ -22334,6 +23289,64 @@ function createNativeRuntimeController(options) {
22334
23289
  metadata: { ok: true, presetDigest: preset.digest }
22335
23290
  };
22336
23291
  }
23292
+ if (input.type === "runtime.project.set" && state === "running") {
23293
+ return {
23294
+ protocol: "v1",
23295
+ type: "runtime.project.set",
23296
+ ...requestId !== void 0 ? { requestId } : {},
23297
+ timestamp,
23298
+ metadata: { ok: false, error: "runtime.already-started" }
23299
+ };
23300
+ }
23301
+ if (input.type === "runtime.start") {
23302
+ if (state === "idle" || !preset || activation === void 0) {
23303
+ return {
23304
+ protocol: "v1",
23305
+ type: "runtime.start",
23306
+ ...requestId !== void 0 ? { requestId } : {},
23307
+ timestamp,
23308
+ metadata: {
23309
+ ok: false,
23310
+ error: "runtime.not-ready",
23311
+ interception: { active: false, reasons: ["not-ready"] }
23312
+ }
23313
+ };
23314
+ }
23315
+ const meta = input.metadata;
23316
+ const sessionConfig = {
23317
+ policyDigest: typeof meta.policyDigest === "string" ? meta.policyDigest : "",
23318
+ extensionId: typeof meta.extensionId === "string" ? meta.extensionId : "",
23319
+ pacOrigins: Array.isArray(meta.pacOrigins) ? meta.pacOrigins : [],
23320
+ targetPolicy: {
23321
+ public: meta.targetPolicy?.public === true || meta.targetPolicy?.publicAllowed === true,
23322
+ localOrigins: Array.isArray(meta.targetPolicy?.localOrigins) ? meta.targetPolicy.localOrigins : []
23323
+ }
23324
+ };
23325
+ const bound = await bindInterception(activation, sessionConfig);
23326
+ activation = bound.activation;
23327
+ return {
23328
+ protocol: "v1",
23329
+ type: "runtime.start",
23330
+ ...requestId !== void 0 ? { requestId } : {},
23331
+ timestamp,
23332
+ metadata: {
23333
+ ok: bound.interception.active,
23334
+ interception: bound.interception,
23335
+ ...activation.proxy !== void 0 ? { proxy: activation.proxy } : {},
23336
+ pacOrigins: activation.pacOrigins
23337
+ }
23338
+ };
23339
+ }
23340
+ if (input.type === "runtime.stop") {
23341
+ await this.stop();
23342
+ return {
23343
+ protocol: "v1",
23344
+ type: "runtime.stop",
23345
+ ...requestId !== void 0 ? { requestId } : {},
23346
+ timestamp,
23347
+ metadata: { ok: true, state: "stopped" }
23348
+ };
23349
+ }
22337
23350
  if (state !== "running" || capability === void 0) {
22338
23351
  throw new Error("runtime not started");
22339
23352
  }
@@ -22596,225 +23609,43 @@ function createNativeRuntimeController(options) {
22596
23609
  const rendered = await renderMockResponse({
22597
23610
  mock,
22598
23611
  fileRoot,
22599
- presetDigest: preset.digest
22600
- });
22601
- if (!rendered.ok) {
22602
- const body = JSON.stringify({
22603
- ok: false,
22604
- error: { code: rendered.error.code }
22605
- });
22606
- const metadata2 = {
22607
- status: 500,
22608
- headers: [["Content-Type", "application/json"]],
22609
- mockBody: base64(new TextEncoder().encode(body))
22610
- };
22611
- return {
22612
- protocol: "v1",
22613
- type: "mock.response",
22614
- ...requestId !== void 0 ? { requestId } : {},
22615
- timestamp,
22616
- metadata: metadata2
22617
- };
22618
- }
22619
- const headers = [];
22620
- for (const header of rendered.value.headers) {
22621
- headers.push([header[0], header[1]]);
22622
- }
22623
- const metadata = {
22624
- status: rendered.value.status,
22625
- headers,
22626
- mockBody: base64(rendered.value.bodyBytes)
22627
- };
22628
- return {
22629
- protocol: "v1",
22630
- type: "mock.response",
22631
- ...requestId !== void 0 ? { requestId } : {},
22632
- timestamp,
22633
- metadata
22634
- };
22635
- }
22636
- }
22637
- function getCurrentSession2() {
22638
- return null;
22639
- }
22640
- function encodeEnvelopeFrame(envelope) {
22641
- const json = new TextEncoder().encode(serializeEnvelope(envelope));
22642
- const out = new Uint8Array(4 + json.byteLength);
22643
- new DataView(out.buffer).setUint32(0, json.byteLength, true);
22644
- out.set(json, 4);
22645
- return out;
22646
- }
22647
- function decodeEnvelopeFrame(buffer) {
22648
- if (buffer.byteLength < 4) throw new Error("frame too small");
22649
- const length = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(
22650
- 0,
22651
- true
22652
- );
22653
- if (buffer.byteLength !== 4 + length)
22654
- throw new Error("frame length mismatch");
22655
- const json = new TextDecoder("utf-8", { fatal: true }).decode(
22656
- buffer.slice(4)
22657
- );
22658
- return parseEnvelope(json);
22659
- }
22660
- function startMockFaucet(port, controller) {
22661
- const server = createServer((req, res) => {
22662
- void (async () => {
22663
- try {
22664
- const url = new URL(req.url ?? "", "http://localhost");
22665
- const match = /^\/mock\/([a-f0-9]+)$/.exec(url.pathname);
22666
- if (!match || req.method !== "GET") {
22667
- res.writeHead(404);
22668
- res.end();
22669
- return;
22670
- }
22671
- const result = await controller.serveMock(match[1]);
22672
- if (!result.ok) {
22673
- res.writeHead(404);
22674
- res.end();
22675
- return;
22676
- }
22677
- const { status, headers, bodyBytes } = result.value;
22678
- res.writeHead(
22679
- status,
22680
- Object.fromEntries(
22681
- headers.map((h) => [h[0], h[1]])
22682
- )
22683
- );
22684
- res.end(Buffer2.from(bodyBytes));
22685
- } catch {
22686
- if (!res.headersSent) res.writeHead(500);
22687
- res.end();
22688
- }
22689
- })();
22690
- });
22691
- return new Promise(
22692
- (resolve22) => server.listen(port, () => resolve22(server))
22693
- );
22694
- }
22695
- function createNativeHost(options) {
22696
- const controller = createNativeRuntimeController({
22697
- preset: options.preset,
22698
- fileRoot: options.fileRoot,
22699
- ...options.mockPort !== void 0 ? { mockPort: options.mockPort } : {},
22700
- ...options.aiProviderConfig !== void 0 ? { aiProviderConfig: options.aiProviderConfig } : {},
22701
- ...options.clock ? { clock: options.clock } : {}
22702
- });
22703
- let faucet = null;
22704
- return {
22705
- controller,
22706
- mockPort: options.mockPort ?? null,
22707
- async start() {
22708
- await controller.start();
22709
- if (options.mockPort !== void 0) {
22710
- faucet = await startMockFaucet(options.mockPort, controller);
22711
- }
22712
- },
22713
- async stop() {
22714
- if (faucet) {
22715
- await new Promise((resolve22) => faucet?.close(() => resolve22()));
22716
- faucet = null;
22717
- }
22718
- await controller.stop();
22719
- },
22720
- async processFrame(frame) {
22721
- let envelope;
22722
- try {
22723
- envelope = decodeEnvelopeFrame(frame);
22724
- } catch (error) {
22725
- console.error(
22726
- "[rogatio-host] decodeEnvelopeFrame failed:",
22727
- error instanceof Error ? error.message : String(error)
22728
- );
22729
- return null;
22730
- }
22731
- console.error(
22732
- "[rogatio-host] received envelope:",
22733
- envelope.type,
22734
- "requestId:",
22735
- envelope.requestId
22736
- );
22737
- let response;
22738
- try {
22739
- response = await controller.handleEnvelope(envelope);
22740
- } catch (error) {
22741
- console.error(
22742
- "[rogatio-host] handleEnvelope failed:",
22743
- error instanceof Error ? error.message : String(error)
22744
- );
22745
- return null;
22746
- }
22747
- try {
22748
- return encodeEnvelopeFrame(response);
22749
- } catch {
22750
- return null;
22751
- }
22752
- }
22753
- };
22754
- }
22755
- async function runNativeHost(options) {
22756
- const host = createNativeHost(options);
22757
- await host.start();
22758
- const stdin = options.stdin ?? process.stdin;
22759
- const stdout = options.stdout ?? process.stdout;
22760
- if (options.onReady) options.onReady();
22761
- let buffer = Buffer2.alloc(0);
22762
- stdin.on("data", (chunk) => {
22763
- const buf = Buffer2.isBuffer(chunk) ? chunk : Buffer2.from(chunk);
22764
- console.error(
22765
- "[rogatio-host] stdin data:",
22766
- buf.length,
22767
- "bytes, buffer:",
22768
- buffer.length
22769
- );
22770
- buffer = Buffer2.concat([buffer, buf]);
22771
- void (async () => {
22772
- for (; ; ) {
22773
- if (buffer.length < 4) break;
22774
- const length = buffer.readUInt32LE(0);
22775
- if (buffer.length < 4 + length) break;
22776
- const frame = new Uint8Array(
22777
- buffer.buffer,
22778
- buffer.byteOffset,
22779
- 4 + length
22780
- );
22781
- console.error("[rogatio-host] processing frame:", 4 + length, "bytes");
22782
- const response = await host.processFrame(frame);
22783
- buffer = buffer.subarray(4 + length);
22784
- if (response && stdout.writable) {
22785
- console.error(
22786
- "[rogatio-host] writing response:",
22787
- response.length,
22788
- "bytes"
22789
- );
22790
- stdout.write(Buffer2.from(response));
22791
- } else {
22792
- console.error("[rogatio-host] no response to write");
22793
- }
22794
- }
22795
- })();
22796
- });
22797
- await new Promise((resolve22) => {
22798
- stdin.on("end", () => {
22799
- void host.stop().then(resolve22);
22800
- });
22801
- });
22802
- }
22803
- var NATIVE_FRAME_MAX_BYTES = 64 * 1024;
22804
- var NATIVE_POLICY_MAX_BYTES = 256 * 1024;
22805
- var PRIVATE_RANGES = [
22806
- { start: ipToInt("10.0.0.0"), end: ipToInt("10.255.255.255") },
22807
- { start: ipToInt("172.16.0.0"), end: ipToInt("172.31.255.255") },
22808
- { start: ipToInt("192.168.0.0"), end: ipToInt("192.168.255.255") },
22809
- { start: ipToInt("127.0.0.0"), end: ipToInt("127.255.255.255") },
22810
- { start: ipToInt("169.254.0.0"), end: ipToInt("169.254.255.255") },
22811
- { start: ipToInt("0.0.0.0"), end: ipToInt("0.255.255.255") },
22812
- { start: ipToInt("224.0.0.0"), end: ipToInt("239.255.255.255") },
22813
- { start: ipToInt("240.0.0.0"), end: ipToInt("255.255.255.255") }
22814
- ];
22815
- function ipToInt(ip) {
22816
- const parts = ip.split(".").map(Number);
22817
- return parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3];
23612
+ presetDigest: preset.digest
23613
+ });
23614
+ if (!rendered.ok) {
23615
+ const body = JSON.stringify({
23616
+ ok: false,
23617
+ error: { code: rendered.error.code }
23618
+ });
23619
+ const metadata2 = {
23620
+ status: 500,
23621
+ headers: [["Content-Type", "application/json"]],
23622
+ mockBody: base64(new TextEncoder().encode(body))
23623
+ };
23624
+ return {
23625
+ protocol: "v1",
23626
+ type: "mock.response",
23627
+ ...requestId !== void 0 ? { requestId } : {},
23628
+ timestamp,
23629
+ metadata: metadata2
23630
+ };
23631
+ }
23632
+ const headers = [];
23633
+ for (const header of rendered.value.headers) {
23634
+ headers.push([header[0], header[1]]);
23635
+ }
23636
+ const metadata = {
23637
+ status: rendered.value.status,
23638
+ headers,
23639
+ mockBody: base64(rendered.value.bodyBytes)
23640
+ };
23641
+ return {
23642
+ protocol: "v1",
23643
+ type: "mock.response",
23644
+ ...requestId !== void 0 ? { requestId } : {},
23645
+ timestamp,
23646
+ metadata
23647
+ };
23648
+ }
22818
23649
  }
22819
23650
  var import_reflect_metadata = __toESM(require_Reflect(), 1);
22820
23651
  var import_x509 = __toESM(require_x509_cjs(), 1);
@@ -22851,7 +23682,7 @@ async function createCertificate(subjectName, _privateKey, validityDays) {
22851
23682
  return { certPem, keyPem };
22852
23683
  }
22853
23684
  function generateCaKeyPair(bits = 2048) {
22854
- return generateKeyPairSync2("rsa", { modulusLength: bits });
23685
+ return generateKeyPairSync("rsa", { modulusLength: bits });
22855
23686
  }
22856
23687
  var TRUST_LIMITS = {
22857
23688
  manifestMaxBytes: 4096,
@@ -23176,6 +24007,323 @@ function createRequestBodyTrustController(options = {}) {
23176
24007
  }
23177
24008
  return { install, uninstall, status, verify };
23178
24009
  }
24010
+ function encodeEnvelopeFrame(envelope) {
24011
+ const json = new TextEncoder().encode(serializeEnvelope(envelope));
24012
+ const out = new Uint8Array(4 + json.byteLength);
24013
+ new DataView(out.buffer).setUint32(0, json.byteLength, true);
24014
+ out.set(json, 4);
24015
+ return out;
24016
+ }
24017
+ function decodeEnvelopeFrame(buffer) {
24018
+ if (buffer.byteLength < 4) throw new Error("frame too small");
24019
+ const length = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(
24020
+ 0,
24021
+ true
24022
+ );
24023
+ if (buffer.byteLength !== 4 + length)
24024
+ throw new Error("frame length mismatch");
24025
+ const json = new TextDecoder("utf-8", { fatal: true }).decode(
24026
+ buffer.slice(4)
24027
+ );
24028
+ return parseEnvelope(json);
24029
+ }
24030
+ function startMockFaucet(port, controller) {
24031
+ const server = createServer2((req, res) => {
24032
+ void (async () => {
24033
+ try {
24034
+ const url = new URL(req.url ?? "", "http://localhost");
24035
+ const match = /^\/mock\/([a-f0-9]+)$/.exec(url.pathname);
24036
+ if (!match || req.method !== "GET") {
24037
+ res.writeHead(404);
24038
+ res.end();
24039
+ return;
24040
+ }
24041
+ const result = await controller.serveMock(match[1]);
24042
+ if (!result.ok) {
24043
+ res.writeHead(404);
24044
+ res.end();
24045
+ return;
24046
+ }
24047
+ const { status, headers, bodyBytes } = result.value;
24048
+ res.writeHead(
24049
+ status,
24050
+ Object.fromEntries(
24051
+ headers.map((h) => [h[0], h[1]])
24052
+ )
24053
+ );
24054
+ res.end(Buffer2.from(bodyBytes));
24055
+ } catch {
24056
+ if (!res.headersSent) res.writeHead(500);
24057
+ res.end();
24058
+ }
24059
+ })();
24060
+ });
24061
+ return new Promise(
24062
+ (resolve22) => server.listen(port, () => resolve22(server))
24063
+ );
24064
+ }
24065
+ function defaultTrustRoot() {
24066
+ return defaultTrustInstallRoot(process.platform);
24067
+ }
24068
+ function createNativeHost(options) {
24069
+ const controller = createNativeRuntimeController({
24070
+ preset: options.preset,
24071
+ fileRoot: options.fileRoot,
24072
+ ...options.mockPort !== void 0 ? { mockPort: options.mockPort } : {},
24073
+ ...options.aiProviderConfig !== void 0 ? { aiProviderConfig: options.aiProviderConfig } : {},
24074
+ ...options.clock ? { clock: options.clock } : {}
24075
+ });
24076
+ let faucet = null;
24077
+ let interceptProxy = null;
24078
+ let outboundWrite = null;
24079
+ let hostRequestCounter = 0;
24080
+ const pendingHost = /* @__PURE__ */ new Map();
24081
+ function sendHostRequest(type, metadata) {
24082
+ const requestId = `host-${++hostRequestCounter}`;
24083
+ const envelope = {
24084
+ protocol: "v1",
24085
+ type,
24086
+ requestId,
24087
+ timestamp: Date.now(),
24088
+ metadata
24089
+ };
24090
+ return new Promise((resolve22, reject) => {
24091
+ if (!outboundWrite) {
24092
+ reject(new Error("host stdout not attached"));
24093
+ return;
24094
+ }
24095
+ pendingHost.set(requestId, { resolve: resolve22, reject });
24096
+ try {
24097
+ outboundWrite(encodeEnvelopeFrame(envelope));
24098
+ } catch (error) {
24099
+ pendingHost.delete(requestId);
24100
+ reject(error instanceof Error ? error : new Error(String(error)));
24101
+ return;
24102
+ }
24103
+ setTimeout(() => {
24104
+ if (pendingHost.has(requestId)) {
24105
+ pendingHost.delete(requestId);
24106
+ reject(new Error("host pac request timed out"));
24107
+ }
24108
+ }, 1e4);
24109
+ });
24110
+ }
24111
+ const trustRoot = options.trustRoot ?? defaultTrustRoot();
24112
+ const caKeyFile = join3(trustRoot, ".rogatio-ca.key");
24113
+ const caCertFile = join3(trustRoot, ".rogatio-ca.crt");
24114
+ const platformAdapter = {
24115
+ platform: process.platform,
24116
+ detect() {
24117
+ const trusted = existsSync(caKeyFile) && existsSync(caCertFile);
24118
+ return {
24119
+ supported: true,
24120
+ reasons: [],
24121
+ trustedDeviceLocalCa: trusted,
24122
+ controllingProxy: false,
24123
+ controllingPac: false,
24124
+ controllingExtension: false,
24125
+ enterprisePolicy: false
24126
+ };
24127
+ },
24128
+ async provisionOrVerifyCa() {
24129
+ return existsSync(caKeyFile) && existsSync(caCertFile);
24130
+ },
24131
+ async installPac(script) {
24132
+ const response = await sendHostRequest("runtime.pac.install", {
24133
+ script
24134
+ });
24135
+ if (response.metadata.ok !== true) {
24136
+ const error = typeof response.metadata.error === "string" ? response.metadata.error : "pac-install-failed";
24137
+ throw new Error(error);
24138
+ }
24139
+ },
24140
+ async removePac() {
24141
+ try {
24142
+ await sendHostRequest("runtime.pac.remove", {});
24143
+ } catch {
24144
+ }
24145
+ },
24146
+ async startTlsProxy() {
24147
+ if (interceptProxy) {
24148
+ await interceptProxy.stop();
24149
+ interceptProxy = null;
24150
+ }
24151
+ const policy = controller.getActivePolicy();
24152
+ interceptProxy = await startInterceptProxy({
24153
+ policy: policy ? { project: policy.project, operations: policy.operations } : null
24154
+ });
24155
+ return interceptProxy.endpoint;
24156
+ },
24157
+ async stopTlsProxy() {
24158
+ if (interceptProxy) {
24159
+ await interceptProxy.stop();
24160
+ interceptProxy = null;
24161
+ }
24162
+ }
24163
+ };
24164
+ const platformProvider = createPlatformInterceptionProvider(platformAdapter);
24165
+ let pendingOrigins = [];
24166
+ registerInterceptionProvider({
24167
+ platform: platformProvider.platform,
24168
+ detect: () => platformProvider.detect(),
24169
+ async start(activation) {
24170
+ const origins = pendingOrigins.length > 0 ? pendingOrigins : activation.pacOrigins;
24171
+ const endpoint = await platformProvider.start(activation, origins);
24172
+ const policy = controller.getActivePolicy();
24173
+ interceptProxy?.setPolicy(
24174
+ policy ? { project: policy.project, operations: policy.operations } : null
24175
+ );
24176
+ return endpoint;
24177
+ },
24178
+ async stop() {
24179
+ await platformProvider.stop();
24180
+ }
24181
+ });
24182
+ const handle = {
24183
+ controller,
24184
+ mockPort: options.mockPort ?? null,
24185
+ async start() {
24186
+ await controller.start();
24187
+ if (options.mockPort !== void 0) {
24188
+ faucet = await startMockFaucet(options.mockPort, controller);
24189
+ }
24190
+ },
24191
+ async stop() {
24192
+ if (faucet) {
24193
+ await new Promise((resolve22) => faucet?.close(() => resolve22()));
24194
+ faucet = null;
24195
+ }
24196
+ await controller.stop();
24197
+ clearSession();
24198
+ if (interceptProxy) {
24199
+ await interceptProxy.stop();
24200
+ interceptProxy = null;
24201
+ }
24202
+ },
24203
+ async processFrame(frame) {
24204
+ let envelope;
24205
+ try {
24206
+ envelope = decodeEnvelopeFrame(frame);
24207
+ } catch (error) {
24208
+ console.error(
24209
+ "[rogatio-host] decodeEnvelopeFrame failed:",
24210
+ error instanceof Error ? error.message : String(error)
24211
+ );
24212
+ return null;
24213
+ }
24214
+ if (typeof envelope.requestId === "string" && envelope.requestId.startsWith("host-")) {
24215
+ const pending = pendingHost.get(envelope.requestId);
24216
+ if (pending) {
24217
+ pendingHost.delete(envelope.requestId);
24218
+ pending.resolve(envelope);
24219
+ return null;
24220
+ }
24221
+ }
24222
+ if (envelope.type === "runtime.start") {
24223
+ const origins = envelope.metadata.pacOrigins;
24224
+ pendingOrigins = Array.isArray(origins) ? origins.filter((o) => typeof o === "string") : [];
24225
+ }
24226
+ console.error(
24227
+ "[rogatio-host] received envelope:",
24228
+ envelope.type,
24229
+ "requestId:",
24230
+ envelope.requestId
24231
+ );
24232
+ let response;
24233
+ try {
24234
+ response = await controller.handleEnvelope(envelope);
24235
+ } catch (error) {
24236
+ console.error(
24237
+ "[rogatio-host] handleEnvelope failed:",
24238
+ error instanceof Error ? error.message : String(error)
24239
+ );
24240
+ return null;
24241
+ }
24242
+ try {
24243
+ return encodeEnvelopeFrame(response);
24244
+ } catch {
24245
+ return null;
24246
+ }
24247
+ }
24248
+ };
24249
+ Object.defineProperty(handle, "__setOutboundWrite", {
24250
+ value(write) {
24251
+ outboundWrite = write;
24252
+ },
24253
+ enumerable: false
24254
+ });
24255
+ return handle;
24256
+ }
24257
+ async function runNativeHost(options) {
24258
+ const host = createNativeHost(options);
24259
+ const setOutbound = host.__setOutboundWrite;
24260
+ const stdout = options.stdout ?? process.stdout;
24261
+ if (setOutbound) {
24262
+ setOutbound((frame) => {
24263
+ if (stdout.writable) stdout.write(Buffer2.from(frame));
24264
+ });
24265
+ }
24266
+ await host.start();
24267
+ const stdin = options.stdin ?? process.stdin;
24268
+ if (options.onReady) options.onReady();
24269
+ let buffer = Buffer2.alloc(0);
24270
+ stdin.on("data", (chunk) => {
24271
+ const buf = Buffer2.isBuffer(chunk) ? Buffer2.from(chunk) : Buffer2.from(chunk);
24272
+ console.error(
24273
+ "[rogatio-host] stdin data:",
24274
+ buf.length,
24275
+ "bytes, buffer:",
24276
+ buffer.length
24277
+ );
24278
+ buffer = Buffer2.concat([buffer, buf]);
24279
+ void (async () => {
24280
+ for (; ; ) {
24281
+ if (buffer.length < 4) break;
24282
+ const length = buffer.readUInt32LE(0);
24283
+ if (buffer.length < 4 + length) break;
24284
+ const frame = new Uint8Array(
24285
+ buffer.buffer,
24286
+ buffer.byteOffset,
24287
+ 4 + length
24288
+ );
24289
+ console.error("[rogatio-host] processing frame:", 4 + length, "bytes");
24290
+ const response = await host.processFrame(frame);
24291
+ buffer = buffer.subarray(4 + length);
24292
+ if (response && stdout.writable) {
24293
+ console.error(
24294
+ "[rogatio-host] writing response:",
24295
+ response.length,
24296
+ "bytes"
24297
+ );
24298
+ stdout.write(Buffer2.from(response));
24299
+ } else {
24300
+ console.error("[rogatio-host] no response to write");
24301
+ }
24302
+ }
24303
+ })();
24304
+ });
24305
+ await new Promise((resolve22) => {
24306
+ stdin.on("end", () => {
24307
+ void host.stop().then(resolve22);
24308
+ });
24309
+ });
24310
+ }
24311
+ var NATIVE_FRAME_MAX_BYTES = 64 * 1024;
24312
+ var NATIVE_POLICY_MAX_BYTES = 256 * 1024;
24313
+ var PRIVATE_RANGES = [
24314
+ { start: ipToInt("10.0.0.0"), end: ipToInt("10.255.255.255") },
24315
+ { start: ipToInt("172.16.0.0"), end: ipToInt("172.31.255.255") },
24316
+ { start: ipToInt("192.168.0.0"), end: ipToInt("192.168.255.255") },
24317
+ { start: ipToInt("127.0.0.0"), end: ipToInt("127.255.255.255") },
24318
+ { start: ipToInt("169.254.0.0"), end: ipToInt("169.254.255.255") },
24319
+ { start: ipToInt("0.0.0.0"), end: ipToInt("0.255.255.255") },
24320
+ { start: ipToInt("224.0.0.0"), end: ipToInt("239.255.255.255") },
24321
+ { start: ipToInt("240.0.0.0"), end: ipToInt("255.255.255.255") }
24322
+ ];
24323
+ function ipToInt(ip) {
24324
+ const parts = ip.split(".").map(Number);
24325
+ return parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3];
24326
+ }
23179
24327
  var darwinAdapter = {
23180
24328
  platform: "darwin",
23181
24329
  defaultManifestDir: () => {
@@ -23217,7 +24365,7 @@ var darwinAdapter = {
23217
24365
  },
23218
24366
  async caTrustInstaller(certPem) {
23219
24367
  const keychain = this.defaultCaInstallPath();
23220
- const tmpPath = join3(tmpdir(), `rogatio-ca-${Date.now()}.crt`);
24368
+ const tmpPath = join4(tmpdir(), `rogatio-ca-${Date.now()}.crt`);
23221
24369
  try {
23222
24370
  writeFileSync(tmpPath, certPem, "utf8");
23223
24371
  } catch {
@@ -23449,7 +24597,7 @@ var win32Adapter = {
23449
24597
  };
23450
24598
  },
23451
24599
  async caTrustInstaller(certPem) {
23452
- const tmpPath = join4(tmpdir2(), `rogatio-ca-${Date.now()}.crt`);
24600
+ const tmpPath = join5(tmpdir2(), `rogatio-ca-${Date.now()}.crt`);
23453
24601
  try {
23454
24602
  writeFileSync2(tmpPath, certPem, "utf8");
23455
24603
  } catch {
@@ -23869,7 +25017,7 @@ var HttpServerError = class extends Error {
23869
25017
  this.code = code;
23870
25018
  }
23871
25019
  };
23872
- function createServer2(handler, options = {}) {
25020
+ function createServer3(handler, options = {}) {
23873
25021
  const server = createHttpServer(handler);
23874
25022
  const preferredPort = options.port;
23875
25023
  let port = null;
@@ -24937,7 +26085,7 @@ import {
24937
26085
  unlink as unlink2,
24938
26086
  writeFile as writeFile3
24939
26087
  } from "node:fs/promises";
24940
- import { basename as basename2, dirname as dirname6, join as join5 } from "node:path";
26088
+ import { basename as basename2, dirname as dirname6, join as join6 } from "node:path";
24941
26089
  var ProjectStorageError = class extends Error {
24942
26090
  code;
24943
26091
  id;
@@ -25100,7 +26248,7 @@ async function isDirectoryPath(id2) {
25100
26248
  }
25101
26249
  async function writeDocument(id2, data) {
25102
26250
  const tempName = `.${basename2(dirname6(id2))}.${randomBytes4(8).toString("hex")}.tmp`;
25103
- const tempPath = join5(dirname6(id2), tempName);
26251
+ const tempPath = join6(dirname6(id2), tempName);
25104
26252
  try {
25105
26253
  if (await isDirectoryPath(id2)) {
25106
26254
  throw new ProjectFileError(
@@ -25159,7 +26307,7 @@ function createJsonFileProjectStorage() {
25159
26307
  if (entry.isDirectory() || !isRogatioProjectFilename(entry.name)) {
25160
26308
  continue;
25161
26309
  }
25162
- const id2 = join5(scope, entry.name);
26310
+ const id2 = join6(scope, entry.name);
25163
26311
  let name = "";
25164
26312
  try {
25165
26313
  const data = await readDocument(id2);
@@ -25348,7 +26496,7 @@ async function editCommand(args, options = {}) {
25348
26496
  };
25349
26497
  let server;
25350
26498
  try {
25351
- server = createServer2(
26499
+ server = createServer3(
25352
26500
  createRoutes(context),
25353
26501
  port !== void 0 ? { port } : {}
25354
26502
  );
@@ -25628,7 +26776,7 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
25628
26776
 
25629
26777
  // packages/cli/src/commands/runtime.ts
25630
26778
  import { chmod, mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
25631
- import { dirname as dirname7, join as join6, resolve as resolve5 } from "node:path";
26779
+ import { dirname as dirname7, join as join7, resolve as resolve5 } from "node:path";
25632
26780
  function toMatcherOperations2(operations) {
25633
26781
  return operations.map(
25634
26782
  ({ groupId, ruleId, name, matcher, redactSensitiveInLogs }) => ({
@@ -25655,7 +26803,7 @@ exec "${nodeBin}" "${cliScript}" runtime host 2>>"${logPath}"
25655
26803
  }
25656
26804
  async function writeHostWrapper(hostPath, installRoot) {
25657
26805
  await mkdir4(installRoot, { recursive: true });
25658
- const logPath = join6(installRoot, "runtime-host.log");
26806
+ const logPath = join7(installRoot, "runtime-host.log");
25659
26807
  const script = generateHostWrapper(
25660
26808
  `${process.execPath} ${process.argv[1] ?? "rogatio"}`,
25661
26809
  logPath
@@ -25670,7 +26818,7 @@ function makeTrustController() {
25670
26818
  return createRequestBodyTrustController({
25671
26819
  platform,
25672
26820
  installRoot,
25673
- hostPath: join6(installRoot, "runtime-host"),
26821
+ hostPath: join7(installRoot, "runtime-host"),
25674
26822
  hostName: "com.rogatio.runtime",
25675
26823
  allowedOrigins: [],
25676
26824
  manifestDir: adapter.defaultManifestDir(),
@@ -25727,7 +26875,7 @@ async function trustRuntimeCommand(args) {
25727
26875
  }
25728
26876
  const controller = makeTrustController();
25729
26877
  const installRoot = defaultTrustInstallRoot(process.platform);
25730
- const hostPath = join6(installRoot, "runtime-host");
26878
+ const hostPath = join7(installRoot, "runtime-host");
25731
26879
  switch (subcommand) {
25732
26880
  case "install": {
25733
26881
  const installResult = await controller.install(extensionId ?? "");
@@ -25753,7 +26901,7 @@ async function trustRuntimeCommand(args) {
25753
26901
  } catch {
25754
26902
  }
25755
26903
  try {
25756
- await rm2(join6(installRoot, "runtime-host.log"), { force: true });
26904
+ await rm2(join7(installRoot, "runtime-host.log"), { force: true });
25757
26905
  } catch {
25758
26906
  }
25759
26907
  return reportTrust(