crosscheck-cli 0.0.59 → 0.0.61
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/dist/cli.js +39 -42
- package/dist/cli.js.map +1 -1
- package/dist/engine-version.js +34 -4
- package/dist/engine-version.js.map +1 -1
- package/dist/http.js +1 -0
- package/dist/http.js.map +1 -1
- package/dist/key-status-signing.js +26 -0
- package/dist/key-status-signing.js.map +1 -0
- package/dist/key-status.js +118 -0
- package/dist/key-status.js.map +1 -0
- package/dist/key-verify.js +130 -0
- package/dist/key-verify.js.map +1 -0
- package/dist/keys-browser-flow.js +13 -126
- package/dist/keys-browser-flow.js.map +1 -1
- package/dist/keys-manage-cli.js +44 -0
- package/dist/keys-manage-cli.js.map +1 -0
- package/dist/keys-session.js +314 -0
- package/dist/keys-session.js.map +1 -0
- package/dist/reconcile.js +43 -1
- package/dist/reconcile.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -9,9 +9,10 @@ import { activate } from "./activate.js";
|
|
|
9
9
|
import { runDoctor } from "./doctor.js";
|
|
10
10
|
import { runHeartbeat } from "./heartbeat.js";
|
|
11
11
|
import { deleteSecret, fileStorePath, getSecret, listSecrets, ONEPASSWORD_LIST_PLACEHOLDER, secretBackend, setSecret, setSecretBackend, } from "./keychain.js";
|
|
12
|
-
import { captureKeyViaBrowser
|
|
12
|
+
import { captureKeyViaBrowser } from "./keys-browser-flow.js";
|
|
13
13
|
import { resolveProvider, validateKey, formatWarning, CANONICAL_PROVIDERS } from "./key-format.js";
|
|
14
14
|
import { runKeysSetup } from "./keys-setup.js";
|
|
15
|
+
import { runKeysManage } from "./keys-manage-cli.js";
|
|
15
16
|
import { runOnePasswordKeysSetup } from "./onepassword/setup.js";
|
|
16
17
|
import { runInstall } from "./install.js";
|
|
17
18
|
import { startMcpServer } from "./mcp.js";
|
|
@@ -27,8 +28,12 @@ Usage:
|
|
|
27
28
|
crosscheck login Alias for \`crosscheck activate\`.
|
|
28
29
|
crosscheck deactivate Wipe local credentials.
|
|
29
30
|
crosscheck heartbeat Check status against the server.
|
|
30
|
-
crosscheck keys
|
|
31
|
-
provider
|
|
31
|
+
crosscheck keys [--no-open] Open a live key session in your browser: see every
|
|
32
|
+
provider, add/replace/remove a key, and verify one
|
|
33
|
+
against the provider (free — no tokens). Keys go
|
|
34
|
+
browser → 127.0.0.1 only, never to Crosscheck.
|
|
35
|
+
Ends after 10 minutes idle. \`keys manage\` is the
|
|
36
|
+
same command spelled out.
|
|
32
37
|
crosscheck keys setup Interactive wizard for all provider keys.
|
|
33
38
|
crosscheck keys setup --backend 1password Browser wizard: pick which providers read from
|
|
34
39
|
1Password, validate, and switch on (enterprise,
|
|
@@ -64,6 +69,13 @@ Usage:
|
|
|
64
69
|
set; per-panel selection is browser-only for now.
|
|
65
70
|
crosscheck doctor Diagnose a broken install (Node, secret store,
|
|
66
71
|
engine, activation, keys).
|
|
72
|
+
|
|
73
|
+
Which of your machines has which key: crosscheckagent.com/account/providers.
|
|
74
|
+
Each machine reports the providers it has a key for — presence, the last 4
|
|
75
|
+
characters, and how the key last behaved. Never the key itself; the endpoint
|
|
76
|
+
physically cannot accept one. Set CROSSCHECK_NO_KEY_STATUS=1 to report
|
|
77
|
+
nothing at all. Keys are always fixed ON the machine that has them, with
|
|
78
|
+
\`crosscheck keys setup\` — there is no remote key management, by design.
|
|
67
79
|
crosscheck explain Show what your work has cost, per branch,
|
|
68
80
|
resolved locally (the server only sees hashes).
|
|
69
81
|
crosscheck help Show this help.
|
|
@@ -153,11 +165,21 @@ async function main() {
|
|
|
153
165
|
return;
|
|
154
166
|
}
|
|
155
167
|
case "keys": {
|
|
156
|
-
|
|
157
|
-
|
|
168
|
+
// Read positionals past the flags. `crosscheck keys --no-open` has no
|
|
169
|
+
// subcommand at all, and treating the flag as one sent it to the usage
|
|
170
|
+
// error instead of opening a session.
|
|
171
|
+
const positional = rest.filter((a) => !a.startsWith("-"));
|
|
172
|
+
const sub = positional[0];
|
|
173
|
+
const provider = positional[1];
|
|
158
174
|
// --paste falls back to the legacy stdin paste flow (useful in
|
|
159
175
|
// CI/headless/SSH environments where the browser flow can't work).
|
|
160
176
|
const paste = rest.includes("--paste");
|
|
177
|
+
if (sub === "manage") {
|
|
178
|
+
// --no-open for headless boxes and CI: print the link, don't try to
|
|
179
|
+
// launch a browser that isn't there.
|
|
180
|
+
await runKeysManage({ open: !rest.includes("--no-open") });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
161
183
|
if (sub === "setup") {
|
|
162
184
|
const backendFlagIdx = rest.indexOf("--backend");
|
|
163
185
|
const backendArg = backendFlagIdx >= 0 ? rest[backendFlagIdx + 1] : undefined;
|
|
@@ -174,43 +196,18 @@ async function main() {
|
|
|
174
196
|
await runKeysSetup();
|
|
175
197
|
return;
|
|
176
198
|
}
|
|
177
|
-
// Bare `crosscheck keys`
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
199
|
+
// Bare `crosscheck keys` → the live management session, the same thing
|
|
200
|
+
// `keys manage` opens.
|
|
201
|
+
//
|
|
202
|
+
// This used to open a DIFFERENT page: a one-shot form that collected a
|
|
203
|
+
// batch of changes, POSTed once, and exited. Two browser flows that look
|
|
204
|
+
// alike but behave differently is a worse problem than either of them
|
|
205
|
+
// solves — you could not tell from the terminal which one you were about
|
|
206
|
+
// to get, and only one of them let you verify a key or see a change land.
|
|
207
|
+
// The one-shot page is still served for CLIs already in the wild; it just
|
|
208
|
+
// isn't what this command opens any more.
|
|
181
209
|
if (!sub) {
|
|
182
|
-
|
|
183
|
-
.filter((i) => i.account.startsWith("key:"))
|
|
184
|
-
.map((i) => i.account.slice(4));
|
|
185
|
-
let changes;
|
|
186
|
-
try {
|
|
187
|
-
changes = await captureKeyChangesViaBrowser({ setProviders });
|
|
188
|
-
}
|
|
189
|
-
catch (err) {
|
|
190
|
-
// eslint-disable-next-line no-console
|
|
191
|
-
console.error(`✗ ${err instanceof Error ? err.message : err}\n You can also manage keys from the terminal: \`crosscheck keys add|remove <provider>\`.`);
|
|
192
|
-
process.exit(1);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
let saved = 0;
|
|
196
|
-
let removed = 0;
|
|
197
|
-
for (const [p, value] of Object.entries(changes.set)) {
|
|
198
|
-
const warning = formatWarning(p, value);
|
|
199
|
-
if (warning) {
|
|
200
|
-
// eslint-disable-next-line no-console
|
|
201
|
-
console.warn(`⚠ ${p}: ${warning}`);
|
|
202
|
-
}
|
|
203
|
-
await setSecret(`key:${p}`, value);
|
|
204
|
-
saved += 1;
|
|
205
|
-
}
|
|
206
|
-
for (const p of changes.remove) {
|
|
207
|
-
if (await deleteSecret(`key:${p}`))
|
|
208
|
-
removed += 1;
|
|
209
|
-
}
|
|
210
|
-
// eslint-disable-next-line no-console
|
|
211
|
-
console.log(saved === 0 && removed === 0
|
|
212
|
-
? "✓ No changes — your keys are unchanged."
|
|
213
|
-
: `✓ Updated keys locally on this machine: ${saved} saved, ${removed} removed (never sent to Crosscheck).`);
|
|
210
|
+
await runKeysManage({ open: !rest.includes("--no-open") });
|
|
214
211
|
return;
|
|
215
212
|
}
|
|
216
213
|
if (sub === "list") {
|
|
@@ -222,7 +219,7 @@ async function main() {
|
|
|
222
219
|
return;
|
|
223
220
|
}
|
|
224
221
|
if (sub === "store") {
|
|
225
|
-
const target =
|
|
222
|
+
const target = positional[1];
|
|
226
223
|
if (target !== "file" && target !== "keychain") {
|
|
227
224
|
// eslint-disable-next-line no-console
|
|
228
225
|
console.error(`usage: crosscheck keys store (file|keychain)\n current: ${secretBackend()}`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,aAAa,EACb,SAAS,EACT,WAAW,EACX,4BAA4B,EAC5B,aAAa,EACb,SAAS,EACT,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/engine-version.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve the crosscheck-mcp engine version actually
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Resolve the crosscheck-mcp engine version this process is actually RUNNING
|
|
3
|
+
* — for diagnostics (heartbeat reporting) only, never for launch decisions
|
|
4
|
+
* (mcp.ts's resolveEngineLauncher is the source of truth there).
|
|
5
|
+
*
|
|
6
|
+
* MEMOIZED, AND THAT IS THE POINT. `serve` is a long-lived process that
|
|
7
|
+
* loaded the engine once at startup, but the file it loaded from is an npx
|
|
8
|
+
* cache directory that a later `npx crosscheck-cli@latest` overwrites in
|
|
9
|
+
* place. Re-reading package.json on every heartbeat therefore reported the
|
|
10
|
+
* version on DISK while the process kept executing the code it had already
|
|
11
|
+
* loaded — so a server running a five-day-old engine cheerfully reported
|
|
12
|
+
* itself as current.
|
|
13
|
+
*
|
|
14
|
+
* That is not a cosmetic reporting bug. It is why a GPT-6 Astra failure went
|
|
15
|
+
* unexplained for days: the dashboard said the engine was new enough to have
|
|
16
|
+
* the fix, the process was not, and nothing anywhere could see the gap.
|
|
17
|
+
* Reading once, at startup, means the reported version is the version whose
|
|
18
|
+
* code is running.
|
|
5
19
|
*
|
|
6
20
|
* Can't `require.resolve(`${ENGINE_PACKAGE}/package.json`)` directly — the
|
|
7
21
|
* engine's "exports" map only exposes ".", so that subpath throws on Node
|
|
@@ -9,14 +23,30 @@
|
|
|
9
23
|
* walk up two directories to the package root.
|
|
10
24
|
*/
|
|
11
25
|
import { createRequire } from "node:module";
|
|
26
|
+
import fs from "node:fs";
|
|
12
27
|
import path from "node:path";
|
|
13
28
|
import { ENGINE_FALLBACK_VERSION, ENGINE_PACKAGE } from "./config.js";
|
|
14
29
|
const require = createRequire(import.meta.url);
|
|
30
|
+
let cached = null;
|
|
15
31
|
export function resolveEngineVersion() {
|
|
32
|
+
if (cached !== null)
|
|
33
|
+
return cached;
|
|
34
|
+
cached = readEngineVersion();
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
/** Test seam only — lets a test observe that the value is pinned. */
|
|
38
|
+
export function __resetEngineVersionCache() {
|
|
39
|
+
cached = null;
|
|
40
|
+
}
|
|
41
|
+
function readEngineVersion() {
|
|
16
42
|
try {
|
|
17
43
|
const entry = require.resolve(ENGINE_PACKAGE);
|
|
18
44
|
const pkgPath = path.join(path.dirname(entry), "..", "package.json");
|
|
19
|
-
|
|
45
|
+
// fs.readFileSync, not require(): require() keeps its own module cache, so
|
|
46
|
+
// the freshness of this read would depend on a cache we don't control and
|
|
47
|
+
// can't reason about. The memo above is the only caching here, which is
|
|
48
|
+
// the whole point — one place, with a documented reason.
|
|
49
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
20
50
|
if (typeof pkg.version === "string" && pkg.version)
|
|
21
51
|
return pkg.version;
|
|
22
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../src/engine-version.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../src/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,MAAM,UAAU,oBAAoB;IAClC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACnC,MAAM,GAAG,iBAAiB,EAAE,CAAC;IAC7B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,yBAAyB;IACvC,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,iBAAiB;IACxB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACrE,2EAA2E;QAC3E,0EAA0E;QAC1E,wEAAwE;QACxE,yDAAyD;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAyB,CAAC;QACjF,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,4CAA4C;IAC9C,CAAC;IACD,OAAO,uBAAuB,CAAC;AACjC,CAAC"}
|
package/dist/http.js
CHANGED
|
@@ -17,6 +17,7 @@ export async function request(url, opts = {}) {
|
|
|
17
17
|
headers: opts.headers,
|
|
18
18
|
body: opts.body,
|
|
19
19
|
redirect: "follow", // handles the apex→www 308 transparently
|
|
20
|
+
signal: opts.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined,
|
|
20
21
|
});
|
|
21
22
|
return {
|
|
22
23
|
statusCode: res.status,
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAW,EAAE,OAAoB,EAAE;IAC/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;QAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,QAAQ,EAAE,yCAAyC;QAC7D,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;KACzE,CAAC,CAAC;IACH,OAAO;QACL,UAAU,EAAE,GAAG,CAAC,MAAM;QACtB,IAAI,EAAE;YACJ,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE;YACtB,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE;SACvB;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical signing form for key status reports.
|
|
3
|
+
*
|
|
4
|
+
* MUST stay byte-identical to src/lib/crosscheck/key-status-crypto.ts in
|
|
5
|
+
* crosscheck-site. Drift fails verification server-side, and a rejected
|
|
6
|
+
* report is indistinguishable in the UI from "this machine has no keys" —
|
|
7
|
+
* the most misleading failure this feature could have.
|
|
8
|
+
*
|
|
9
|
+
* Providers are sorted before joining: a report is a set, not a sequence, and
|
|
10
|
+
* signing it as a sequence would make verification depend on the order the
|
|
11
|
+
* key store happened to enumerate in.
|
|
12
|
+
*/
|
|
13
|
+
export function canonicalKeyStatusBody(r) {
|
|
14
|
+
const rows = [...r.providers]
|
|
15
|
+
.sort((a, b) => a.provider.localeCompare(b.provider))
|
|
16
|
+
.map((p) => [
|
|
17
|
+
p.provider,
|
|
18
|
+
p.present ? "1" : "0",
|
|
19
|
+
p.suffix ?? "",
|
|
20
|
+
p.lastResult ?? "",
|
|
21
|
+
p.lastCheckedAt ?? "",
|
|
22
|
+
].join(","))
|
|
23
|
+
.join(";");
|
|
24
|
+
return [r.seatId, r.orgId, r.reportedAt, rows].join("|");
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=key-status-signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-status-signing.js","sourceRoot":"","sources":["../src/key-status-signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,MAAM,UAAU,sBAAsB,CAAC,CAKtC;IACC,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC;SAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACT;QACE,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;QACrB,CAAC,CAAC,MAAM,IAAI,EAAE;QACd,CAAC,CAAC,UAAU,IAAI,EAAE;QAClB,CAAC,CAAC,aAAa,IAAI,EAAE;KACtB,CAAC,IAAI,CAAC,GAAG,CAAC,CACZ;SACA,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report which providers this machine has keys for — and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS: keys are per-machine and never leave the machine, which is
|
|
5
|
+
* the right security posture but leaves the browser blind. Open /account on
|
|
6
|
+
* your laptop and it cannot tell you that your desktop lost its OpenAI key
|
|
7
|
+
* two days ago, because it has no idea the desktop ever had one. Every
|
|
8
|
+
* machine but the one you are sitting at is a black hole. This closes that
|
|
9
|
+
* with metadata, without weakening the posture at all.
|
|
10
|
+
*
|
|
11
|
+
* WHAT LEAVES THIS MACHINE, exhaustively:
|
|
12
|
+
* provider name · a key is present yes/no · the last 4 characters ·
|
|
13
|
+
* how it last behaved · when that was
|
|
14
|
+
*
|
|
15
|
+
* The last-4 is the only part derived from the key itself, and it exists for
|
|
16
|
+
* one reason: so a human can tell whether the key on the dashboard is the one
|
|
17
|
+
* they think it is before replacing it. `suffix4()` is the ONLY place in this
|
|
18
|
+
* file that touches key material, it is four characters wide by construction,
|
|
19
|
+
* and the server's schema independently rejects anything longer. Two
|
|
20
|
+
* independent bounds, because a single one is a single edit away from gone.
|
|
21
|
+
*
|
|
22
|
+
* Reporting is opt-OUT via CROSSCHECK_NO_KEY_STATUS=1. An org that considers
|
|
23
|
+
* provider coverage sensitive can turn it off and lose only the dashboard.
|
|
24
|
+
*/
|
|
25
|
+
import { getSecret } from "./keychain.js";
|
|
26
|
+
import { CANONICAL_PROVIDERS } from "./key-format.js";
|
|
27
|
+
import { SERVER_URL } from "./config.js";
|
|
28
|
+
import { request } from "./http.js";
|
|
29
|
+
import { canonicalKeyStatusBody } from "./key-status-signing.js";
|
|
30
|
+
import crypto from "node:crypto";
|
|
31
|
+
/**
|
|
32
|
+
* The last four characters, or nothing.
|
|
33
|
+
*
|
|
34
|
+
* Short keys return undefined rather than a padded or partial value: a key
|
|
35
|
+
* under 8 characters is not a real key, and reporting a suffix of a 5-char
|
|
36
|
+
* string would expose most of it. The threshold is deliberately well above 4.
|
|
37
|
+
*/
|
|
38
|
+
export function suffix4(key) {
|
|
39
|
+
const k = (key ?? "").trim();
|
|
40
|
+
if (k.length < 8)
|
|
41
|
+
return undefined;
|
|
42
|
+
return k.slice(-4);
|
|
43
|
+
}
|
|
44
|
+
export function keyStatusDisabled(env = process.env) {
|
|
45
|
+
const v = (env.CROSSCHECK_NO_KEY_STATUS ?? "").trim().toLowerCase();
|
|
46
|
+
return v === "1" || v === "true" || v === "yes";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the local store and describe it. Absent providers are reported
|
|
50
|
+
* explicitly as `present: false` rather than omitted, because the server
|
|
51
|
+
* treats a report as a full replacement of the seat's provider set and
|
|
52
|
+
* "missing from the list" has to mean "no longer configured".
|
|
53
|
+
*/
|
|
54
|
+
export async function collectKeyStatus() {
|
|
55
|
+
const items = [];
|
|
56
|
+
// Every canonical provider, not just the six-provider default panel:
|
|
57
|
+
// `keys add` accepts all nine, and a provider omitted here is invisible on
|
|
58
|
+
// the dashboard no matter how much it's used.
|
|
59
|
+
for (const key of CANONICAL_PROVIDERS) {
|
|
60
|
+
let secret = null;
|
|
61
|
+
try {
|
|
62
|
+
secret = await getSecret(`key:${key}`);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// A store we can't read (locked keychain, 1Password not signed in) is
|
|
66
|
+
// NOT the same as a key that isn't there. Skip the provider entirely
|
|
67
|
+
// rather than reporting a false "no key" that the dashboard would show
|
|
68
|
+
// as a problem the user doesn't have.
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!secret) {
|
|
72
|
+
items.push({ provider: key, present: false });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
items.push({ provider: key, present: true, suffix: suffix4(secret) });
|
|
76
|
+
}
|
|
77
|
+
return items;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Send the report. Best-effort and silent: this runs on the MCP startup path,
|
|
81
|
+
* where a network problem must cost a stale dashboard row and never a failed
|
|
82
|
+
* connection.
|
|
83
|
+
*/
|
|
84
|
+
export async function reportKeyStatus(args) {
|
|
85
|
+
if (keyStatusDisabled())
|
|
86
|
+
return "skipped";
|
|
87
|
+
const hmacKey = await getSecret("hmacSigningKey");
|
|
88
|
+
if (!hmacKey)
|
|
89
|
+
return "skipped";
|
|
90
|
+
const providers = await collectKeyStatus();
|
|
91
|
+
if (providers.length === 0)
|
|
92
|
+
return "skipped";
|
|
93
|
+
const body = {
|
|
94
|
+
seatId: args.seatId,
|
|
95
|
+
orgId: args.orgId,
|
|
96
|
+
reportedAt: new Date().toISOString(),
|
|
97
|
+
providers,
|
|
98
|
+
};
|
|
99
|
+
const signatureHmac = crypto
|
|
100
|
+
.createHmac("sha256", hmacKey)
|
|
101
|
+
.update(canonicalKeyStatusBody(body))
|
|
102
|
+
.digest("hex");
|
|
103
|
+
try {
|
|
104
|
+
const res = await request(`${args.serverUrl ?? SERVER_URL}/api/crosscheck/key-status`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${args.jwt}` },
|
|
107
|
+
body: JSON.stringify({ ...body, signatureHmac }),
|
|
108
|
+
timeoutMs: args.timeoutMs ?? 2_000,
|
|
109
|
+
});
|
|
110
|
+
// Drain so the socket can be reused; we don't care about the payload.
|
|
111
|
+
await res.body.text().catch(() => "");
|
|
112
|
+
return res.statusCode === 200 ? "sent" : "failed";
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return "failed";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=key-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-status.js","sourceRoot":"","sources":["../src/key-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Does this key actually work?" — answered by asking the provider, cheaply.
|
|
3
|
+
*
|
|
4
|
+
* Every probe here is a model-list GET: no completion, no tokens, no cost.
|
|
5
|
+
* That matters, because a verify button that quietly bills you is a verify
|
|
6
|
+
* button people learn not to press.
|
|
7
|
+
*
|
|
8
|
+
* THE DISTINCTION THIS FILE EXISTS FOR: "we could not reach the provider" and
|
|
9
|
+
* "the provider rejected your key" are completely different problems with
|
|
10
|
+
* completely different fixes, and they arrive looking similar. Collapsing
|
|
11
|
+
* them sends someone to regenerate a key because their wifi dropped. So the
|
|
12
|
+
* default for anything ambiguous is `network` or `unknown` — never
|
|
13
|
+
* `invalid_key`. We accuse a key only when a provider actually refused it.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The cheapest authenticated endpoint each provider offers. Chosen over a
|
|
17
|
+
* 1-token completion because a list call is free, fast, and still exercises
|
|
18
|
+
* the exact credential path a real call uses.
|
|
19
|
+
*/
|
|
20
|
+
const PROBES = {
|
|
21
|
+
anthropic: {
|
|
22
|
+
url: "https://api.anthropic.com/v1/models?limit=1",
|
|
23
|
+
headers: (k) => ({ "x-api-key": k, "anthropic-version": "2023-06-01" }),
|
|
24
|
+
},
|
|
25
|
+
openai: {
|
|
26
|
+
url: "https://api.openai.com/v1/models",
|
|
27
|
+
headers: (k) => ({ authorization: `Bearer ${k}` }),
|
|
28
|
+
},
|
|
29
|
+
gemini: {
|
|
30
|
+
// Google takes the key as a query parameter, but putting it in the URL
|
|
31
|
+
// would leak it into any error message that echoes the URL — so it goes
|
|
32
|
+
// in the header form Google also accepts.
|
|
33
|
+
url: "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1",
|
|
34
|
+
headers: (k) => ({ "x-goog-api-key": k }),
|
|
35
|
+
},
|
|
36
|
+
grok: {
|
|
37
|
+
url: "https://api.x.ai/v1/models",
|
|
38
|
+
headers: (k) => ({ authorization: `Bearer ${k}` }),
|
|
39
|
+
},
|
|
40
|
+
kimi: {
|
|
41
|
+
url: "https://api.moonshot.ai/v1/models",
|
|
42
|
+
headers: (k) => ({ authorization: `Bearer ${k}` }),
|
|
43
|
+
},
|
|
44
|
+
qwen: {
|
|
45
|
+
url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models",
|
|
46
|
+
headers: (k) => ({ authorization: `Bearer ${k}` }),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export function canVerify(provider) {
|
|
50
|
+
return provider in PROBES;
|
|
51
|
+
}
|
|
52
|
+
/** Same credit/billing vocabulary the engine's http-errors.ts matches on.
|
|
53
|
+
* Kept in sync deliberately: the two must agree on what "out of credit"
|
|
54
|
+
* looks like, or the dashboard and the engine will disagree about the same
|
|
55
|
+
* key. */
|
|
56
|
+
const CREDIT_RE = /insufficient|no\s+credit|credits?|billing|quota|payment\s*required|exceeded your current quota|not\s+enough|fund|no active subscription|spend limit/i;
|
|
57
|
+
export function classifyStatus(status, body) {
|
|
58
|
+
if (status >= 200 && status < 300) {
|
|
59
|
+
return { result: "ok", detail: "The provider accepted this key." };
|
|
60
|
+
}
|
|
61
|
+
// 402 is unambiguous. On the auth-shaped codes we look at the body, because
|
|
62
|
+
// several providers return 401/403 for an unpaid account with a perfectly
|
|
63
|
+
// valid key — xAI most notoriously.
|
|
64
|
+
if (status === 402 || ((status === 401 || status === 403 || status === 429) && CREDIT_RE.test(body))) {
|
|
65
|
+
return {
|
|
66
|
+
result: "quota_billing",
|
|
67
|
+
detail: "The key is valid, but the account is out of credit or over a limit.",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (status === 429) {
|
|
71
|
+
return {
|
|
72
|
+
result: "quota_billing",
|
|
73
|
+
detail: "Rate limited. The key works; you're calling faster than the account allows.",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (status === 401 || status === 403) {
|
|
77
|
+
return { result: "invalid_key", detail: "The provider rejected this key." };
|
|
78
|
+
}
|
|
79
|
+
if (status >= 500) {
|
|
80
|
+
return {
|
|
81
|
+
result: "provider_down",
|
|
82
|
+
detail: `The provider returned a server error (HTTP ${status}). Nothing wrong on your side.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
result: "unknown",
|
|
87
|
+
detail: `Unexpected response (HTTP ${status}). This doesn't tell us the key is bad.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Probe one provider. `fetchImpl` is injectable so the classification can be
|
|
92
|
+
* tested without touching the network.
|
|
93
|
+
*/
|
|
94
|
+
export async function verifyKey(provider, key, opts = {}) {
|
|
95
|
+
const probe = PROBES[provider];
|
|
96
|
+
if (!probe) {
|
|
97
|
+
return {
|
|
98
|
+
result: "unknown",
|
|
99
|
+
detail: `No verification endpoint is configured for ${provider}.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
103
|
+
try {
|
|
104
|
+
const res = await doFetch(probe.url, {
|
|
105
|
+
method: "GET",
|
|
106
|
+
headers: probe.headers(key),
|
|
107
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 10_000),
|
|
108
|
+
});
|
|
109
|
+
// Cap the body read: an error page can be enormous, and we only need
|
|
110
|
+
// enough to tell a billing message from an auth message.
|
|
111
|
+
let body = "";
|
|
112
|
+
try {
|
|
113
|
+
body = (await res.text()).slice(0, 2048);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* a body we can't read doesn't change the status code's meaning */
|
|
117
|
+
}
|
|
118
|
+
return classifyStatus(res.status, body);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
// A timeout, a DNS failure, a proxy refusing the connection. None of
|
|
122
|
+
// these is evidence about the key, and saying so is the whole point.
|
|
123
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
124
|
+
return {
|
|
125
|
+
result: "network",
|
|
126
|
+
detail: `Couldn't reach ${provider} from this machine (${msg}). This is not a bad key.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=key-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-verify.js","sourceRoot":"","sources":["../src/key-verify.ts"],"names":[],"mappings":"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"}
|