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.
- package/README.md +48 -0
- package/config/audit-redaction.json +48 -0
- package/dist/auth/api-key-store.d.ts +28 -0
- package/dist/auth/api-key-store.d.ts.map +1 -0
- package/dist/auth/api-key-store.js +489 -0
- package/dist/auth/api-key-store.js.map +1 -0
- package/dist/auth/api-key-types.d.ts +29 -0
- package/dist/auth/api-key-types.d.ts.map +1 -0
- package/dist/auth/api-key-types.js +5 -0
- package/dist/auth/api-key-types.js.map +1 -0
- package/dist/auth/jwt-verifier.d.ts +13 -0
- package/dist/auth/jwt-verifier.d.ts.map +1 -0
- package/dist/auth/jwt-verifier.js +107 -0
- package/dist/auth/jwt-verifier.js.map +1 -0
- package/dist/auth/scope-policy.d.ts +8 -0
- package/dist/auth/scope-policy.d.ts.map +1 -0
- package/dist/auth/scope-policy.js +140 -0
- package/dist/auth/scope-policy.js.map +1 -0
- package/dist/cdp/client.d.ts +18 -3
- package/dist/cdp/client.d.ts.map +1 -1
- package/dist/cdp/client.js +113 -52
- package/dist/cdp/client.js.map +1 -1
- package/dist/cdp/errors.d.ts +23 -0
- package/dist/cdp/errors.d.ts.map +1 -0
- package/dist/cdp/errors.js +36 -0
- package/dist/cdp/errors.js.map +1 -0
- package/dist/chrome/launcher.d.ts +31 -0
- package/dist/chrome/launcher.d.ts.map +1 -1
- package/dist/chrome/launcher.js +101 -11
- package/dist/chrome/launcher.js.map +1 -1
- package/dist/cli/admin-keys.d.ts +56 -0
- package/dist/cli/admin-keys.js +234 -0
- package/dist/cli/admin-keys.js.map +1 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/defaults.d.ts +24 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +27 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/tool-tiers.js +1 -1
- package/dist/config/tool-tiers.js.map +1 -1
- package/dist/errors/abort.d.ts +10 -0
- package/dist/errors/abort.d.ts.map +1 -0
- package/dist/errors/abort.js +21 -0
- package/dist/errors/abort.js.map +1 -0
- package/dist/index.js +28 -7
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts +64 -5
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +376 -36
- package/dist/mcp-server.js.map +1 -1
- package/dist/metrics/collector.d.ts +27 -0
- package/dist/metrics/collector.d.ts.map +1 -1
- package/dist/metrics/collector.js +54 -1
- package/dist/metrics/collector.js.map +1 -1
- package/dist/middleware/auth.d.ts +46 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +171 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/tenant-extractor.d.ts +52 -0
- package/dist/middleware/tenant-extractor.d.ts.map +1 -0
- package/dist/middleware/tenant-extractor.js +93 -0
- package/dist/middleware/tenant-extractor.js.map +1 -0
- package/dist/observability/logger.d.ts +6 -0
- package/dist/observability/logger.d.ts.map +1 -0
- package/dist/observability/logger.js +34 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/observability/redaction.d.ts +37 -0
- package/dist/observability/redaction.d.ts.map +1 -0
- package/dist/observability/redaction.js +338 -0
- package/dist/observability/redaction.js.map +1 -0
- package/dist/observability/request-id.d.ts +41 -0
- package/dist/observability/request-id.d.ts.map +1 -0
- package/dist/observability/request-id.js +121 -0
- package/dist/observability/request-id.js.map +1 -0
- package/dist/security/audit-logger.d.ts +28 -1
- package/dist/security/audit-logger.d.ts.map +1 -1
- package/dist/security/audit-logger.js +163 -35
- package/dist/security/audit-logger.js.map +1 -1
- package/dist/session-manager.d.ts +47 -5
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +79 -14
- package/dist/session-manager.js.map +1 -1
- package/dist/tenant/manager.d.ts +90 -0
- package/dist/tenant/manager.d.ts.map +1 -0
- package/dist/tenant/manager.js +253 -0
- package/dist/tenant/manager.js.map +1 -0
- package/dist/tenant/registry.d.ts +30 -0
- package/dist/tenant/registry.d.ts.map +1 -0
- package/dist/tenant/registry.js +71 -0
- package/dist/tenant/registry.js.map +1 -0
- package/dist/tenant/types.d.ts +30 -0
- package/dist/tenant/types.d.ts.map +1 -0
- package/dist/tenant/types.js +13 -0
- package/dist/tenant/types.js.map +1 -0
- package/dist/tools/fill-form.d.ts.map +1 -1
- package/dist/tools/fill-form.js +5 -0
- package/dist/tools/fill-form.js.map +1 -1
- package/dist/tools/interact.d.ts.map +1 -1
- package/dist/tools/interact.js +3 -0
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/javascript.d.ts.map +1 -1
- package/dist/tools/javascript.js +11 -16
- package/dist/tools/javascript.js.map +1 -1
- package/dist/tools/navigate.d.ts.map +1 -1
- package/dist/tools/navigate.js +1 -0
- package/dist/tools/navigate.js.map +1 -1
- package/dist/tools/read-page.d.ts.map +1 -1
- package/dist/tools/read-page.js +8 -6
- package/dist/tools/read-page.js.map +1 -1
- package/dist/transports/http.d.ts +66 -2
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +355 -33
- package/dist/transports/http.js.map +1 -1
- package/dist/transports/index.d.ts +15 -1
- package/dist/transports/index.d.ts.map +1 -1
- package/dist/transports/index.js +1 -1
- package/dist/transports/index.js.map +1 -1
- package/dist/transports/stdio.d.ts +1 -1
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js.map +1 -1
- package/dist/types/mcp.d.ts +10 -0
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/types/mcp.js +14 -0
- package/dist/types/mcp.js.map +1 -1
- package/dist/types/session.d.ts +14 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/utils/budget.d.ts +54 -0
- package/dist/utils/budget.d.ts.map +1 -0
- package/dist/utils/budget.js +84 -0
- package/dist/utils/budget.js.map +1 -0
- package/dist/utils/cdp-abort.d.ts +19 -0
- package/dist/utils/cdp-abort.d.ts.map +1 -0
- package/dist/utils/cdp-abort.js +39 -0
- package/dist/utils/cdp-abort.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +17 -6
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +24 -9
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/safe-listener.d.ts +36 -0
- package/dist/utils/safe-listener.d.ts.map +1 -0
- package/dist/utils/safe-listener.js +74 -0
- package/dist/utils/safe-listener.js.map +1 -0
- package/dist/utils/with-timeout.d.ts +8 -3
- package/dist/utils/with-timeout.d.ts.map +1 -1
- package/dist/utils/with-timeout.js +25 -4
- package/dist/utils/with-timeout.js.map +1 -1
- 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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"}
|