crosscheck-cli 0.0.44 → 0.0.46
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 +36 -2
- package/dist/cli.js.map +1 -1
- package/dist/doctor.js +51 -1
- package/dist/doctor.js.map +1 -1
- package/dist/keychain.js +185 -7
- package/dist/keychain.js.map +1 -1
- package/dist/keys-browser-flow.js +1 -1
- package/dist/keys-browser-flow.js.map +1 -1
- package/dist/mcp.js +15 -2
- package/dist/mcp.js.map +1 -1
- package/dist/onepassword/browser-flow.js +162 -0
- package/dist/onepassword/browser-flow.js.map +1 -0
- package/dist/onepassword/config.js +164 -0
- package/dist/onepassword/config.js.map +1 -0
- package/dist/onepassword/op.js +254 -0
- package/dist/onepassword/op.js.map +1 -0
- package/dist/onepassword/reference.js +82 -0
- package/dist/onepassword/reference.js.map +1 -0
- package/dist/onepassword/setup.js +150 -0
- package/dist/onepassword/setup.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `op` CLI runner — the only place this backend ever executes anything.
|
|
3
|
+
* Security checkpoint (see docs/onepassword-security.md): argv construction,
|
|
4
|
+
* environment policy, timeouts/cleanup, output caps, and the error taxonomy
|
|
5
|
+
* all live here.
|
|
6
|
+
*
|
|
7
|
+
* Hard rules:
|
|
8
|
+
* - spawn() only, never a shell. Fixed argv shape, no user-controlled flags.
|
|
9
|
+
* - The reference is re-validated here even though config.ts already
|
|
10
|
+
* validated it on load — defense in depth, not redundancy for its own
|
|
11
|
+
* sake, since this is the last checkpoint before anything reaches argv.
|
|
12
|
+
* - Resolved secret values, tokens, and full op:// references never reach
|
|
13
|
+
* a log, an error message, or telemetry. Errors carry category +
|
|
14
|
+
* remediation + provider-agnostic detail only.
|
|
15
|
+
* - Fail closed: any ambiguity (unparseable version, unmatched stderr,
|
|
16
|
+
* unexpected exit) becomes a generic "unknown" error, never a pass.
|
|
17
|
+
*/
|
|
18
|
+
import { spawn } from "node:child_process";
|
|
19
|
+
import { parseOpReference } from "./reference.js";
|
|
20
|
+
const DEFAULT_OP_BIN = "op";
|
|
21
|
+
// Best-effort minimum — 1Password CLI v2 added desktop-app integration and
|
|
22
|
+
// service-account support well before this version. NEEDS EMPIRICAL
|
|
23
|
+
// VERIFICATION against a real `op` install before relying on it as a hard
|
|
24
|
+
// gate in production (see docs/onepassword-security.md's open items).
|
|
25
|
+
export const MIN_OP_VERSION = "2.18.0";
|
|
26
|
+
const DESKTOP_TIMEOUT_MS = 120_000; // interactive: biometric/hardware-key prompts can't be rushed
|
|
27
|
+
const SERVICE_ACCOUNT_TIMEOUT_MS = 10_000; // non-interactive: a hang means misconfiguration
|
|
28
|
+
const VERSION_CHECK_TIMEOUT_MS = 10_000;
|
|
29
|
+
const GRACE_MS = 2_000; // SIGTERM -> grace -> SIGKILL
|
|
30
|
+
const MAX_OUTPUT_BYTES = 64 * 1024; // generous for a single secret value or short error text
|
|
31
|
+
// Env vars `op` itself recognizes for service-account/Connect auth. Stripped
|
|
32
|
+
// from a desktop-mode child regardless of what the parent process happens to
|
|
33
|
+
// have set — a stray token in someone's shell must never silently switch
|
|
34
|
+
// which identity the child authenticates as.
|
|
35
|
+
const OP_AUTH_ENV_VARS = ["OP_SERVICE_ACCOUNT_TOKEN", "OP_CONNECT_HOST", "OP_CONNECT_TOKEN"];
|
|
36
|
+
function runOpProcess(opBin, args, opts) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
let stdout = "";
|
|
39
|
+
let stderr = "";
|
|
40
|
+
let stdoutBytes = 0;
|
|
41
|
+
let stderrBytes = 0;
|
|
42
|
+
let outputTooLarge = false;
|
|
43
|
+
let timedOut = false;
|
|
44
|
+
let killedForOverflow = false;
|
|
45
|
+
let graceTimer;
|
|
46
|
+
let settled = false;
|
|
47
|
+
const child = spawn(opBin, args, {
|
|
48
|
+
env: opts.env,
|
|
49
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
50
|
+
shell: false,
|
|
51
|
+
});
|
|
52
|
+
const timeoutTimer = setTimeout(() => {
|
|
53
|
+
timedOut = true;
|
|
54
|
+
child.kill("SIGTERM");
|
|
55
|
+
graceTimer = setTimeout(() => {
|
|
56
|
+
try {
|
|
57
|
+
child.kill("SIGKILL");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* already gone */
|
|
61
|
+
}
|
|
62
|
+
}, GRACE_MS);
|
|
63
|
+
}, opts.timeoutMs);
|
|
64
|
+
function checkOverflow() {
|
|
65
|
+
if (outputTooLarge || killedForOverflow)
|
|
66
|
+
return;
|
|
67
|
+
if (stdoutBytes + stderrBytes > MAX_OUTPUT_BYTES) {
|
|
68
|
+
outputTooLarge = true;
|
|
69
|
+
killedForOverflow = true;
|
|
70
|
+
child.kill("SIGTERM");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
child.stdout.on("data", (chunk) => {
|
|
74
|
+
stdoutBytes += chunk.length;
|
|
75
|
+
if (stdoutBytes <= MAX_OUTPUT_BYTES)
|
|
76
|
+
stdout += chunk.toString("utf8");
|
|
77
|
+
checkOverflow();
|
|
78
|
+
});
|
|
79
|
+
child.stderr.on("data", (chunk) => {
|
|
80
|
+
stderrBytes += chunk.length;
|
|
81
|
+
if (stderrBytes <= MAX_OUTPUT_BYTES)
|
|
82
|
+
stderr += chunk.toString("utf8");
|
|
83
|
+
checkOverflow();
|
|
84
|
+
});
|
|
85
|
+
child.on("error", (err) => {
|
|
86
|
+
if (settled)
|
|
87
|
+
return;
|
|
88
|
+
settled = true;
|
|
89
|
+
clearTimeout(timeoutTimer);
|
|
90
|
+
if (graceTimer)
|
|
91
|
+
clearTimeout(graceTimer);
|
|
92
|
+
reject(err);
|
|
93
|
+
});
|
|
94
|
+
child.on("close", (code, signal) => {
|
|
95
|
+
if (settled)
|
|
96
|
+
return;
|
|
97
|
+
settled = true;
|
|
98
|
+
clearTimeout(timeoutTimer);
|
|
99
|
+
if (graceTimer)
|
|
100
|
+
clearTimeout(graceTimer);
|
|
101
|
+
resolve({ code, signal, stdout, stderr, timedOut, outputTooLarge });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function parseVersion(v) {
|
|
106
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
107
|
+
if (!m)
|
|
108
|
+
return null;
|
|
109
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
110
|
+
}
|
|
111
|
+
function isVersionAtLeast(actual, minimum) {
|
|
112
|
+
const a = parseVersion(actual);
|
|
113
|
+
const b = parseVersion(minimum);
|
|
114
|
+
if (!a || !b)
|
|
115
|
+
return false; // fail closed on an unparseable version string
|
|
116
|
+
for (let i = 0; i < 3; i += 1) {
|
|
117
|
+
if (a[i] > b[i])
|
|
118
|
+
return true;
|
|
119
|
+
if (a[i] < b[i])
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true; // equal
|
|
123
|
+
}
|
|
124
|
+
export async function checkOpVersion(opts = {}) {
|
|
125
|
+
const opBin = opts.opBin ?? DEFAULT_OP_BIN;
|
|
126
|
+
let result;
|
|
127
|
+
try {
|
|
128
|
+
result = await runOpProcess(opBin, ["--version"], {
|
|
129
|
+
env: process.env,
|
|
130
|
+
timeoutMs: VERSION_CHECK_TIMEOUT_MS,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
const err = e;
|
|
135
|
+
if (err.code === "ENOENT") {
|
|
136
|
+
return { ok: false, error: "not-installed", detail: `\`${opBin}\` was not found on PATH` };
|
|
137
|
+
}
|
|
138
|
+
return { ok: false, error: "unknown", detail: "could not determine the installed op version" };
|
|
139
|
+
}
|
|
140
|
+
if (result.code !== 0 || result.timedOut) {
|
|
141
|
+
return { ok: false, error: "unknown", detail: "`op --version` did not exit cleanly" };
|
|
142
|
+
}
|
|
143
|
+
const version = result.stdout.trim();
|
|
144
|
+
if (!isVersionAtLeast(version, MIN_OP_VERSION)) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
error: "wrong-version",
|
|
148
|
+
detail: `op ${version || "(unknown)"} is older than the minimum supported ${MIN_OP_VERSION} — upgrade the 1Password CLI`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return { ok: true, version };
|
|
152
|
+
}
|
|
153
|
+
/** Env vars 1Password's desktop-IPC auth needs vary by platform and haven't
|
|
154
|
+
* been empirically verified across all of them (see docs/onepassword-
|
|
155
|
+
* security.md). Until that verification lands, desktop mode uses
|
|
156
|
+
* inherited-env-minus-auth-vars rather than a tighter allowlist — safe
|
|
157
|
+
* (auth vars are still stripped) but not yet as narrow as the target. */
|
|
158
|
+
function buildChildEnv(opts) {
|
|
159
|
+
const env = { ...process.env };
|
|
160
|
+
for (const key of OP_AUTH_ENV_VARS)
|
|
161
|
+
delete env[key];
|
|
162
|
+
if (opts.authMode === "service-account") {
|
|
163
|
+
env["OP_SERVICE_ACCOUNT_TOKEN"] = opts.serviceAccountToken;
|
|
164
|
+
}
|
|
165
|
+
return env;
|
|
166
|
+
}
|
|
167
|
+
function classifyOpFailure(stderr, authMode) {
|
|
168
|
+
const s = stderr.toLowerCase();
|
|
169
|
+
if (s.includes("not currently signed in") || s.includes("not signed in") || s.includes("sign in")) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
error: "not-signed-in",
|
|
173
|
+
detail: authMode === "desktop"
|
|
174
|
+
? "unlock prompt unanswered or desktop app unreachable — if running headless, configure service-account mode"
|
|
175
|
+
: "check that the service-account token is valid and not expired",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (s.includes("access denied") || s.includes("forbidden") || s.includes("permission")) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
error: "access-denied",
|
|
182
|
+
detail: "the authenticated identity does not have access to this item",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (s.includes("could not find") ||
|
|
186
|
+
s.includes("not found") ||
|
|
187
|
+
s.includes("no such") ||
|
|
188
|
+
s.includes("isn't a vault") ||
|
|
189
|
+
s.includes("isn't an item")) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
error: "item-or-field-missing",
|
|
193
|
+
detail: "the referenced vault, item, or field was not found",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// Conservative catch-all: never surface raw stderr — it could echo back
|
|
197
|
+
// parts of the reference, item names, or unrelated system paths.
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
error: "unknown",
|
|
201
|
+
detail: "op exited with an error — run `op read <ref>` manually to see the full message",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Read one secret value from 1Password. Re-validates `refInput` itself
|
|
206
|
+
* (never trusts a caller's prior validation), never falls back to any other
|
|
207
|
+
* source on failure, and never lets a resolved value, token, or reference
|
|
208
|
+
* reach the returned error on the failure path.
|
|
209
|
+
*/
|
|
210
|
+
export async function readOpSecret(refInput, opts) {
|
|
211
|
+
const parsed = parseOpReference(refInput);
|
|
212
|
+
if (!parsed.ok) {
|
|
213
|
+
return { ok: false, error: "malformed-reference", detail: parsed.detail };
|
|
214
|
+
}
|
|
215
|
+
if (opts.authMode === "service-account" && !opts.serviceAccountToken) {
|
|
216
|
+
return { ok: false, error: "token-missing", detail: "service-account mode requires a token" };
|
|
217
|
+
}
|
|
218
|
+
const opBin = opts.opBin ?? DEFAULT_OP_BIN;
|
|
219
|
+
const versionCheck = await checkOpVersion({ opBin });
|
|
220
|
+
if (!versionCheck.ok)
|
|
221
|
+
return versionCheck;
|
|
222
|
+
const env = buildChildEnv(opts);
|
|
223
|
+
const timeoutMs = opts.authMode === "desktop" ? DESKTOP_TIMEOUT_MS : SERVICE_ACCOUNT_TIMEOUT_MS;
|
|
224
|
+
let result;
|
|
225
|
+
try {
|
|
226
|
+
result = await runOpProcess(opBin, ["read", parsed.ref], { env, timeoutMs });
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
const err = e;
|
|
230
|
+
if (err.code === "ENOENT") {
|
|
231
|
+
return { ok: false, error: "not-installed", detail: `\`${opBin}\` was not found on PATH` };
|
|
232
|
+
}
|
|
233
|
+
return { ok: false, error: "unknown", detail: "failed to launch the op process" };
|
|
234
|
+
}
|
|
235
|
+
if (result.outputTooLarge) {
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
error: "output-too-large",
|
|
239
|
+
detail: `op output exceeded ${MAX_OUTPUT_BYTES} bytes`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (result.timedOut) {
|
|
243
|
+
return { ok: false, error: "timeout", detail: `op did not respond within ${timeoutMs}ms` };
|
|
244
|
+
}
|
|
245
|
+
if (result.code !== 0) {
|
|
246
|
+
return classifyOpFailure(result.stderr, opts.authMode);
|
|
247
|
+
}
|
|
248
|
+
const value = result.stdout.replace(/\r?\n$/, "");
|
|
249
|
+
if (value.length === 0) {
|
|
250
|
+
return { ok: false, error: "empty-value", detail: "op returned an empty value" };
|
|
251
|
+
}
|
|
252
|
+
return { ok: true, value };
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=op.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"op.js","sourceRoot":"","sources":["../../src/onepassword/op.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict parser/validator for 1Password secret references (`op://vault/item/field`).
|
|
3
|
+
*
|
|
4
|
+
* v1 supports only the plain 3-segment form — no optional `section` segment.
|
|
5
|
+
* Keeping the grammar minimal keeps this file (and its test surface) small,
|
|
6
|
+
* which matters because it's the first of two defense-in-depth checkpoints
|
|
7
|
+
* against CWE-78: op.ts (PR 2) re-validates immediately before spawning `op`,
|
|
8
|
+
* but rejecting anything that isn't a clean `op://...` string here means a
|
|
9
|
+
* malformed/hostile config value never gets far enough to be a runner
|
|
10
|
+
* concern. Anything not starting with the literal "op://" prefix also can't
|
|
11
|
+
* be mistaken for a flag by `op`'s own argument parser, since it's always
|
|
12
|
+
* passed as a single argv element via execFile — never a shell.
|
|
13
|
+
*/
|
|
14
|
+
const OP_URI_PREFIX = "op://";
|
|
15
|
+
const MAX_REF_LENGTH = 512;
|
|
16
|
+
// Conservative allowlist per segment. Real op:// references are plain
|
|
17
|
+
// vault/item/field names — never shell metacharacters or control
|
|
18
|
+
// characters. Tighten or loosen only against a verified real `op` binary
|
|
19
|
+
// (see PR 2's empirical env/argv matrix), not by inspection alone.
|
|
20
|
+
const SEGMENT_CHARS = /^[A-Za-z0-9 _.'-]+$/;
|
|
21
|
+
export function parseOpReference(input) {
|
|
22
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
23
|
+
return { ok: false, error: "empty", detail: "reference is empty" };
|
|
24
|
+
}
|
|
25
|
+
if (input.length > MAX_REF_LENGTH) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
error: "too-long",
|
|
29
|
+
detail: `reference exceeds ${MAX_REF_LENGTH} characters`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// eslint-disable-next-line no-control-regex
|
|
33
|
+
if (/[\x00-\x1f\x7f]/.test(input)) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
error: "control-characters",
|
|
37
|
+
detail: "reference contains control characters",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (input.includes("?") || input.includes("#")) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: "query-or-fragment",
|
|
44
|
+
detail: "reference must not contain '?' or '#'",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (!input.startsWith(OP_URI_PREFIX)) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: "missing-prefix",
|
|
51
|
+
detail: `reference must start with "${OP_URI_PREFIX}"`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const rest = input.slice(OP_URI_PREFIX.length);
|
|
55
|
+
const segments = rest.split("/");
|
|
56
|
+
if (segments.length !== 3) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: "wrong-segment-count",
|
|
60
|
+
detail: `expected exactly 3 segments (vault/item/field), got ${segments.length}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const [vault, item, field] = segments;
|
|
64
|
+
for (const seg of [vault, item, field]) {
|
|
65
|
+
if (seg.length === 0) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: "empty-segment",
|
|
69
|
+
detail: "vault/item/field segments must not be empty",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (!SEGMENT_CHARS.test(seg)) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: "invalid-characters",
|
|
76
|
+
detail: "segments may only contain letters, digits, spaces, and . _ - '",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, ref: input, vault, item, field };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../src/onepassword/reference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,sEAAsE;AACtE,iEAAiE;AACjE,yEAAyE;AACzE,mEAAmE;AACnE,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAgB5C,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrE,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAClC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,UAAU;YACjB,MAAM,EAAE,qBAAqB,cAAc,aAAa;SACzD,CAAC;IACJ,CAAC;IACD,4CAA4C;IAC5C,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,oBAAoB;YAC3B,MAAM,EAAE,uCAAuC;SAChD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,mBAAmB;YAC1B,MAAM,EAAE,uCAAuC;SAChD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACrC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,gBAAgB;YACvB,MAAM,EAAE,8BAA8B,aAAa,GAAG;SACvD,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,qBAAqB;YAC5B,MAAM,EAAE,uDAAuD,QAAQ,CAAC,MAAM,EAAE;SACjF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,QAAoC,CAAC;IAClE,KAAK,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACvC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,eAAe;gBACtB,MAAM,EAAE,6CAA6C;aACtD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,oBAAoB;gBAC3B,MAAM,EAAE,gEAAgE;aACzE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAoB,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `crosscheck keys setup --backend 1password` — validates the config end to
|
|
3
|
+
* end BEFORE ever persisting the backend preference. Never writes anything
|
|
4
|
+
* to 1Password (the backend is read-only); a "test read" only ever proves
|
|
5
|
+
* connectivity — the value is discarded immediately, never printed, never
|
|
6
|
+
* stored.
|
|
7
|
+
*/
|
|
8
|
+
import { loadOnePasswordConfig, writeOnePasswordConfig, configFilePath } from "./config.js";
|
|
9
|
+
import { checkOpVersion, readOpSecret, MIN_OP_VERSION } from "./op.js";
|
|
10
|
+
import { captureOnePasswordConfigViaBrowser } from "./browser-flow.js";
|
|
11
|
+
import { enableOnePasswordBackend } from "../keychain.js";
|
|
12
|
+
import { SUPPORTED_PROVIDERS } from "../keys-browser-flow.js";
|
|
13
|
+
/**
|
|
14
|
+
* Validate config → `op` version → test-read every mapped provider.
|
|
15
|
+
* Discloses provider NAMES only, never a full op:// reference — matching
|
|
16
|
+
* the same disclosure discipline as op.ts's error taxonomy. Never touches
|
|
17
|
+
* the persisted backend preference; the caller only does that after seeing
|
|
18
|
+
* `{ ok: true }`.
|
|
19
|
+
*
|
|
20
|
+
* "Success" here means at least one mapped provider resolved — not that
|
|
21
|
+
* every one did. A single bad item/field name is a per-provider config
|
|
22
|
+
* typo the user can fix later; it shouldn't block enabling the backend for
|
|
23
|
+
* every OTHER provider that already works. A systemic problem (op missing,
|
|
24
|
+
* wrong version, or every single provider failing — usually a signed-out
|
|
25
|
+
* or invalid-token case) DOES block, since persisting a backend where
|
|
26
|
+
* nothing works at all isn't useful and would just defer the failure to
|
|
27
|
+
* the next `crosscheck serve`.
|
|
28
|
+
*/
|
|
29
|
+
export async function validateOnePasswordSetup() {
|
|
30
|
+
const cfg = loadOnePasswordConfig();
|
|
31
|
+
if (!cfg.ok) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
stage: "config",
|
|
35
|
+
detail: `${cfg.error}: ${cfg.detail} (expected at ${configFilePath()})`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const versionCheck = await checkOpVersion();
|
|
39
|
+
if (!versionCheck.ok) {
|
|
40
|
+
return { ok: false, stage: "op-version", detail: versionCheck.detail };
|
|
41
|
+
}
|
|
42
|
+
const providerNames = Object.keys(cfg.config.mappings).sort();
|
|
43
|
+
const results = [];
|
|
44
|
+
for (const provider of providerNames) {
|
|
45
|
+
const ref = cfg.config.mappings[provider];
|
|
46
|
+
const result = await readOpSecret(ref, {
|
|
47
|
+
authMode: cfg.config.authMode,
|
|
48
|
+
serviceAccountToken: cfg.config.authMode === "service-account"
|
|
49
|
+
? process.env.OP_SERVICE_ACCOUNT_TOKEN
|
|
50
|
+
: undefined,
|
|
51
|
+
});
|
|
52
|
+
results.push(result.ok
|
|
53
|
+
? { provider, ok: true }
|
|
54
|
+
: { provider, ok: false, error: `${result.error}: ${result.detail}` });
|
|
55
|
+
}
|
|
56
|
+
if (providerNames.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
stage: "no-providers-resolved",
|
|
60
|
+
detail: "no providers are mapped in the config — add at least one before enabling",
|
|
61
|
+
providers: results,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (!results.some((r) => r.ok)) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
stage: "no-providers-resolved",
|
|
68
|
+
detail: "no configured provider resolved successfully",
|
|
69
|
+
providers: results,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, authMode: cfg.config.authMode, providers: results };
|
|
73
|
+
}
|
|
74
|
+
function printProviderResults(results) {
|
|
75
|
+
for (const p of results) {
|
|
76
|
+
process.stdout.write(p.ok ? ` ✓ ${p.provider}\n` : ` ✗ ${p.provider}: ${p.error}\n`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Orchestrates `crosscheck keys setup --backend 1password`: by default,
|
|
81
|
+
* opens a browser wizard to collect authMode + per-provider Vault/Item/Field
|
|
82
|
+
* (pre-filled with whatever's already configured, so re-running to add one
|
|
83
|
+
* more provider doesn't lose the rest) and writes the config — no one has
|
|
84
|
+
* to hand-edit ~/.crosscheck/1password.json. Pass `{ useFile: true }` (the
|
|
85
|
+
* CLI's `--file` flag) to skip the browser and just validate a config
|
|
86
|
+
* that's already been placed there some other way (dotfiles, CI
|
|
87
|
+
* provisioning, etc.). Either way, ends the same: validate, then (only on
|
|
88
|
+
* success) persist the preference and report what's usable.
|
|
89
|
+
*/
|
|
90
|
+
export async function runOnePasswordKeysSetup(opts = {}) {
|
|
91
|
+
if (!opts.useFile) {
|
|
92
|
+
const existingLoad = loadOnePasswordConfig();
|
|
93
|
+
const existing = existingLoad.ok ? existingLoad.config.mappings : undefined;
|
|
94
|
+
const existingAuthMode = existingLoad.ok ? existingLoad.config.authMode : undefined;
|
|
95
|
+
process.stdout.write("\nLet's set up 1Password in your browser — no file to hand-edit.\n");
|
|
96
|
+
let captured;
|
|
97
|
+
try {
|
|
98
|
+
captured = await captureOnePasswordConfigViaBrowser({
|
|
99
|
+
providers: [...SUPPORTED_PROVIDERS],
|
|
100
|
+
existing,
|
|
101
|
+
existingAuthMode,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
process.stderr.write(`\n✗ ${err instanceof Error ? err.message : String(err)}\n\n`);
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (Object.keys(captured.mappings).length === 0) {
|
|
110
|
+
process.stderr.write("\n✗ No providers were mapped — nothing to enable.\n\n");
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const written = writeOnePasswordConfig(captured);
|
|
115
|
+
if (!written.ok) {
|
|
116
|
+
process.stderr.write(`\n✗ ${written.error}: ${written.detail}\n\n`);
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(`✓ Saved ${configFilePath()}\n`);
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write("\nValidating your 1Password configuration...\n");
|
|
123
|
+
const result = await validateOnePasswordSetup();
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
process.stderr.write(`\n✗ ${result.detail}\n`);
|
|
126
|
+
if (result.stage === "config") {
|
|
127
|
+
process.stderr.write(`\nExpected a config at ${configFilePath()} shaped like:\n` +
|
|
128
|
+
' {\n "authMode": "desktop",\n "mappings": { "anthropic": "op://Vault/Item/field" }\n }\n' +
|
|
129
|
+
'\n(authMode is "desktop" or "service-account"; for service-account mode, set\n' +
|
|
130
|
+
"OP_SERVICE_ACCOUNT_TOKEN in the environment — never store it in this file.)\n");
|
|
131
|
+
}
|
|
132
|
+
if (result.stage === "op-version") {
|
|
133
|
+
process.stderr.write(`\nRequires the 1Password CLI (\`op\`), version >= ${MIN_OP_VERSION}.\n`);
|
|
134
|
+
}
|
|
135
|
+
if (result.providers)
|
|
136
|
+
printProviderResults(result.providers);
|
|
137
|
+
process.stderr.write("\nFix the above, then re-run `crosscheck keys setup --backend 1password`.\n\n");
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
enableOnePasswordBackend();
|
|
142
|
+
process.stdout.write(`\n✓ 1Password backend enabled (authMode: ${result.authMode}).\n\n`);
|
|
143
|
+
printProviderResults(result.providers);
|
|
144
|
+
process.stdout.write("\nMachine-local secrets (activation, HMAC signing key) still use the local\n" +
|
|
145
|
+
"encrypted file, unchanged — only providers marked ✓ above are read from\n" +
|
|
146
|
+
"1Password. Anything else (including a ✗ above) falls back to whatever's\n" +
|
|
147
|
+
"already stored locally, if anything.\n\n" +
|
|
148
|
+
"Run `crosscheck doctor` any time to recheck this.\n\n");
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/onepassword/setup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crosscheck-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"description": "Crosscheck — multi-LLM orchestration MCP for the terminal. Single-machine activation, BYO upstream LLM keys, content-free telemetry. Installs into Claude Code in one command.",
|
|
5
5
|
"license": "SEE LICENSE.md",
|
|
6
6
|
"type": "module",
|