@rogatio/cli 6.21.0 → 6.21.2

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.
package/README.md CHANGED
@@ -42,7 +42,7 @@ rogatio <command> [options]
42
42
  | `rogatio import requestly <export.json>` | Migrate a Requestly export into `.rogatio.json`. `--out <path>` chooses the file (default `.rogatio.json`); `--merge` appends onto an existing project; `--json` prints the report. |
43
43
  | `rogatio test [path] [url...]` | Run offline dry-run tests. Any filename is accepted; the default is `.rogatio.json` in the current directory. |
44
44
  | `rogatio ai <setup\|ls\|show\|delete\|test>` | AI provider configuration. `setup` interactive; `ls` list; `show` redacted; `delete` remove; `test` connection. |
45
- | `rogatio runtime <install\|uninstall\|verify>` | Register the native-messaging host with `install` and trust the device-local CA in one **transactional** call; it needs elevated privileges (Linux `sudo`, macOS keychain authorization, Windows Administrator) and rolls back with exit `1` and `trust unsupported: <reasons>` when a capability or elevation is missing. Release users never pass `--extension-id`. That flag is only for development (a local unpacked build without the release key) and for forks. `uninstall` removes the host manifest, the device-local CA files, and the CA trust installation (idempotent). `verify` reports whether manifest, `runtime-host` wrapper, allowed origins, and CA trust are all valid, and fails when `allowed_origins` omits the release ID. Release users never pass `--extension-id` on verify either. |
45
+ | `rogatio runtime <install\|uninstall\|verify>` | Register the native-messaging host with `install` and trust the device-local CA in one **transactional** call; it needs elevated privileges (Linux `sudo`, macOS keychain authorization, Windows Administrator) and rolls back with exit `1` and `trust unsupported: <reasons>` when a capability or elevation is missing. Release users never pass `--extension-id`. That flag is only for development (a local unpacked build without the release key) and for forks. `uninstall` removes the host manifest, the device-local CA files, and the CA trust installation (idempotent). If the trust-store refresh fails, it exits `1` and does not print `runtime uninstall complete`. `verify` reports whether manifest, `runtime-host` wrapper, allowed origins, and CA trust are all valid, and fails when `allowed_origins` omits the release ID. Release users never pass `--extension-id` on verify either. |
46
46
  | `rogatio runtime host [path]` | Run the consolidated native-messaging host for the project (pair/authorize/body transforms and mock responses over stdio). Normally launched by the browser; run manually only for debugging. `--root <dir>` overrides the confined mock file root. When omitted, a saved device-local root for that project file is used, otherwise the project directory. A stdin project that contains a file mock requires `--root`. |
47
47
 
48
48
  `rogatio edit` includes **Run checks**. It calls `POST /e/<session>/api/doctor` (session path, loopback Origin, and `X-CSRF-Token`) and shows editor checks plus the same report as `rogatio doctor` for the open file. A provider configured after the page opened is picked up on the next `rogatio edit`.
@@ -2306,7 +2306,7 @@ import { lstat as lstat3, mkdtemp, open as open4, rm as rm2 } from "node:fs/prom
2306
2306
  import { tmpdir } from "node:os";
2307
2307
  import { join as join3 } from "node:path";
2308
2308
  import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
2309
- import { accessSync as accessSync2, constants as constants5, mkdirSync, unlinkSync } from "node:fs";
2309
+ import { accessSync as accessSync2, constants as constants5, mkdirSync } from "node:fs";
2310
2310
  import { dirname as dirname4 } from "node:path";
2311
2311
  import { join as posixJoin2 } from "node:path/posix";
2312
2312
  import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
@@ -2318,18 +2318,19 @@ import { isAbsolute as isAbsolute3 } from "node:path";
2318
2318
  import { Buffer as Buffer2 } from "node:buffer";
2319
2319
  import { existsSync as existsSync2 } from "node:fs";
2320
2320
  import { join as join6 } from "node:path";
