openchrome-mcp 1.9.7 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +48 -0
  2. package/config/audit-redaction.json +48 -0
  3. package/dist/auth/api-key-store.d.ts +28 -0
  4. package/dist/auth/api-key-store.d.ts.map +1 -0
  5. package/dist/auth/api-key-store.js +489 -0
  6. package/dist/auth/api-key-store.js.map +1 -0
  7. package/dist/auth/api-key-types.d.ts +29 -0
  8. package/dist/auth/api-key-types.d.ts.map +1 -0
  9. package/dist/auth/api-key-types.js +5 -0
  10. package/dist/auth/api-key-types.js.map +1 -0
  11. package/dist/auth/jwt-verifier.d.ts +13 -0
  12. package/dist/auth/jwt-verifier.d.ts.map +1 -0
  13. package/dist/auth/jwt-verifier.js +107 -0
  14. package/dist/auth/jwt-verifier.js.map +1 -0
  15. package/dist/auth/scope-policy.d.ts +8 -0
  16. package/dist/auth/scope-policy.d.ts.map +1 -0
  17. package/dist/auth/scope-policy.js +140 -0
  18. package/dist/auth/scope-policy.js.map +1 -0
  19. package/dist/cdp/client.d.ts +18 -3
  20. package/dist/cdp/client.d.ts.map +1 -1
  21. package/dist/cdp/client.js +113 -52
  22. package/dist/cdp/client.js.map +1 -1
  23. package/dist/cdp/errors.d.ts +23 -0
  24. package/dist/cdp/errors.d.ts.map +1 -0
  25. package/dist/cdp/errors.js +36 -0
  26. package/dist/cdp/errors.js.map +1 -0
  27. package/dist/chrome/launcher.d.ts +31 -0
  28. package/dist/chrome/launcher.d.ts.map +1 -1
  29. package/dist/chrome/launcher.js +101 -11
  30. package/dist/chrome/launcher.js.map +1 -1
  31. package/dist/cli/admin-keys.d.ts +56 -0
  32. package/dist/cli/admin-keys.js +234 -0
  33. package/dist/cli/admin-keys.js.map +1 -0
  34. package/dist/cli/index.js +3 -0
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/config/defaults.d.ts +24 -0
  37. package/dist/config/defaults.d.ts.map +1 -1
  38. package/dist/config/defaults.js +27 -2
  39. package/dist/config/defaults.js.map +1 -1
  40. package/dist/config/tool-tiers.js +1 -1
  41. package/dist/config/tool-tiers.js.map +1 -1
  42. package/dist/errors/abort.d.ts +10 -0
  43. package/dist/errors/abort.d.ts.map +1 -0
  44. package/dist/errors/abort.js +21 -0
  45. package/dist/errors/abort.js.map +1 -0
  46. package/dist/index.js +28 -7
  47. package/dist/index.js.map +1 -1
  48. package/dist/mcp-server.d.ts +64 -5
  49. package/dist/mcp-server.d.ts.map +1 -1
  50. package/dist/mcp-server.js +376 -36
  51. package/dist/mcp-server.js.map +1 -1
  52. package/dist/metrics/collector.d.ts +27 -0
  53. package/dist/metrics/collector.d.ts.map +1 -1
  54. package/dist/metrics/collector.js +54 -1
  55. package/dist/metrics/collector.js.map +1 -1
  56. package/dist/middleware/auth.d.ts +46 -0
  57. package/dist/middleware/auth.d.ts.map +1 -0
  58. package/dist/middleware/auth.js +171 -0
  59. package/dist/middleware/auth.js.map +1 -0
  60. package/dist/middleware/tenant-extractor.d.ts +52 -0
  61. package/dist/middleware/tenant-extractor.d.ts.map +1 -0
  62. package/dist/middleware/tenant-extractor.js +93 -0
  63. package/dist/middleware/tenant-extractor.js.map +1 -0
  64. package/dist/observability/logger.d.ts +6 -0
  65. package/dist/observability/logger.d.ts.map +1 -0
  66. package/dist/observability/logger.js +34 -0
  67. package/dist/observability/logger.js.map +1 -0
  68. package/dist/observability/redaction.d.ts +37 -0
  69. package/dist/observability/redaction.d.ts.map +1 -0
  70. package/dist/observability/redaction.js +338 -0
  71. package/dist/observability/redaction.js.map +1 -0
  72. package/dist/observability/request-id.d.ts +41 -0
  73. package/dist/observability/request-id.d.ts.map +1 -0
  74. package/dist/observability/request-id.js +121 -0
  75. package/dist/observability/request-id.js.map +1 -0
  76. package/dist/security/audit-logger.d.ts +28 -1
  77. package/dist/security/audit-logger.d.ts.map +1 -1
  78. package/dist/security/audit-logger.js +163 -35
  79. package/dist/security/audit-logger.js.map +1 -1
  80. package/dist/session-manager.d.ts +47 -5
  81. package/dist/session-manager.d.ts.map +1 -1
  82. package/dist/session-manager.js +79 -14
  83. package/dist/session-manager.js.map +1 -1
  84. package/dist/tenant/manager.d.ts +90 -0
  85. package/dist/tenant/manager.d.ts.map +1 -0
  86. package/dist/tenant/manager.js +253 -0
  87. package/dist/tenant/manager.js.map +1 -0
  88. package/dist/tenant/registry.d.ts +30 -0
  89. package/dist/tenant/registry.d.ts.map +1 -0
  90. package/dist/tenant/registry.js +71 -0
  91. package/dist/tenant/registry.js.map +1 -0
  92. package/dist/tenant/types.d.ts +30 -0
  93. package/dist/tenant/types.d.ts.map +1 -0
  94. package/dist/tenant/types.js +13 -0
  95. package/dist/tenant/types.js.map +1 -0
  96. package/dist/tools/fill-form.d.ts.map +1 -1
  97. package/dist/tools/fill-form.js +5 -0
  98. package/dist/tools/fill-form.js.map +1 -1
  99. package/dist/tools/interact.d.ts.map +1 -1
  100. package/dist/tools/interact.js +3 -0
  101. package/dist/tools/interact.js.map +1 -1
  102. package/dist/tools/javascript.d.ts.map +1 -1
  103. package/dist/tools/javascript.js +11 -16
  104. package/dist/tools/javascript.js.map +1 -1
  105. package/dist/tools/navigate.d.ts.map +1 -1
  106. package/dist/tools/navigate.js +1 -0
  107. package/dist/tools/navigate.js.map +1 -1
  108. package/dist/tools/read-page.d.ts.map +1 -1
  109. package/dist/tools/read-page.js +8 -6
  110. package/dist/tools/read-page.js.map +1 -1
  111. package/dist/transports/http.d.ts +66 -2
  112. package/dist/transports/http.d.ts.map +1 -1
  113. package/dist/transports/http.js +355 -33
  114. package/dist/transports/http.js.map +1 -1
  115. package/dist/transports/index.d.ts +15 -1
  116. package/dist/transports/index.d.ts.map +1 -1
  117. package/dist/transports/index.js +1 -1
  118. package/dist/transports/index.js.map +1 -1
  119. package/dist/transports/stdio.d.ts +1 -1
  120. package/dist/transports/stdio.d.ts.map +1 -1
  121. package/dist/transports/stdio.js.map +1 -1
  122. package/dist/types/mcp.d.ts +10 -0
  123. package/dist/types/mcp.d.ts.map +1 -1
  124. package/dist/types/mcp.js +14 -0
  125. package/dist/types/mcp.js.map +1 -1
  126. package/dist/types/session.d.ts +14 -0
  127. package/dist/types/session.d.ts.map +1 -1
  128. package/dist/utils/budget.d.ts +54 -0
  129. package/dist/utils/budget.d.ts.map +1 -0
  130. package/dist/utils/budget.js +84 -0
  131. package/dist/utils/budget.js.map +1 -0
  132. package/dist/utils/cdp-abort.d.ts +19 -0
  133. package/dist/utils/cdp-abort.d.ts.map +1 -0
  134. package/dist/utils/cdp-abort.js +39 -0
  135. package/dist/utils/cdp-abort.js.map +1 -0
  136. package/dist/utils/rate-limiter.d.ts +17 -6
  137. package/dist/utils/rate-limiter.d.ts.map +1 -1
  138. package/dist/utils/rate-limiter.js +24 -9
  139. package/dist/utils/rate-limiter.js.map +1 -1
  140. package/dist/utils/safe-listener.d.ts +36 -0
  141. package/dist/utils/safe-listener.d.ts.map +1 -0
  142. package/dist/utils/safe-listener.js +74 -0
  143. package/dist/utils/safe-listener.js.map +1 -0
  144. package/dist/utils/with-timeout.d.ts +8 -3
  145. package/dist/utils/with-timeout.d.ts.map +1 -1
  146. package/dist/utils/with-timeout.js +25 -4
  147. package/dist/utils/with-timeout.js.map +1 -1
  148. package/package.json +4 -1
