@rogatio/cli 6.20.1 → 6.20.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
@@ -45,7 +45,7 @@ rogatio <command> [options]
45
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. |
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
- `rogatio edit` includes **Run checks**. It calls `POST /api/doctor` (CSRF-protected) 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`.
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`.
49
49
 
50
50
  Global options: `--help, -h` and `--version, -v`. Run `rogatio <command> --help`
51
51
  for command-specific usage.
@@ -7,8 +7,6 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
7
7
 
8
8
  // packages/runtime/dist/node/index.js
9
9
  import { isIP } from "node:net";
10
- import * as fs from "node:fs/promises";
11
- import * as path from "node:path";
12
10
 
13
11
  // packages/schema/dist/node/index.js
14
12
  import { Ajv2020 } from "ajv/dist/2020.js";
@@ -2260,6 +2258,8 @@ function sameOrigin(a, b) {
2260
2258
  }
2261
2259
 
2262
2260
  // packages/runtime/dist/node/index.js
2261
+ import * as fs from "node:fs/promises";
2262
+ import * as path from "node:path";
2263
2263
  import { isIP as isIP2 } from "node:net";
2264
2264
  import { constants, existsSync } from "node:fs";
2265
2265
  import { open, readlink, realpath } from "node:fs/promises";
@@ -21299,67 +21299,23 @@ function createAIClient(config) {
21299
21299
  }
21300
21300
  };
21301
21301
  }
21302
- var RESOURCE_TYPES2 = [
21303
- "main_frame",
21304
- "sub_frame",
21305
- "stylesheet",
21306
- "script",
21307
- "image",
21308
- "font",
21309
- "object",
21310
- "xmlhttprequest",
21311
- "fetch",
21312
- "media",
21313
- "websocket",
21314
- "manifest",
21315
- "other"
21316
- ];
21317
- var HTTP_METHODS2 = [
21318
- "GET",
21319
- "POST",
21320
- "PUT",
21321
- "PATCH",
21322
- "DELETE",
21323
- "HEAD",
21324
- "OPTIONS",
21325
- "CONNECT",
21326
- "TRACE"
21327
- ];
21328
- var FORBIDDEN_REQUEST_HEADERS2 = [
21329
- "accept-charset",
21330
- "accept-encoding",
21331
- "access-control-request-headers",
21332
- "access-control-request-method",
21333
- "connection",
21334
- "content-length",
21335
- "cookie",
21336
- "cookie2",
21337
- "date",
21338
- "dnt",
21339
- "expect",
21340
- "host",
21341
- "keep-alive",
21342
- "origin",
21343
- "referer",
21344
- "te",
21345
- "trailer",
21346
- "transfer-encoding",
21347
- "upgrade",
21348
- "via"
21349
- ];
21350
- var FORBIDDEN_RESPONSE_HEADERS2 = ["set-cookie", "set-cookie2"];
21302
+ var MAX_PROJECTS_PER_PROFILE = 64;
21351
21303
  function formatResourceTypes() {
21352
- return RESOURCE_TYPES2.join(", ");
21304
+ return [...RESOURCE_TYPES].join(", ");
21353
21305
  }
21354
21306
  function formatHttpMethods() {
21355
- return HTTP_METHODS2.join(", ");
21307
+ return [...HTTP_METHODS].join(", ");
21356
21308
  }
21357
21309
  function formatForbiddenHeaders() {
21358
21310
  return [
21359
- ...FORBIDDEN_REQUEST_HEADERS2.map((h) => `request: ${h}`),
21360
- ...FORBIDDEN_RESPONSE_HEADERS2.map((h) => `response: ${h}`)
21311
+ ...FORBIDDEN_REQUEST_HEADERS.map((header) => `request: ${header}`),
21312
+ ...FORBIDDEN_REQUEST_PREFIXES.map((prefix) => `request prefix: ${prefix}`),
21313
+ ...FORBIDDEN_RESPONSE_HEADERS.map((header) => `response: ${header}`)
21361
21314
  ].join(", ");
21362
21315
  }