2321
+ import { timingSafeEqual } from "node:crypto";
2321
2322
  import { lookup as dnsLookup2 } from "node:dns/promises";
2322
2323
  import {
2323
2324
  createServer as createServer2
2324
2325
  } from "node:http";
2325
2326
  import { connect } from "node:net";
2326
2327
  import { Worker } from "node:worker_threads";
2328
+ import { randomBytes } from "node:crypto";
2327
2329
  import { lookup as dnsLookup } from "node:dns/promises";
2328
2330
  import { request as httpRequest2 } from "node:http";
2329
2331
  import { request as httpsRequest } from "node:https";
2330
- import { createHash as createHash2, randomBytes, timingSafeEqual } from "node:crypto";
2332
+ import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
2331
2333
  import { spawn as spawn4 } from "node:child_process";
2332
- import { randomBytes as randomBytes2 } from "node:crypto";
2333
2334
  import { createHash as createHash3 } from "node:crypto";
2334
2335
  import { createHash as createHash4 } from "node:crypto";
2335
2336
  var __create = Object.create;
@@ -23705,9 +23706,10 @@ var linuxAdapter = {
23705
23706
  throw new TrustError("trust.internal", reason, [reason]);
23706
23707
  },
23707
23708
  async caTrustRemover() {
23708
- try {
23709
- unlinkSync(DEBIAN_CERT);
23710
- } catch {
23709
+ if (caBackend() === "debian") {
23710
+ await sudoOrThrow(["rm", "-f", DEBIAN_CERT]);
23711
+ await sudoOrThrow(["update-ca-certificates"]);
23712
+ return;
23711
23713
  }
23712
23714
  if (!toolOnPath("update-ca-trust")) return;
23713
23715
  const present = P11_CERTS.filter((path2) => exists(path2));
@@ -25024,6 +25026,74 @@ async function sourceMatchesWithinDeadline(source, url) {
25024
25026
  function matchUrlCapturesWithinDeadline(urlRegex, url) {
25025
25027
  return runRegexExec(url, urlRegex, RUNTIME_LIMITS.maxRegexDeadlineMs);
25026
25028
  }
25029
+ async function readMockFile(root, logicalPath, signal) {
25030
+ return readConfinedPathBytes(root, logicalPath, signal);
25031
+ }
25032
+ function mintToken() {
25033
+ return randomBytes(32).toString("hex");
25034
+ }
25035
+ function headerName(name) {
25036
+ return name.toLowerCase();
25037
+ }
25038
+ function waitForDelay(delayMs, signal) {
25039
+ if (signal?.aborted) return Promise.resolve(failure("runtime.timeout"));
25040
+ if (delayMs === void 0 || delayMs <= 0) {
25041
+ return Promise.resolve({ ok: true, value: void 0 });
25042
+ }
25043
+ return new Promise((resolve12) => {
25044
+ const onAbort = () => {
25045
+ clearTimeout(timer);
25046
+ resolve12(failure("runtime.timeout"));
25047
+ };
25048
+ const timer = setTimeout(() => {
25049
+ signal?.removeEventListener("abort", onAbort);
25050
+ resolve12({ ok: true, value: void 0 });
25051
+ }, delayMs);
25052
+ signal?.addEventListener("abort", onAbort, { once: true });
25053
+ });
25054
+ }
25055
+ async function renderMockResponse(options) {
25056
+ const { mock, fileRoot, method, signal } = options;
25057
+ const waited = await waitForDelay(mock.delayMs, signal);
25058
+ if (!waited.ok) return waited;
25059
+ let fullBytes;
25060
+ if (mock.body !== void 0) {
25061
+ fullBytes = new TextEncoder().encode(mock.body);
25062
+ } else if (mock.file !== void 0) {
25063
+ if (fileRoot === void 0) return failure("runtime.file-denied");
25064
+ if (!isConfinedFileSupported()) {
25065
+ return failure("runtime.platform-unsupported");
25066
+ }
25067
+ const read2 = await readMockFile(fileRoot, mock.file, signal);
25068
+ if (!read2.ok) return read2;
25069
+ fullBytes = read2.value;
25070
+ } else {
25071
+ return failure("runtime.file-denied");
25072
+ }
25073
+ const headers = [];
25074
+ let hasContentType = false;
25075
+ let hasCacheControl = false;
25076
+ for (const header of mock.headers ?? []) {
25077
+ const name = headerName(header.name);
25078
+ if (name === "content-length") continue;
25079
+ if (name === "content-type") hasContentType = true;
25080
+ if (name === "cache-control") hasCacheControl = true;
25081
+ headers.push([header.name, header.value]);
25082
+ }
25083
+ if (!hasContentType) {
25084
+ headers.push(["Content-Type", "application/octet-stream"]);
25085
+ }
25086
+ headers.push(["Content-Length", String(fullBytes.byteLength)]);
25087
+ if (!hasCacheControl) headers.push(["Cache-Control", "no-store"]);
25088
+ return {
25089
+ ok: true,
25090
+ value: {
25091
+ status: mock.status,
25092
+ headers,
25093
+ bodyBytes: method === "HEAD" ? new Uint8Array() : fullBytes
25094
+ }
25095
+ };
25096
+ }
25027
25097
  var MOCK_LISTENER_PREFIX = "/.rogatio/mock/";
25028
25098
  function isSingleSegment(value) {
25029
25099
  return value.length > 0 && value.length <= 2048 && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && !value.includes("?") && !hasControl(value);
@@ -25402,6 +25472,9 @@ async function fetchAndRewriteAuthorizedResponse(operation, action, options = {}
25402
25472
  };
25403
25473
  }
25404
25474
  var RESPONSE_BODY_LISTENER_PREFIX = "/.rogatio/body/";
25475
+ function isSingleSegment2(value) {
25476
+ return value.length > 0 && value.length <= 2048 && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && !value.includes("?") && !hasControl(value);
25477
+ }
25405
25478
  function parseResponseBodyRedirect(rawUrl) {
25406
25479
  const queryAt = rawUrl.indexOf("?");
25407
25480
  const path2 = queryAt === -1 ? rawUrl : rawUrl.slice(0, queryAt);
@@ -25412,18 +25485,24 @@ function parseResponseBodyRedirect(rawUrl) {
25412
25485
  if (slash1 <= 0) return null;
25413
25486
  const slash2 = rest.indexOf("/", slash1 + 1);
25414
25487
  if (slash2 <= slash1 + 1) return null;
25488
+ const slash3 = rest.indexOf("/", slash2 + 1);
25489
+ if (slash3 <= slash2 + 1) return null;
25490
+ let token;
25415
25491
  let ruleId;
25416
25492
  let digest;
25417
25493
  try {
25418
- ruleId = decodeURIComponent(rest.slice(0, slash1));
25419
- digest = decodeURIComponent(rest.slice(slash1 + 1, slash2));
25494
+ token = decodeURIComponent(rest.slice(0, slash1));
25495
+ ruleId = decodeURIComponent(rest.slice(slash1 + 1, slash2));
25496
+ digest = decodeURIComponent(rest.slice(slash2 + 1, slash3));
25420
25497
  } catch {
25421
25498
  return null;
25422
25499
  }
25423
- if (ruleId.length === 0 || digest.length === 0) return null;
25424
- const original = rest.slice(slash2 + 1) + search;
25500
+ if (!isSingleSegment2(token) || !isSingleSegment2(ruleId) || !isSingleSegment2(digest)) {
25501
+ return null;
25502
+ }
25503
+ const original = rest.slice(slash3 + 1) + search;
25425
25504
  if (!/^https?:\/\//i.test(original)) return null;
25426
- return { ruleId, digest, originalUrl: original };
25505
+ return { token, ruleId, digest, originalUrl: original };
25427
25506
  }
25428
25507
  function originOf(value) {
25429
25508
  const direct = normalizeSiteOrigin(value);
@@ -25492,8 +25571,12 @@ async function revalidateAuthority(project, operations, request) {
25492
25571
  }
25493
25572
  }
25494
25573
  if (matcher.resourceTypes.length > 0) {
25495
- const resourceType = typeof request.resourceType === "string" ? request.resourceType : void 0;
25496
- if (resourceType === void 0 || !matcher.resourceTypes.includes(resourceType)) {
25574
+ const supplied = request.resourceType;
25575
+ if (supplied === void 0) {
25576
+ if (operation.kind !== "response-body") {
25577
+ return { allowed: false, reason: "resource-type-unauthorized" };
25578
+ }
25579
+ } else if (typeof supplied !== "string" || !matcher.resourceTypes.includes(supplied)) {
25497
25580
  return { allowed: false, reason: "resource-type-unauthorized" };
25498
25581
  }
25499
25582
  }
@@ -25504,6 +25587,24 @@ async function revalidateAuthority(project, operations, request) {
25504
25587
  operation
25505
25588
  };
25506
25589
  }
25590
+ var PROXY_DOCUMENT_CSP = "sandbox; default-src 'none'; script-src 'none'; connect-src 'none'; form-action 'none'; frame-src 'none'";
25591
+ var DROPPED_DOCUMENT_HEADERS = /* @__PURE__ */ new Set([
25592
+ "content-security-policy",
25593
+ "content-security-policy-report-only",
25594
+ "x-content-type-options"
25595
+ ]);
25596
+ function sameToken(supplied, expected) {
25597
+ const left = Buffer.from(supplied);
25598
+ const right = Buffer.from(expected);
25599
+ if (left.byteLength !== right.byteLength) return false;
25600
+ return timingSafeEqual(left, right);
25601
+ }
25602
+ function proxyDocumentHeaders() {
25603
+ return [
25604
+ ["content-security-policy", PROXY_DOCUMENT_CSP],
25605
+ ["x-content-type-options", "nosniff"]
25606
+ ];
25607
+ }
25507
25608
  function isBodyOp(op) {
25508
25609
  return op.kind === "request-body" || op.kind === "response-body";
25509
25610
  }
@@ -25906,6 +26007,8 @@ async function authorizeOp(policy, op, url, method, headers) {
25906
26007
  return decision.allowed;
25907
26008
  }
25908
26009
  async function startInterceptProxy(options = {}) {
26010
+ const responseBodyToken = mintToken();
26011
+ const outbound = options.outbound;
25909
26012
  let policy = options.policy ?? null;
25910
26013
  const serveMock = options.serveMock;
25911
26014
  const resolver = options.resolver ?? defaultResolver2;
@@ -26126,10 +26229,11 @@ async function startInterceptProxy(options = {}) {
26126
26229
  endEmpty(res, status);
26127
26230
  return;
26128
26231
  }
26129
- res.writeHead(
26130
- rendered.value.status,
26131
- rendered.value.headers.map(([name, value]) => [name, value])
26232
+ const mockHeaders = rendered.value.headers.flatMap(
26233
+ ([name, value]) => DROPPED_DOCUMENT_HEADERS.has(name.toLowerCase()) ? [] : [[name, value]]
26132
26234
  );
26235
+ mockHeaders.push(...proxyDocumentHeaders());
26236
+ res.writeHead(rendered.value.status, mockHeaders);
26133
26237
  res.end(Buffer.from(rendered.value.bodyBytes));
26134
26238
  } catch {
26135
26239
  endEmpty(res, 502);
@@ -26146,9 +26250,19 @@ async function startInterceptProxy(options = {}) {
26146
26250
  return;
26147
26251
  }
26148
26252
  const parsed = parseResponseBodyRedirect(rawUrl);
26253
+ if (parsed === null) {
26254
+ res.writeHead(400);
26255
+ res.end();
26256
+ return;
26257
+ }
26258
+ if (!sameToken(parsed.token, responseBodyToken)) {
26259
+ res.writeHead(403);
26260
+ res.end();
26261
+ return;
26262
+ }
26149
26263
  const active = policy;
26150
- if (parsed === null || active === null || active.presetDigest === void 0) {
26151
- res.writeHead(parsed === null ? 400 : 403);
26264
+ if (active === null || active.presetDigest === void 0) {
26265
+ res.writeHead(403);
26152
26266
  res.end();
26153
26267
  return;
26154
26268
  }
@@ -26189,8 +26303,7 @@ async function startInterceptProxy(options = {}) {
26189
26303
  url: parsed.originalUrl,
26190
26304
  target,
26191
26305
  method: "GET",
26192
- initiator: initiatorFromHeaders(req.headers, parsed.originalUrl) ?? new URL(target).origin,
26193
- resourceType: operation.matcher.resourceTypes[0]
26306
+ initiator: initiatorFromHeaders(req.headers, parsed.originalUrl) ?? new URL(target).origin
26194
26307
  }
26195
26308
  );
26196
26309
  if (!allowed.allowed) {
@@ -26209,7 +26322,8 @@ async function startInterceptProxy(options = {}) {
26209
26322
  sourceValue: operation.matcher.source.value,
26210
26323
  presetDigest
26211
26324
  },
26212
- operation.responseBody
26325
+ operation.responseBody,
26326
+ outbound ?? {}
26213
26327
  );
26214
26328
  if (!fetched.ok) {
26215
26329
  res.writeHead(502);
@@ -26219,7 +26333,7 @@ async function startInterceptProxy(options = {}) {
26219
26333
  const headers = {};
26220
26334
  for (const [name, value] of fetched.value.headers) {
26221
26335
  const lower = name.toLowerCase();
26222
- if (lower === "set-cookie" || lower === "cookie" || lower === "authorization" || lower === "proxy-authorization") {
26336
+ if (lower === "set-cookie" || lower === "cookie" || lower === "authorization" || lower === "proxy-authorization" || DROPPED_DOCUMENT_HEADERS.has(lower)) {
26223
26337
  continue;
26224
26338
  }
26225
26339
  const existing = headers[lower];
@@ -26227,6 +26341,9 @@ async function startInterceptProxy(options = {}) {
26227
26341
  else if (Array.isArray(existing)) existing.push(value);
26228
26342
  else headers[lower] = [existing, value];
26229
26343
  }
26344
+ for (const [name, value] of proxyDocumentHeaders()) {
26345
+ headers[name] = value;
26346
+ }
26230
26347
  writeResponse(
26231
26348
  res,
26232
26349
  fetched.value.status,
@@ -26296,6 +26413,7 @@ async function startInterceptProxy(options = {}) {
26296
26413
  });
26297
26414
  return {
26298
26415
  endpoint,
26416
+ responseBodyToken,
26299
26417
  setPolicy(next) {
26300
26418
  policy = next;
26301
26419
  },
@@ -26462,7 +26580,7 @@ function createPlatformInterceptionProvider(adapter) {
26462
26580
  var TOKEN_BYTES = 32;
26463
26581
  var TOKEN_LENGTH = 43;
26464
26582
  function createCapabilityState(preset, now) {
26465
- const bootstrap = randomBytes(TOKEN_BYTES).toString("base64url");
26583
+ const bootstrap = randomBytes2(TOKEN_BYTES).toString("base64url");
26466
26584
  return {
26467
26585
  presetDigest: preset.digest,
26468
26586
  bootstrap,
@@ -26485,15 +26603,15 @@ function isToken(value) {
26485
26603
  return false;
26486
26604
  }
26487
26605
  }
26488
- function sameToken(value, expectedDigest) {
26606
+ function sameToken2(value, expectedDigest) {
26489
26607
  const actual = digestToken(value);
26490
- return actual.length === expectedDigest.length && timingSafeEqual(actual, expectedDigest);
26608
+ return actual.length === expectedDigest.length && timingSafeEqual2(actual, expectedDigest);
26491
26609
  }
26492
26610
  function sameDigest(value, expected) {
26493
26611
  if (!isSha256Digest(value) || value.length !== expected.length) return false;
26494
26612
  const actual = Buffer.from(value, "ascii");
26495
26613
  const wanted = Buffer.from(expected, "ascii");
26496
- return timingSafeEqual(actual, wanted);
26614
+ return timingSafeEqual2(actual, wanted);
26497
26615
  }
26498
26616
  function cleanSessions(state, now) {
26499
26617
  for (let index = state.sessions.length - 1; index >= 0; index -= 1) {
@@ -26503,14 +26621,14 @@ function cleanSessions(state, now) {
26503
26621
  }
26504
26622
  function pairCapability(state, capability, digest, now) {
26505
26623
  cleanSessions(state, now);
26506
- if (state.closed || state.consumed || !isToken(capability) || typeof digest !== "string" || !sameDigest(digest, state.presetDigest) || now >= state.bootstrapExpiresAt || !sameToken(capability, state.bootstrapDigest)) {
26624
+ if (state.closed || state.consumed || !isToken(capability) || typeof digest !== "string" || !sameDigest(digest, state.presetDigest) || now >= state.bootstrapExpiresAt || !sameToken2(capability, state.bootstrapDigest)) {
26507
26625
  return failure("runtime.pairing-denied");
26508
26626
  }
26509
26627
  if (state.sessions.length >= RUNTIME_LIMITS.maxConcurrentSessions) {
26510
26628
  return failure("runtime.overloaded");
26511
26629
  }
26512
26630
  state.consumed = true;
26513
- const sessionCapability = randomBytes(TOKEN_BYTES).toString("base64url");
26631
+ const sessionCapability = randomBytes2(TOKEN_BYTES).toString("base64url");
26514
26632
  const expiresAt = now + RUNTIME_LIMITS.sessionLifetimeMs;
26515
26633
  state.sessions.push({
26516
26634
  digest: Buffer.from(state.presetDigest, "ascii"),
@@ -26533,7 +26651,7 @@ function findSession(state, capability, digest, now) {
26533
26651
  if (!sameDigest(digest, state.presetDigest)) return null;
26534
26652
  const candidate = digestToken(capability);
26535
26653
  for (const session of state.sessions) {
26536
- if (candidate.length === session.tokenDigest.length && timingSafeEqual(candidate, session.tokenDigest) && sameDigest(digest, session.digest.toString("ascii"))) {
26654
+ if (candidate.length === session.tokenDigest.length && timingSafeEqual2(candidate, session.tokenDigest) && sameDigest(digest, session.digest.toString("ascii"))) {
26537
26655
  return session;
26538
26656
  }
26539
26657
  }
@@ -26649,74 +26767,6 @@ async function pickDirectory(run = runDialog, platform = process.platform) {
26649
26767
  }
26650
26768
  return { ok: false, code: "runtime.picker-unavailable" };
26651
26769
  }
26652
- async function readMockFile(root, logicalPath, signal) {
26653
- return readConfinedPathBytes(root, logicalPath, signal);
26654
- }
26655
- function mintToken() {
26656
- return randomBytes2(32).toString("hex");
26657
- }
26658
- function headerName(name) {
26659
- return name.toLowerCase();
26660
- }
26661
- function waitForDelay(delayMs, signal) {
26662
- if (signal?.aborted) return Promise.resolve(failure("runtime.timeout"));
26663
- if (delayMs === void 0 || delayMs <= 0) {
26664
- return Promise.resolve({ ok: true, value: void 0 });
26665
- }
26666
- return new Promise((resolve12) => {
26667
- const onAbort = () => {
26668
- clearTimeout(timer);
26669
- resolve12(failure("runtime.timeout"));
26670
- };
26671
- const timer = setTimeout(() => {
26672
- signal?.removeEventListener("abort", onAbort);
26673
- resolve12({ ok: true, value: void 0 });
26674
- }, delayMs);
26675
- signal?.addEventListener("abort", onAbort, { once: true });
26676
- });
26677
- }
26678
- async function renderMockResponse(options) {
26679
- const { mock, fileRoot, method, signal } = options;
26680
- const waited = await waitForDelay(mock.delayMs, signal);
26681
- if (!waited.ok) return waited;
26682
- let fullBytes;
26683
- if (mock.body !== void 0) {
26684
- fullBytes = new TextEncoder().encode(mock.body);
26685
- } else if (mock.file !== void 0) {
26686
- if (fileRoot === void 0) return failure("runtime.file-denied");
26687
- if (!isConfinedFileSupported()) {
26688
- return failure("runtime.platform-unsupported");
26689
- }
26690
- const read2 = await readMockFile(fileRoot, mock.file, signal);
26691
- if (!read2.ok) return read2;
26692
- fullBytes = read2.value;
26693
- } else {
26694
- return failure("runtime.file-denied");
26695
- }
26696
- const headers = [];
26697
- let hasContentType = false;
26698
- let hasCacheControl = false;
26699
- for (const header of mock.headers ?? []) {
26700
- const name = headerName(header.name);
26701
- if (name === "content-length") continue;
26702
- if (name === "content-type") hasContentType = true;
26703
- if (name === "cache-control") hasCacheControl = true;
26704
- headers.push([header.name, header.value]);
26705
- }
26706
- if (!hasContentType) {
26707
- headers.push(["Content-Type", "application/octet-stream"]);
26708
- }
26709
- headers.push(["Content-Length", String(fullBytes.byteLength)]);
26710
- if (!hasCacheControl) headers.push(["Cache-Control", "no-store"]);
26711
- return {
26712
- ok: true,
26713
- value: {
26714
- status: mock.status,
26715
- headers,
26716
- bodyBytes: method === "HEAD" ? new Uint8Array() : fullBytes
26717
- }
26718
- };
26719
- }
26720
26770
  var MAX_MOCK_FILE_ERRORS = 64;
26721
26771
  var FILE_ERROR_CODES = /* @__PURE__ */ new Set([
26722
26772
  "runtime.file-denied",
@@ -28171,7 +28221,10 @@ function createNativeHost(options) {
28171
28221
  signal: request.signal
28172
28222
  })
28173
28223
  });
28174
- return interceptProxy.endpoint;
28224
+ return {
28225
+ ...interceptProxy.endpoint,
28226
+ responseBodyToken: interceptProxy.responseBodyToken
28227
+ };
28175
28228
  },
28176
28229
  async stopTlsProxy() {
28177
28230
  if (interceptProxy) {
@@ -29792,7 +29845,7 @@ function createServer3(handler, options = {}) {
29792
29845
  }
29793
29846
 
29794
29847
  // packages/cli/src/server/routes.ts
29795
- import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
29848
+ import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
29796
29849
  import { readFile as readFile4 } from "node:fs/promises";
29797
29850
  import { dirname as dirname9, isAbsolute as isAbsolute5, resolve as resolve5 } from "node:path";
29798
29851
 
@@ -30261,7 +30314,7 @@ function tokensEqual(provided, expected) {
30261
30314
  const providedBytes = Buffer.from(provided);
30262
30315
  const expectedBytes = Buffer.from(expected);
30263
30316
  if (providedBytes.length !== expectedBytes.length) return false;
30264
- return timingSafeEqual2(providedBytes, expectedBytes);
30317
+ return timingSafeEqual3(providedBytes, expectedBytes);
30265
30318
  }
30266
30319
  function scopedPathname(pathname, sessionToken) {
30267
30320
  const marker = "/e/";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogatio/cli",
3
- "version": "6.21.0",
3
+ "version": "6.21.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local-first browser request and response rules — editor host, file verification, test runner, and runtime dispatch.",