package/README.md CHANGED
@@ -409,6 +409,48 @@ Tier 3 launches a real headed Chrome window with a genuine user-agent (`Chrome/.
409
409
 
410
410
  ---
411
411
 
412
+ ## FAQ: Comparison with Other Browser MCPs
413
+
414
+ Common questions from users evaluating OpenChrome against Chrome DevTools MCP, Firefox DevTools MCP, and similar tools (see [#612](https://github.com/shaun0927/openchrome/issues/612)).
415
+
416
+ ### Can multiple MCP clients share tabs safely?
417
+
418
+ **Yes — tabs cannot clobber each other across clients.**
419
+
420
+ OpenChrome identifies every tab by its CDP `targetId` — a stable, browser-assigned string — not by a visible 1/2/3 index. On top of stable IDs, two layers of isolation are specifically designed for multi-client scenarios:
421
+
422
+ - **`workerId`** — logical tab groups per client or parallel lane. A new tab under one `workerId` is invisible to another and never replaces one.
423
+ - **`profileDirectory`** — launches a fully separate Chrome instance bound to that profile, giving OS-level cookie / storage / extension isolation.
424
+
425
+ If client A opens five tabs and client B opens five tabs, all ten `tabId`s are distinct and stable; a new tab from A can never displace B's tab #3.
426
+
427
+ ### How do I handle sites that require interactive login (password, 2FA, CAPTCHA)?
428
+
429
+ Two mechanisms, used together in practice:
430
+
431
+ **1. Persistent-profile headless — log in once, automate forever.**
432
+ Point OpenChrome at a persistent `userDataDir` (+ optional `profileDirectory`) and cookies / `localStorage` / IndexedDB survive across runs. After the first login, subsequent **headless** runs stay logged in until the site invalidates the session.
433
+
434
+ **2. Headed fallback for the initial login or WAF-blocked sites.**
435
+ When a human action is genuinely required (first-time login, 2FA, CAPTCHA, WebAuthn) or when a Tier-1/Tier-2 headless attempt is blocked by a CDN/WAF, OpenChrome lazy-launches a separate headed Chrome on a different debug port. Cookies are sync'd from the real Chrome profile before launch, and the headed page is registered back into the same logical session so the surrounding workflow continues seamlessly.
436
+
437
+ **Typical pattern:** bootstrap the login once via the headed path → the persistent profile carries the cookies → all subsequent automation runs headless without a window.
438
+
439
+ ### Does OpenChrome steal focus with popup windows?
440
+
441
+ **No — the "recurring popup interruptions" problem does not occur in OpenChrome.**
442
+
443
+ The headed-browser focus-stealing pattern that users encounter with some MCP servers (cross-Space jumps on macOS, un-minimizable popups, per-tool-call window raises) comes from designs where the MCP drives a user-visible browser and creates OS windows as it works. OpenChrome is architected differently:
444
+
445
+ - **Default mode is headless** — no window exists, so there is nothing to take focus.
446
+ - **`tabs_create` opens a tab, not an OS window.** New tabs are created via CDP inside the already-running Chrome, and OpenChrome never calls `page.bringToFront()` anywhere in the codebase.
447
+ - **The headed fallback is lazy and reused.** When it is needed, it launches **once** per server lifetime; every later navigation/tab runs inside that same instance. At worst you see one focus grab the very first time the fallback activates — not one per action, never one per tab.
448
+ - **The headed fallback is optional.** If persistent-profile headless is sufficient for your sites, you will never see a browser window.
449
+
450
+ The only scenario in which a window appears at all is the first time the Tier-3 headed fallback activates in a given session.
451
+
452
+ ---
453
+
412
454
  ## Benchmarks
413
455
 
414
456
  Measure token efficiency and parallel performance:
@@ -514,6 +556,12 @@ openchrome serve \
514
556
 
515
557
  ---
516
558
 
559
+ ## Authentication
560
+
561
+ OpenChrome supports per-tenant API keys, JWT/OAuth, a legacy shared token, and an unauthenticated mode. See **[docs/auth.md](docs/auth.md)** for the full guide including quickstart, scope table, key rotation, revocation, and troubleshooting.
562
+
563
+ ---
564
+
517
565
  ## Under the Hood: 8-Layer Reliability
518
566
 
519
567
  OpenChrome has **49 distinct reliability mechanisms** across 8 defense layers — ensuring no single failure can hang the MCP server.
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "./audit-redaction.schema.json",
3
+ "description": "Per-tool redaction rules applied to audit log entries. Each rule identifies a sensitive field by a JSON-path-like key and describes how the value should be recorded: 'redact' replaces with [REDACTED], 'hash' stores sha256:<hex>, 'truncate' keeps the first N bytes and appends a hash of the full value.",
4
+ "defaultSensitiveFieldNames": [
5
+ "password",
6
+ "passwd",
7
+ "pwd",
8
+ "secret",
9
+ "token",
10
+ "authorization",
11
+ "auth",
12
+ "api_key",
13
+ "apikey",
14
+ "access_key",
15
+ "refresh_token",
16
+ "id_token",
17
+ "session_token",
18
+ "cookie",
19
+ "set-cookie",
20
+ "credit_card",
21
+ "ssn",
22
+ "private_key"
23
+ ],
24
+ "tools": {
25
+ "cookies": [
26
+ { "path": "value", "mode": "hash" }
27
+ ],
28
+ "cookies.set": [
29
+ { "path": "value", "mode": "hash" },
30
+ { "path": "cookies[*].value", "mode": "hash" }
31
+ ],
32
+ "fill_form": [
33
+ { "path": "fields[*].value", "mode": "redactIfSensitiveName" }
34
+ ],
35
+ "form_input": [
36
+ { "path": "value", "mode": "redact" }
37
+ ],
38
+ "type": [
39
+ { "path": "text", "mode": "truncate", "maxBytes": 200 }
40
+ ],
41
+ "javascript_tool": [
42
+ { "path": "code", "mode": "truncate", "maxBytes": 200 }
43
+ ],
44
+ "storage": [
45
+ { "path": "value", "mode": "truncate", "maxBytes": 200 }
46
+ ]
47
+ }
48
+ }
@@ -0,0 +1,28 @@
1
+ import type { ApiKey, ApiKeyCreateInput, ApiKeyCreateResult } from './api-key-types';
2
+ declare function defaultStorePath(): string;
3
+ export declare class ApiKeyStore {
4
+ private readonly storePath;
5
+ private readonly lockPath;
6
+ private readonly index;
7
+ private decoyHash;
8
+ private lastReadSize;
9
+ private lastReadIno;
10
+ private pendingSync;
11
+ private constructor();
12
+ static open(storePath?: string): Promise<ApiKeyStore>;
13
+ private applyLines;
14
+ private doSyncFromDisk;
15
+ private syncFromDisk;
16
+ private syncInsideLock;
17
+ private withLock;
18
+ private appendRecord;
19
+ private rewriteAll;
20
+ create(input: ApiKeyCreateInput): Promise<ApiKeyCreateResult>;
21
+ list(): Promise<ApiKey[]>;
22
+ revoke(keyId: string): Promise<boolean>;
23
+ rotate(keyId: string): Promise<ApiKeyCreateResult>;
24
+ verify(plaintext: string): Promise<ApiKey | null>;
25
+ touchLastUsed(keyId: string): Promise<void>;
26
+ }
27
+ export { defaultStorePath };
28
+ //# sourceMappingURL=api-key-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-store.d.ts","sourceRoot":"","sources":["../../src/auth/api-key-store.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,iBAAiB,CAAC;AA+CzB,iBAAS,gBAAgB,IAAI,MAAM,CAElC;AAwBD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IAGxD,OAAO,CAAC,SAAS,CAAc;IAM/B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,WAAW,CAAa;IAGhC,OAAO,CAAC,WAAW,CAA8B;IAEjD,OAAO;WAKM,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgD3D,OAAO,CAAC,UAAU;YAuBJ,cAAc;YA2Dd,YAAY;YAmBZ,cAAc;YAmBd,QAAQ;YAmBR,YAAY;YAgCZ,UAAU;IAUlB,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiC7D,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKzB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAavC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAgBlD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiCjD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBlD;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,489 @@
1
+ "use strict";
2
+ // Tenant-scoped API key store.
3
+ // Append-only JSONL log at ~/.openchrome/auth/api-keys.jsonl, replayed into an
4
+ // in-memory Map on load. Hashes with argon2id; plaintext is never persisted and
5
+ // is returned only once from create()/rotate(). See issue #9 / PR1.
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.ApiKeyStore = void 0;
44
+ exports.defaultStorePath = defaultStorePath;
45
+ const crypto = __importStar(require("crypto"));
46
+ const fs = __importStar(require("fs"));
47
+ const os = __importStar(require("os"));
48
+ const path = __importStar(require("path"));
49
+ const write_file_atomic_1 = __importDefault(require("write-file-atomic"));
50
+ const lockfile = __importStar(require("proper-lockfile"));
51
+ const argon2 = __importStar(require("argon2"));
52
+ const KEY_PREFIX = 'oc_live_';
53
+ const RANDOM_BYTES = 24; // ~32 base62 chars
54
+ const ARGON2_OPTS = {
55
+ type: argon2.argon2id,
56
+ memoryCost: 19456, // 19 MiB
57
+ timeCost: 2,
58
+ parallelism: 1,
59
+ };
60
+ // Base62 alphabet: 0-9 A-Z a-z (no padding, URL-safe).
61
+ const BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
62
+ function base62Encode(buf) {
63
+ // Convert bytes -> bigint -> base62 digits, preserving a fixed output length
64
+ // derived from input size so differently sized buffers map to stable widths.
65
+ if (buf.length === 0)
66
+ return '';
67
+ let num = 0n;
68
+ for (const byte of buf) {
69
+ num = (num << 8n) | BigInt(byte);
70
+ }
71
+ let out = '';
72
+ while (num > 0n) {
73
+ const rem = Number(num % 62n);
74
+ out = BASE62_ALPHABET[rem] + out;
75
+ num = num / 62n;
76
+ }
77
+ // Pad with leading zero digits to keep output length deterministic.
78
+ // 1 byte ~ log62(256) ~ 1.344 chars -> ceil(n * 1.344)
79
+ const targetLen = Math.ceil(buf.length * Math.log(256) / Math.log(62));
80
+ if (out.length < targetLen) {
81
+ out = BASE62_ALPHABET[0].repeat(targetLen - out.length) + out;
82
+ }
83
+ return out;
84
+ }
85
+ function computeKeyId(plaintext) {
86
+ const digest = crypto.createHash('sha256').update(plaintext).digest();
87
+ // First 10 chars of base62(sha256(plaintext)). Safe to log.
88
+ return 'k_' + base62Encode(digest).slice(0, 10);
89
+ }
90
+ function defaultStoreDir() {
91
+ return path.join(os.homedir(), '.openchrome', 'auth');
92
+ }
93
+ function defaultStorePath() {
94
+ return path.join(defaultStoreDir(), 'api-keys.jsonl');
95
+ }
96
+ function cloneRecord(r) {
97
+ return {
98
+ keyId: r.keyId,
99
+ keyHash: r.keyHash,
100
+ tenantId: r.tenantId,
101
+ scopes: [...r.scopes],
102
+ createdAt: r.createdAt,
103
+ expiresAt: r.expiresAt,
104
+ revokedAt: r.revokedAt,
105
+ lastUsedAt: r.lastUsedAt,
106
+ description: r.description,
107
+ };
108
+ }
109
+ function isExpired(r, now) {
110
+ return typeof r.expiresAt === 'number' && r.expiresAt < now;
111
+ }
112
+ function isRevoked(r) {
113
+ return typeof r.revokedAt === 'number';
114
+ }
115
+ class ApiKeyStore {
116
+ storePath;
117
+ lockPath;
118
+ index = new Map();
119
+ // Precomputed decoy hash so verify(unknown) performs argon2.verify with
120
+ // indistinguishable timing vs verify(known). Initialised in open().
121
+ decoyHash = '';
122
+ // Byte offset up to which the JSONL has already been replayed into `index`,
123
+ // plus the inode observed at that time. verify()/list()/revoke() call
124
+ // syncFromDisk() to incrementally apply lines appended by peer processes so
125
+ // a long-lived store sees cross-process create/revoke in multi-process
126
+ // deployments (fixes issue #9 PR1 Codex P1 review).
127
+ lastReadSize = 0;
128
+ lastReadIno = 0;
129
+ // Coalesce concurrent syncFromDisk() callers so we don't issue redundant
130
+ // reads when verify() is called in a burst.
131
+ pendingSync = null;
132
+ constructor(storePath) {
133
+ this.storePath = storePath;
134
+ this.lockPath = storePath + '.lock';
135
+ }
136
+ static async open(storePath) {
137
+ const envOverride = process.env.OPENCHROME_API_KEY_STORE_PATH;
138
+ const finalPath = storePath ?? (envOverride && envOverride.length > 0 ? envOverride : defaultStorePath());
139
+ const dir = path.dirname(finalPath);
140
+ // mkdir returns the first path it had to create, or undefined if the
141
+ // directory tree already existed. Only chmod when WE created the dir —
142
+ // otherwise a caller passing a custom path inside a shared parent
143
+ // (e.g. `/tmp/api-keys.jsonl`) would have that parent's permissions
144
+ // silently rewritten (Codex P2 on a9e73c8).
145
+ const dirCreated = await fs.promises.mkdir(dir, {
146
+ recursive: true,
147
+ mode: 0o700,
148
+ });
149
+ if (dirCreated && process.platform !== 'win32') {
150
+ try {
151
+ await fs.promises.chmod(dir, 0o700);
152
+ }
153
+ catch {
154
+ // best-effort
155
+ }
156
+ }
157
+ // Ensure the store file exists with 0600 so proper-lockfile and appends
158
+ // work. Only force-chmod if WE just created the file — don't mutate the
159
+ // mode of a pre-existing caller-owned file.
160
+ let fileCreated = false;
161
+ try {
162
+ await fs.promises.access(finalPath);
163
+ }
164
+ catch {
165
+ await fs.promises.writeFile(finalPath, '', { mode: 0o600 });
166
+ fileCreated = true;
167
+ }
168
+ if (fileCreated && process.platform !== 'win32') {
169
+ try {
170
+ await fs.promises.chmod(finalPath, 0o600);
171
+ }
172
+ catch {
173
+ // best-effort
174
+ }
175
+ }
176
+ const store = new ApiKeyStore(finalPath);
177
+ await store.syncFromDisk();
178
+ // Pre-hash a throwaway value. Using a random secret each open prevents any
179
+ // adversarial timing signal from a fixed decoy across processes.
180
+ const decoySecret = crypto.randomBytes(32).toString('hex');
181
+ store.decoyHash = await argon2.hash(decoySecret, ARGON2_OPTS);
182
+ return store;
183
+ }
184
+ applyLines(text) {
185
+ for (const line of text.split('\n')) {
186
+ if (!line.trim())
187
+ continue;
188
+ let parsed;
189
+ try {
190
+ parsed = JSON.parse(line);
191
+ }
192
+ catch (err) {
193
+ console.error('[api-key-store] skipping malformed JSONL line:', err);
194
+ continue;
195
+ }
196
+ if (!parsed || typeof parsed.keyId !== 'string')
197
+ continue;
198
+ // Latest record wins; revokedAt sticks (do not clear on later records).
199
+ const prior = this.index.get(parsed.keyId);
200
+ if (prior && prior.revokedAt && !parsed.revokedAt) {
201
+ parsed.revokedAt = prior.revokedAt;
202
+ }
203
+ this.index.set(parsed.keyId, parsed);
204
+ }
205
+ }
206
+ // The actual stat+read body. Never touches `pendingSync` — callers own
207
+ // the coalescing decision. Tolerates torn peer appends by consuming only
208
+ // up to the last newline; a partial trailing record is deferred.
209
+ async doSyncFromDisk() {
210
+ let stat;
211
+ try {
212
+ stat = await fs.promises.stat(this.storePath);
213
+ }
214
+ catch {
215
+ // File is gone: drop cached state so we don't keep answering for
216
+ // keys that no longer exist on disk.
217
+ this.index.clear();
218
+ this.lastReadSize = 0;
219
+ this.lastReadIno = 0;
220
+ return;
221
+ }
222
+ // File replaced (rotated) or truncated: re-replay from scratch.
223
+ if ((this.lastReadIno !== 0 && stat.ino !== this.lastReadIno) ||
224
+ stat.size < this.lastReadSize) {
225
+ this.index.clear();
226
+ this.lastReadSize = 0;
227
+ }
228
+ this.lastReadIno = stat.ino;
229
+ if (stat.size === this.lastReadSize)
230
+ return;
231
+ const toRead = stat.size - this.lastReadSize;
232
+ const fd = await fs.promises.open(this.storePath, 'r');
233
+ try {
234
+ const buf = Buffer.allocUnsafe(toRead);
235
+ // fd.read can return fewer bytes than requested if the file was
236
+ // truncated/replaced between stat() and read(). Only decode the
237
+ // prefix that was actually filled — the tail of `allocUnsafe` is
238
+ // uninitialized memory and must never be interpreted as content
239
+ // (Codex P1 on a7e216b).
240
+ const { bytesRead } = await fd.read(buf, 0, toRead, this.lastReadSize);
241
+ if (bytesRead <= 0)
242
+ return;
243
+ if (bytesRead < toRead) {
244
+ // Short read: the file was truncated or replaced between our stat()
245
+ // and read(). Cached state may no longer reflect disk, so drop it;
246
+ // the next syncFromDisk will re-replay from offset 0 (or via the
247
+ // inode-change branch if the file was rotated).
248
+ this.index.clear();
249
+ this.lastReadSize = 0;
250
+ this.lastReadIno = 0;
251
+ return;
252
+ }
253
+ const text = buf.subarray(0, bytesRead).toString('utf8');
254
+ const lastNewline = text.lastIndexOf('\n');
255
+ if (lastNewline < 0)
256
+ return;
257
+ const complete = text.slice(0, lastNewline + 1);
258
+ this.applyLines(complete);
259
+ this.lastReadSize += Buffer.byteLength(complete, 'utf8');
260
+ }
261
+ finally {
262
+ await fd.close();
263
+ }
264
+ }
265
+ // Coalesced sync for unlocked read paths (verify, list, rotate-precheck).
266
+ // Concurrent callers share a single in-flight sync to avoid redundant
267
+ // stat+read under burst traffic. NOT safe for writers inside the cross-
268
+ // process lock — use syncInsideLock() there.
269
+ async syncFromDisk() {
270
+ if (this.pendingSync)
271
+ return this.pendingSync;
272
+ this.pendingSync = (async () => {
273
+ try {
274
+ await this.doSyncFromDisk();
275
+ }
276
+ finally {
277
+ this.pendingSync = null;
278
+ }
279
+ })();
280
+ return this.pendingSync;
281
+ }
282
+ // Writer sync: runs INSIDE the cross-process file lock. Must not reuse
283
+ // an in-flight `pendingSync` because that promise's `stat` may have been
284
+ // taken BEFORE a peer append landed — sharing it would hand the writer a
285
+ // stale EOF, appendRecord would then jump lastReadSize past those peer
286
+ // bytes, and this process would skip them forever. Instead, drain any
287
+ // in-flight sync (to avoid clobbering its lastReadSize mutation) and
288
+ // then run a guaranteed-fresh stat+read ourselves (Codex P1 on 0538a8c).
289
+ async syncInsideLock() {
290
+ while (this.pendingSync) {
291
+ try {
292
+ await this.pendingSync;
293
+ }
294
+ catch {
295
+ // Broken sync: retry until the flag clears (doSyncFromDisk's own
296
+ // errors will re-surface when we run our own fresh sync below).
297
+ }
298
+ }
299
+ this.pendingSync = (async () => {
300
+ try {
301
+ await this.doSyncFromDisk();
302
+ }
303
+ finally {
304
+ this.pendingSync = null;
305
+ }
306
+ })();
307
+ return this.pendingSync;
308
+ }
309
+ async withLock(fn) {
310
+ // proper-lockfile requires the target file to exist.
311
+ try {
312
+ await fs.promises.access(this.storePath);
313
+ }
314
+ catch {
315
+ await fs.promises.writeFile(this.storePath, '', { mode: 0o600 });
316
+ }
317
+ const release = await lockfile.lock(this.storePath, {
318
+ lockfilePath: this.lockPath,
319
+ retries: { retries: 10, minTimeout: 25, maxTimeout: 200 },
320
+ stale: 5000,
321
+ });
322
+ try {
323
+ return await fn();
324
+ }
325
+ finally {
326
+ await release();
327
+ }
328
+ }
329
+ async appendRecord(record) {
330
+ let line = JSON.stringify(record) + '\n';
331
+ try {
332
+ const st = await fs.promises.stat(this.storePath);
333
+ if (st.size > 0) {
334
+ const fh = await fs.promises.open(this.storePath, 'r');
335
+ try {
336
+ const tail = Buffer.alloc(1);
337
+ const { bytesRead } = await fh.read(tail, 0, 1, st.size - 1);
338
+ if (bytesRead === 1 && tail[0] !== 0x0a) {
339
+ line = '\n' + line;
340
+ }
341
+ }
342
+ finally {
343
+ await fh.close();
344
+ }
345
+ }
346
+ }
347
+ catch {
348
+ // Best-effort: if stat/read fails we still append the record and let the
349
+ // next sync reconcile from disk.
350
+ }
351
+ await fs.promises.appendFile(this.storePath, line, { mode: 0o600 });
352
+ // Advance the read cursor so the next syncFromDisk() doesn't re-parse
353
+ // our own append. Best-effort: on stat failure, the next sync reconciles.
354
+ try {
355
+ const st = await fs.promises.stat(this.storePath);
356
+ this.lastReadSize = st.size;
357
+ this.lastReadIno = st.ino;
358
+ }
359
+ catch {
360
+ // fallthrough — next sync will detect the change
361
+ }
362
+ }
363
+ async rewriteAll(records) {
364
+ const data = records.map((r) => JSON.stringify(r)).join('\n') + (records.length ? '\n' : '');
365
+ await new Promise((resolve, reject) => {
366
+ (0, write_file_atomic_1.default)(this.storePath, data, { mode: 0o600 }, (err) => {
367
+ if (err)
368
+ reject(err);
369
+ else
370
+ resolve();
371
+ });
372
+ });
373
+ }
374
+ async create(input) {
375
+ if (!input.tenantId || typeof input.tenantId !== 'string') {
376
+ throw new Error('tenantId is required');
377
+ }
378
+ if (!Array.isArray(input.scopes) || input.scopes.length === 0) {
379
+ throw new Error('at least one scope is required');
380
+ }
381
+ const random = base62Encode(crypto.randomBytes(RANDOM_BYTES)).slice(0, 32);
382
+ const plaintext = `${KEY_PREFIX}${input.tenantId}_${random}`;
383
+ const keyId = computeKeyId(plaintext);
384
+ const keyHash = await argon2.hash(plaintext, ARGON2_OPTS);
385
+ const record = {
386
+ keyId,
387
+ keyHash,
388
+ tenantId: input.tenantId,
389
+ scopes: [...input.scopes],
390
+ createdAt: Date.now(),
391
+ expiresAt: input.expiresAt,
392
+ description: input.description ?? '',
393
+ };
394
+ await this.withLock(async () => {
395
+ // Guaranteed-fresh sync inside the cross-process lock: MUST NOT reuse
396
+ // a pre-lock coalesced pendingSync whose stat may predate a peer
397
+ // append, which would leave this process blind to those bytes after
398
+ // appendRecord advances the cursor to EOF (Codex P1 on commits
399
+ // 64af728 + 0538a8c).
400
+ await this.syncInsideLock();
401
+ await this.appendRecord(record);
402
+ this.index.set(keyId, record);
403
+ });
404
+ return { record: cloneRecord(record), plaintext };
405
+ }
406
+ async list() {
407
+ await this.syncFromDisk();
408
+ return Array.from(this.index.values()).map(cloneRecord);
409
+ }
410
+ async revoke(keyId) {
411
+ return this.withLock(async () => {
412
+ await this.syncInsideLock();
413
+ const existing = this.index.get(keyId);
414
+ if (!existing)
415
+ return false;
416
+ if (existing.revokedAt)
417
+ return true; // idempotent
418
+ const updated = { ...existing, revokedAt: Date.now() };
419
+ await this.appendRecord(updated);
420
+ this.index.set(keyId, updated);
421
+ return true;
422
+ });
423
+ }
424
+ async rotate(keyId) {
425
+ await this.syncFromDisk();
426
+ const prior = this.index.get(keyId);
427
+ if (!prior) {
428
+ throw new Error(`unknown keyId: ${keyId}`);
429
+ }
430
+ // Revoke old (best-effort idempotent) then create new with same tenant+scopes.
431
+ await this.revoke(keyId);
432
+ return this.create({
433
+ tenantId: prior.tenantId,
434
+ scopes: [...prior.scopes],
435
+ description: prior.description,
436
+ expiresAt: prior.expiresAt,
437
+ });
438
+ }
439
+ async verify(plaintext) {
440
+ // Pull in any records appended by peer processes (create/revoke) before
441
+ // we consult the in-memory index. Without this, a long-lived instance
442
+ // would keep honouring revoked keys or reject freshly created ones until
443
+ // process restart.
444
+ await this.syncFromDisk();
445
+ // Constant-ish time: always perform exactly one argon2.verify regardless of
446
+ // miss/hit, against either the real hash or the decoy.
447
+ const now = Date.now();
448
+ let candidateHash = this.decoyHash;
449
+ let candidate = null;
450
+ if (typeof plaintext === 'string' && plaintext.startsWith(KEY_PREFIX)) {
451
+ const keyId = computeKeyId(plaintext);
452
+ const found = this.index.get(keyId) ?? null;
453
+ if (found && !isRevoked(found) && !isExpired(found, now)) {
454
+ candidate = found;
455
+ candidateHash = found.keyHash;
456
+ }
457
+ }
458
+ let ok = false;
459
+ try {
460
+ ok = await argon2.verify(candidateHash, plaintext ?? '');
461
+ }
462
+ catch {
463
+ ok = false;
464
+ }
465
+ if (ok && candidate) {
466
+ return cloneRecord(candidate);
467
+ }
468
+ return null;
469
+ }
470
+ async touchLastUsed(keyId) {
471
+ // Sync INSIDE the lock so a peer revoke that lands between any pre-lock
472
+ // sync and our lock acquisition is observed before we build `merged`.
473
+ // Syncing outside the lock would leave `current.revokedAt` absent, and
474
+ // appending `{ ...current, lastUsedAt }` plus the subsequent cursor
475
+ // advance to EOF would clobber the peer revoke in this instance's view
476
+ // (Codex P1 on commit 64af728).
477
+ await this.withLock(async () => {
478
+ await this.syncInsideLock();
479
+ const current = this.index.get(keyId);
480
+ if (!current)
481
+ return;
482
+ const merged = { ...current, lastUsedAt: Date.now() };
483
+ await this.appendRecord(merged);
484
+ this.index.set(keyId, merged);
485
+ });
486
+ }
487
+ }
488
+ exports.ApiKeyStore = ApiKeyStore;
489
+ //# sourceMappingURL=api-key-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-store.js","sourceRoot":"","sources":["../../src/auth/api-key-store.ts"],"names":[],"mappings":"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"}