21316
+ function formatDiagnostics() {
21317
+ return Object.entries(MESSAGES).map(([code, message]) => `- ${code} - ${message}`).join("\n");
21318
+ }
21363
21319
  function formatProjectContext(project) {
21364
21320
  try {
21365
21321
  return JSON.stringify(project, null, 2);
@@ -21392,9 +21348,10 @@ ${formatResourceTypes()}
21392
21348
  ${formatHttpMethods()}
21393
21349
 
21394
21350
  ### Bounds
21395
- - Max 64 projects per profile
21396
- - Max 100 groups per project
21397
- - Max 500 rules per project
21351
+ - Max ${MAX_PROJECTS_PER_PROFILE} projects per profile
21352
+ - Max ${LIMITS.maxGroups} groups per project
21353
+ - Max ${LIMITS.maxRulesPerGroup} rules per group
21354
+ - Max ${LIMITS.maxRulesPerProject} rules per project
21398
21355
 
21399
21356
  ## Rule Types
21400
21357
 
@@ -21416,22 +21373,7 @@ Replace-mode body may use URL captures. Regex-mode replacement strings use captu
21416
21373
  Replace-mode body may use URL captures. Regex-mode replacement strings use captures from the body pattern, not the source pattern.
21417
21374
 
21418
21375
  ## Compiler Diagnostic Codes (stable)
21419
- - schema.required - Required project data is missing
21420
- - schema.unknown-property - The project contains an unknown property
21421
- - schema.invalid-type - A value has an invalid type
21422
- - schema.invalid-format - A value has an invalid format
21423
- - schema.invalid-value - A value is invalid
21424
- - schema.out-of-range - A value is outside its allowed bounds
21425
- - schema.invalid-structure - A value has invalid structure
21426
- - schema.duplicate-id - Duplicate group or rule ID
21427
- - schema.duplicate-name - Duplicate group or rule name; every group and rule name must be unique in the project
21428
- - schema.rule-limit - The project contains too many rules
21429
- - compiler.invariant - The compiler could not normalize the project
21430
- - compiler.forbidden-header - The header is forbidden for this direction
21431
- - compiler.header-value-required - A header value is required for set and append
21432
- - compiler.header-value-unexpected - A header value is not allowed for remove
21433
- - compiler.invalid-header-direction - Invalid header direction
21434
- - compiler.invalid-header-operation - Invalid header operation
21376
+ ${formatDiagnostics()}
21435
21377
 
21436
21378
  ## Current Project
21437
21379
  ${projectJson}
@@ -29678,7 +29620,7 @@ function createServer3(handler, options = {}) {
29678
29620
  }
29679
29621
 
29680
29622
  // packages/cli/src/server/routes.ts
29681
- import { randomBytes as randomBytes4 } from "node:crypto";
29623
+ import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
29682
29624
  import { readFile as readFile5 } from "node:fs/promises";
29683
29625
  import { dirname as dirname9, isAbsolute as isAbsolute5, resolve as resolve5 } from "node:path";
29684
29626
 
@@ -30134,17 +30076,44 @@ function isAllowedEditorHost(host, boundPort) {
30134
30076
  return host === `127.0.0.1:${expected}` || host === `localhost:${expected}`;
30135
30077
  }
30136
30078
  function isAllowedEditorOrigin(origin, boundPort) {
30137
- if (origin === void 0) return true;
30138
- if (!Number.isInteger(boundPort) || boundPort <= 0) return false;
30079
+ if (origin === void 0 || !Number.isInteger(boundPort) || boundPort <= 0) {
30080
+ return false;
30081
+ }
30139
30082
  const expected = String(boundPort);
30140
30083
  return origin === `http://127.0.0.1:${expected}` || origin === `http://localhost:${expected}`;
30141
30084
  }
30142
30085
  function generateCsrfToken() {
30143
30086
  return randomBytes4(16).toString("hex");
30144
30087
  }
30088
+ function tokensEqual(provided, expected) {
30089
+ const providedBytes = Buffer.from(provided);
30090
+ const expectedBytes = Buffer.from(expected);
30091
+ if (providedBytes.length !== expectedBytes.length) return false;
30092
+ return timingSafeEqual2(providedBytes, expectedBytes);
30093
+ }
30094
+ function scopedPathname(pathname, sessionToken) {
30095
+ const marker = "/e/";
30096
+ if (!pathname.startsWith(marker)) return void 0;
30097
+ const after = pathname.slice(marker.length);
30098
+ const slash = after.indexOf("/");
30099
+ const candidate = slash === -1 ? after : after.slice(0, slash);
30100
+ if (candidate.length === 0 || !tokensEqual(candidate, sessionToken)) {
30101
+ return void 0;
30102
+ }
30103
+ return slash === -1 ? "/" : after.slice(slash);
30104
+ }
30105
+ function editorSessionPath(token, suffix) {
30106
+ const path2 = suffix.startsWith("/") ? suffix : `/${suffix}`;
30107
+ return `/e/${token}${path2}`;
30108
+ }
30145
30109
  function validateCsrf(req, expectedToken) {
30146
- const token = req.headers["x-csrf-token"];
30147
- return token === expectedToken;
30110
+ const token = headerValue3(req.headers["x-csrf-token"]);
30111
+ if (token === void 0) return false;
30112
+ return tokensEqual(token, expectedToken);
30113
+ }
30114
+ function emptyNotFound(res) {
30115
+ res.writeHead(404);
30116
+ res.end();
30148
30117
  }
30149
30118
  var MAX_REQUEST_BODY_BYTES = 8 * 1024 * 1024;
30150
30119
  var RequestBodyError = class extends Error {
@@ -30249,8 +30218,13 @@ function createRoutes(context) {
30249
30218
  );
30250
30219
  return;
30251
30220
  }
30252
- const { pathname } = parseUrl(req);
30221
+ const { pathname: requestPath } = parseUrl(req);
30253
30222
  const method = req.method || "GET";
30223
+ const pathname = scopedPathname(requestPath, context.csrfToken);
30224
+ if (pathname === void 0) {
30225
+ emptyNotFound(res);
30226
+ return;
30227
+ }
30254
30228
  if (method === "POST") {
30255
30229
  const origin = headerValue3(req.headers.origin);
30256
30230
  if (!isAllowedEditorOrigin(origin, context.boundPort)) {
@@ -30271,7 +30245,8 @@ function createRoutes(context) {
30271
30245
  }
30272
30246
  if (pathname === "/editor.html" && method === "GET") {
30273
30247
  res.writeHead(200, {
30274
- "Content-Type": "text/html; charset=utf-8"
30248
+ "Content-Type": "text/html; charset=utf-8",
30249
+ "Referrer-Policy": "no-referrer"
30275
30250
  });
30276
30251
  res.end(context.editorHtml);
30277
30252
  return;
@@ -31016,18 +30991,13 @@ async function editCommand(args, options = {}) {
31016
30991
  }
31017
30992
  context.boundPort = server.port;
31018
30993
  const serverUrl = `http://127.0.0.1:${server.port}`;
31019
- const editorUrl = `${serverUrl}/editor.html`;
30994
+ const editorUrl = `${serverUrl}${editorSessionPath(csrfToken, "/editor.html")}`;
31020
30995
  const {
31021
30996
  bundle: editorBundlePath,
31022
30997
  css: editorCssPath,
31023
30998
  fonts: editorFontsPath
31024
30999
  } = editorAssetPaths();
31025
- context.editorHtml = generateEditorHtml(
31026
- serverUrl,
31027
- csrfToken,
31028
- filePath,
31029
- !!aiClient
31030
- );
31000
+ context.editorHtml = generateEditorHtml(filePath, !!aiClient);
31031
31001
  context.editorBundlePath = editorBundlePath;
31032
31002
  context.editorCssPath = editorCssPath;
31033
31003
  context.editorFontsPath = editorFontsPath;
@@ -31092,7 +31062,7 @@ async function ensureCivilizationProjectName(storage, filePath, data) {
31092
31062
  await storage.update(filePath, named);
31093
31063
  return named;
31094
31064
  }
31095
- function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
31065
+ function generateEditorHtml(filePath, aiConfigured) {
31096
31066
  const aiAssistHandler = aiConfigured ? `
31097
31067
  aiAssist: async function(request) {
31098
31068
  const res = await fetch(apiBase + '/api/ai/assist', {
@@ -31121,7 +31091,7 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
31121
31091
  <meta charset="UTF-8">
31122
31092
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
31123
31093
  <title>Rogatio Editor</title>
31124
- <link rel="stylesheet" href="/vendor/editor.css" />
31094
+ <link rel="stylesheet" href="vendor/editor.css" />
31125
31095
  <style>
31126
31096
  html, body { margin: 0; min-height: 100%; }
31127
31097
  body {
@@ -31149,14 +31119,16 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
31149
31119
  <div id="editor-doctor" hidden></div>
31150
31120
  <div id="editor-root"></div>
31151
31121
  <script type="importmap">
31152
- { "imports": { "@rogatio/editor": "/vendor/editor.js" } }
31122
+ { "imports": { "@rogatio/editor": "./vendor/editor.js" } }
31153
31123
  </script>
31154
31124
  <script type="module">
31155
31125
  import { createEditor } from '@rogatio/editor';
31156
31126
 
31157
31127
  const root = document.getElementById('editor-root');
31158
- const apiBase = ${JSON.stringify(apiBase)};
31159
- const csrfToken = ${JSON.stringify(csrfToken)};
31128
+ const apiBase = location.pathname.endsWith('/editor.html')
31129
+ ? location.pathname.slice(0, -'/editor.html'.length)
31130
+ : location.pathname;
31131
+ const csrfToken = apiBase.slice(apiBase.lastIndexOf('/') + 1);
31160
31132
  const filePath = ${embedJson(filePath)};
31161
31133
  const doctorTitles = ${embedJson(editorPageTitles())};
31162
31134
  const serverStoppedJson = ${embedJson(serializeEditorDiagnostics(editorServerStoppedReport(filePath)))};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogatio/cli",
3
- "version": "6.20.1",
3
+ "version": "6.20.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.",