@skrr-ai/cli 0.1.123 → 0.1.125
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/commands/agents/chat.d.ts +26 -0
- package/dist/commands/agents/chat.js +48 -10
- package/dist/commands/agents/delete.d.ts +8 -0
- package/dist/commands/agents/delete.js +16 -0
- package/dist/commands/context.js +7 -0
- package/dist/commands/search/workspace.js +1 -1
- package/dist/commands/store/gallery-admissions/approve.js +1 -1
- package/dist/commands/store/gallery-admissions/preview.js +1 -1
- package/dist/commands/store/gallery-admissions/refuse.js +1 -1
- package/dist/commands/store/gallery-admissions/show.js +1 -1
- package/dist/generated/agent-guides.json +19 -19
- package/dist/lib/agentic-stream.js +15 -1
- package/dist/lib/auth-core-init.d.ts +15 -2
- package/dist/lib/auth-core-init.js +47 -6
- package/dist/lib/command-hygiene.js +30 -1
- package/dist/lib/daemonBrokerRefusal.js +5 -1
- package/dist/lib/keychain.js +41 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +13 -28
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +133 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +387 -90
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +102 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +11 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +43 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +33 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +12 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +15 -37
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +133 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +536 -101
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +13 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +43 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +104 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +14 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +1094 -1067
- package/oclif.manifest.json +16 -3
- package/package.json +4 -1
|
@@ -30,6 +30,10 @@ function isAnsweredBrokerRefusal(outcome) {
|
|
|
30
30
|
/** Codes whose repair is signing the DAEMON in again (laptop only). */
|
|
31
31
|
const DAEMON_RELOGIN_CODES = new Set([
|
|
32
32
|
'DEVICE_PROOF_PIN_MISMATCH',
|
|
33
|
+
// The daemon's device key could not be read, so it sent no proof (OSK-20506). Only an
|
|
34
|
+
// interactive daemon login creates and pins a key; waiting never does, which is why
|
|
35
|
+
// the daemon's own mint hold treats it as credential-bound (`CREDENTIAL_BOUND_CODES`).
|
|
36
|
+
'DEVICE_PROOF_MISSING',
|
|
33
37
|
'SESSION_REVOKED',
|
|
34
38
|
'PRINCIPAL_UNRESOLVED',
|
|
35
39
|
'DAEMON_NOT_AUTHED',
|
|
@@ -88,7 +92,7 @@ function describeBrokerRefusal(failure, bin = 'skrr') {
|
|
|
88
92
|
`\`${bin} machines dedicated health-check <lease-id>\` (\`${bin} machines dedicated list\` shows the lease id). ` +
|
|
89
93
|
guestReissueRemedy(bin, 'If it stays refused');
|
|
90
94
|
}
|
|
91
|
-
else if (code === 'DEVICE_PROOF_PIN_MISMATCH') {
|
|
95
|
+
else if (code === 'DEVICE_PROOF_PIN_MISMATCH' || code === 'DEVICE_PROOF_MISSING') {
|
|
92
96
|
remedy =
|
|
93
97
|
`Run \`${(0, daemon_command_hint_1.daemonCommandHint)('login', { bin })}\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
|
|
94
98
|
`\`${bin} login\` signs this CLI in on its own instead.`;
|
package/dist/lib/keychain.js
CHANGED
|
@@ -609,7 +609,9 @@ function deleteRefreshToken() {
|
|
|
609
609
|
* prints service names and does not prompt for or reveal any secret.
|
|
610
610
|
*
|
|
611
611
|
* Filtered to `LEGACY_NATIVE_ID_PREFIXES` followed by a dot, so a third-party
|
|
612
|
-
* item that merely starts with the same letters is never in scope.
|
|
612
|
+
* item that merely starts with the same letters is never in scope. That is the
|
|
613
|
+
* SCOPE of the sweep and nothing more: which of these services it deletes is
|
|
614
|
+
* decided by `isRetiredBrandSweepTarget`, by name.
|
|
613
615
|
*/
|
|
614
616
|
function discoverLegacyBrandServices() {
|
|
615
617
|
let dump;
|
|
@@ -639,6 +641,12 @@ function discoverLegacyBrandServices() {
|
|
|
639
641
|
* because a delete that never reports "not found" would otherwise spin.
|
|
640
642
|
*/
|
|
641
643
|
function deleteEntireService(service) {
|
|
644
|
+
// The sweep can name key material (`--all-devices` selects the retired KEKs), so it is the one
|
|
645
|
+
// CLI mutator behind the guard. The CLI's own login slots are not: `ai.skrr.cli.<profile>`
|
|
646
|
+
// carries no hash of the home, so a sandbox and the account give the SAME name and the guard
|
|
647
|
+
// could not tell a faked `security` from the real one. A test's protection there is its
|
|
648
|
+
// sandbox home, under which macOS finds no default keychain to write to.
|
|
649
|
+
(0, auth_core_1.assertMayMutateLiveKeychain)(service, `delete every Keychain item in ${service}`);
|
|
642
650
|
let removedAny = false;
|
|
643
651
|
for (let i = 0; i < 32; i += 1) {
|
|
644
652
|
try {
|
|
@@ -680,9 +688,38 @@ function deleteEntireService(service) {
|
|
|
680
688
|
function deleteLegacyBrandEntries(options = {}) {
|
|
681
689
|
if (!isAvailable())
|
|
682
690
|
return [];
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
691
|
+
const includeMachineCredentials = options.includeMachineCredentials === true;
|
|
692
|
+
const inScope = discoverLegacyBrandServices();
|
|
693
|
+
const targets = inScope.filter((service) => isRetiredBrandSweepTarget(service, includeMachineCredentials));
|
|
694
|
+
if (process.env.DEBUG) {
|
|
695
|
+
for (const service of inScope) {
|
|
696
|
+
if (!targets.includes(service)) {
|
|
697
|
+
console.error(`[cli/keychain] legacy sweep left unclassified service ${service}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return targets.filter((service) => deleteEntireService(service));
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* A CLI slot the previous brand wrote: `<previous prefix>.cli.<profile>`, under either of the
|
|
705
|
+
* two previous prefixes. Positive on the whole name, so the bare `.cli` word alone, or a
|
|
706
|
+
* service that merely begins with these words, is not one.
|
|
707
|
+
*/
|
|
708
|
+
const RETIRED_CLI_SERVICE_PATTERN = new RegExp(`^(?:${auth_core_1.LEGACY_NATIVE_ID_PREFIXES.map((prefix) => prefix.replace(/\./g, '\\.')).join('|')})` +
|
|
709
|
+
'\\.cli\\.[A-Za-z0-9][A-Za-z0-9._-]*$');
|
|
710
|
+
/**
|
|
711
|
+
* Whether the legacy sweep deletes this previous-brand service. Named, not matched by prefix:
|
|
712
|
+
* a retired CLI slot always; and, only for `--all-devices` (`includeMachineCredentials`), the
|
|
713
|
+
* daemon services the shared grammar in `@skrr-ai/auth-core` classifies (sessions, the person's
|
|
714
|
+
* own provider keys, and the retired KEKs, which the stolen-laptop case is for). A previous-brand
|
|
715
|
+
* service nobody classified is left in place. The current brand is out of scope entirely.
|
|
716
|
+
*/
|
|
717
|
+
function isRetiredBrandSweepTarget(service, includeMachineCredentials) {
|
|
718
|
+
if (!auth_core_1.LEGACY_NATIVE_ID_PREFIXES.some((prefix) => service.startsWith(`${prefix}.`)))
|
|
719
|
+
return false;
|
|
720
|
+
if (RETIRED_CLI_SERVICE_PATTERN.test(service))
|
|
721
|
+
return true;
|
|
722
|
+
return includeMachineCredentials && (0, auth_core_1.classifyDaemonKeychainService)(service) !== 'unclassified';
|
|
686
723
|
}
|
|
687
724
|
/**
|
|
688
725
|
* Remove every CLI keychain slot for the active profile. Used by `skrr logout`.
|
|
@@ -161,7 +161,11 @@ export type DaemonSupervisor = 'desktop' | 'service';
|
|
|
161
161
|
export declare function daemonSignInRemedy(opts?: {
|
|
162
162
|
/** How the daemon is run; absent when the caller cannot tell. */
|
|
163
163
|
supervisor?: DaemonSupervisor;
|
|
164
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* The exact login command for this profile (`skrrd … login`, or
|
|
166
|
+
* `skrr daemon login --profile …`), when the caller knows it. Used when the
|
|
167
|
+
* supervisor is `service` or unknown; a desktop-hosted daemon is never told one.
|
|
168
|
+
*/
|
|
165
169
|
loginCommand?: string;
|
|
166
170
|
/** The refusal was DEVICE_PROOF_PIN_MISMATCH: say that a sign-in re-pins. */
|
|
167
171
|
repinsDeviceKey?: boolean;
|
|
@@ -132,7 +132,7 @@ function daemonSignInRemedy(opts = {}) {
|
|
|
132
132
|
const login = opts.loginCommand ? `\`${opts.loginCommand}\`` : "`skrr daemon login`";
|
|
133
133
|
return `run ${login}${repin ? " \u2014 an interactive login re-pins the device key" : ""}`;
|
|
134
134
|
}
|
|
135
|
-
return
|
|
135
|
+
return `sign the daemon in again on the machine running it: if the Skrr desktop app runs it, quit and reopen the app; otherwise run \`${opts.loginCommand ?? "skrr daemon login"}\`` + (repin ? " (either one re-pins the device key)" : "");
|
|
136
136
|
}
|
|
137
137
|
// Annotate the CommonJS export names for ESM import in node:
|
|
138
138
|
0 && (module.exports = {
|
|
@@ -55,8 +55,10 @@ var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
|
|
|
55
55
|
var import_credentialEnvelope = require("./credentialEnvelope.js");
|
|
56
56
|
var import_kek = require("./kek/index.js");
|
|
57
57
|
var import_profileStateDir = require("./profileStateDir.js");
|
|
58
|
+
var import_liveState = require("./liveState.js");
|
|
58
59
|
var import_refresh = require("./refresh.js");
|
|
59
60
|
var import_runtime = require("./runtime.js");
|
|
61
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
60
62
|
const ACCOUNT_CRED_DEK_WRAPPED = "cred-dek-wrapped";
|
|
61
63
|
let _state = { kind: "uninit" };
|
|
62
64
|
let _kekRotationDetected = false;
|
|
@@ -71,28 +73,6 @@ function __getStateForTest() {
|
|
|
71
73
|
function wrappedDekFilePath(profile) {
|
|
72
74
|
return (0, import_profileStateDir.resolveProfileStateFile)(profile, ACCOUNT_CRED_DEK_WRAPPED).path;
|
|
73
75
|
}
|
|
74
|
-
function atomicWrite0600(target, value) {
|
|
75
|
-
const dir = import_node_path.default.dirname(target);
|
|
76
|
-
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
77
|
-
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
78
|
-
const fd = import_node_fs.default.openSync(tmp, "w", 384);
|
|
79
|
-
try {
|
|
80
|
-
import_node_fs.default.writeSync(fd, value);
|
|
81
|
-
import_node_fs.default.fsyncSync(fd);
|
|
82
|
-
} finally {
|
|
83
|
-
import_node_fs.default.closeSync(fd);
|
|
84
|
-
}
|
|
85
|
-
import_node_fs.default.renameSync(tmp, target);
|
|
86
|
-
try {
|
|
87
|
-
const dirFd = import_node_fs.default.openSync(dir, "r");
|
|
88
|
-
try {
|
|
89
|
-
import_node_fs.default.fsyncSync(dirFd);
|
|
90
|
-
} finally {
|
|
91
|
-
import_node_fs.default.closeSync(dirFd);
|
|
92
|
-
}
|
|
93
|
-
} catch {
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
76
|
const warnedSignatures = /* @__PURE__ */ new Set();
|
|
97
77
|
const MAX_WARNED_SIGNATURES = 64;
|
|
98
78
|
function __clearWarnDedupeForTest() {
|
|
@@ -124,9 +104,9 @@ function credEnvelopeLockPath(profile) {
|
|
|
124
104
|
async function withCredEnvelopeLock(profile, body) {
|
|
125
105
|
const lockPath = credEnvelopeLockPath(profile);
|
|
126
106
|
const dir = import_node_path.default.dirname(lockPath);
|
|
127
|
-
|
|
107
|
+
(0, import_stateFiles.ensureStateDir)(dir, `take the credential envelope lock for profile ${profile}`);
|
|
128
108
|
if (!import_node_fs.default.existsSync(lockPath)) {
|
|
129
|
-
|
|
109
|
+
(0, import_stateFiles.writeStateFile)(lockPath, "", { mode: 384 }, "create the credential envelope lock");
|
|
130
110
|
}
|
|
131
111
|
const acquireStart = Date.now();
|
|
132
112
|
const release = await import_proper_lockfile.default.lock(lockPath, {
|
|
@@ -202,6 +182,7 @@ async function consolidateWrappedDek(profile, kek, aad) {
|
|
|
202
182
|
onEvent: (name, success, meta) => emit(`cred_envelope.${name}`, success, meta)
|
|
203
183
|
}));
|
|
204
184
|
} catch (err) {
|
|
185
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
205
186
|
emit("cred_envelope.path_consolidation.failed", false, {
|
|
206
187
|
profile,
|
|
207
188
|
message: err?.message ?? "unknown"
|
|
@@ -268,16 +249,17 @@ function keepUnreadableWrappedDek(profile, wrapped) {
|
|
|
268
249
|
}
|
|
269
250
|
}
|
|
270
251
|
const name = `${UNREADABLE_DEK_COPY_PREFIX}${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
271
|
-
atomicWrite0600(import_node_path.default.join(dir, name), wrapped);
|
|
252
|
+
(0, import_stateFiles.atomicWrite0600)(import_node_path.default.join(dir, name), wrapped, "keep the unreadable wrapped DEK");
|
|
272
253
|
const kept = [...existing, name].sort();
|
|
273
254
|
for (const stale of kept.slice(0, Math.max(0, kept.length - MAX_UNREADABLE_DEK_COPIES))) {
|
|
274
255
|
try {
|
|
275
|
-
|
|
256
|
+
(0, import_stateFiles.removeStateFile)(import_node_path.default.join(dir, stale), "prune a kept wrapped DEK");
|
|
276
257
|
} catch {
|
|
277
258
|
}
|
|
278
259
|
}
|
|
279
260
|
return name;
|
|
280
261
|
} catch (err) {
|
|
262
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
281
263
|
emit("cred_envelope.unreadable_dek_copy_failed", false, {
|
|
282
264
|
profile,
|
|
283
265
|
message: err?.message ?? "unknown"
|
|
@@ -395,6 +377,7 @@ async function initNewIndependentCredEnvelope(profile) {
|
|
|
395
377
|
const dek = (0, import_credentialEnvelope.generateDek)();
|
|
396
378
|
let temp;
|
|
397
379
|
try {
|
|
380
|
+
(0, import_liveState.assertMayMutateLiveState)(dir, `enroll the wrapped DEK for background profile ${profile}`);
|
|
398
381
|
const wrapped = await kek.wrap(dek, (0, import_credentialEnvelope.buildCredentialAad)(profile));
|
|
399
382
|
if (paths.some(present))
|
|
400
383
|
return await initExistingCredEnvelope(profile);
|
|
@@ -572,7 +555,7 @@ async function initCredEnvelope(profile) {
|
|
|
572
555
|
try {
|
|
573
556
|
dek = (0, import_credentialEnvelope.generateDek)();
|
|
574
557
|
const wrapped = await kek.wrap(dek, aad);
|
|
575
|
-
atomicWrite0600(targetPath, wrapped);
|
|
558
|
+
(0, import_stateFiles.atomicWrite0600)(targetPath, wrapped, `write the wrapped DEK for profile ${profile}`);
|
|
576
559
|
emit("cred_envelope.dek_enrolled", true, {
|
|
577
560
|
kekId: kek.id,
|
|
578
561
|
kekKind: captureKind(),
|
|
@@ -580,6 +563,7 @@ async function initCredEnvelope(profile) {
|
|
|
580
563
|
...unwrapAttemptedAndFailed ? { reEnrolledAfterRotation: true } : {}
|
|
581
564
|
});
|
|
582
565
|
} catch (err) {
|
|
566
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
583
567
|
_state = { kind: "disabled", reason: "init_failed" };
|
|
584
568
|
emit("cred_envelope.init_failed", false, {
|
|
585
569
|
reason: "enrollment_failed",
|
|
@@ -613,8 +597,9 @@ async function resetCredEnvelope(opts) {
|
|
|
613
597
|
...retiredDekFiles(opts.profile)
|
|
614
598
|
]) {
|
|
615
599
|
try {
|
|
616
|
-
|
|
600
|
+
(0, import_stateFiles.removeStateFile)(target, `delete the wrapped DEK file ${import_node_path.default.basename(target)}`);
|
|
617
601
|
} catch (err) {
|
|
602
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
618
603
|
if (err.code !== "ENOENT") {
|
|
619
604
|
emit("cred_envelope.clear_failed", false, {
|
|
620
605
|
profile: opts.profile,
|
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type DeviceKeyAlg, type DevicePublicJwk } from './deviceKey.js';
|
|
2
|
+
import { type DeviceKeyCustody, type DeviceKeyHandle } from './deviceKeyHandle.js';
|
|
3
|
+
import { type HardwareKeyCapability, type HardwareKeyRecord } from './deviceKeyHardware.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `cred_envelope.read.decrypt_failed` hint for the device PRIVATE key
|
|
6
|
+
* (OSK-20506).
|
|
7
|
+
*
|
|
8
|
+
* The envelope's default hint says "signing in again rebuilds it", which is true
|
|
9
|
+
* of a credential and false of this: `skrr login` never creates or replaces a
|
|
10
|
+
* device key, and only an interactive daemon sign-in does (it re-pins the new key
|
|
11
|
+
* on the server). `loadKeypair` hands this to `maybeDecryptOnRead` so the
|
|
12
|
+
* producer says WHAT failed; a presenter that already prints one device-key line
|
|
13
|
+
* (the CLI's `recovery_required` notice) recognises it by identity and prints
|
|
14
|
+
* nothing for the decrypt failure, instead of two lines with two remedies. A
|
|
15
|
+
* logger that prints every warning, the daemon's, shows this one as written.
|
|
16
|
+
*
|
|
17
|
+
* Compared by value by the CLI presenter: change the text and the CLI prints the
|
|
18
|
+
* credential line again, which `auth-core-init.device-key.spec.ts` catches.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT = "stored device key is unreadable on this machine \u2014 only an interactive daemon sign-in replaces it";
|
|
21
|
+
/**
|
|
22
|
+
* `key` is a handle, never a keypair: nothing in this module (or any caller of
|
|
23
|
+
* it) can read a private scalar once a key is active, which is what lets a
|
|
24
|
+
* hardware key stand in for a software one without a second code path.
|
|
25
|
+
*
|
|
26
|
+
* `envelopeProtected` is about the PRIVATE KEY FILE being sealed under the
|
|
27
|
+
* credential envelope. A hardware record is deliberately not sealed (it is
|
|
28
|
+
* useless off this machine, and sealing it would tie the device identity's life
|
|
29
|
+
* to the envelope key again), so it is always `false` for hardware and says
|
|
30
|
+
* nothing about how well that key is protected: read `key.custody` for that.
|
|
31
|
+
*/
|
|
2
32
|
type DeviceIdentityState = {
|
|
3
33
|
kind: 'uninit';
|
|
4
34
|
} | {
|
|
@@ -6,7 +36,7 @@ type DeviceIdentityState = {
|
|
|
6
36
|
reason: DisabledReason;
|
|
7
37
|
} | {
|
|
8
38
|
kind: 'active';
|
|
9
|
-
|
|
39
|
+
key: DeviceKeyHandle;
|
|
10
40
|
profile: string;
|
|
11
41
|
envelopeProtected: boolean;
|
|
12
42
|
};
|
|
@@ -69,6 +99,69 @@ export declare function resetDeviceIdentity(opts?: {
|
|
|
69
99
|
profile?: string;
|
|
70
100
|
clearOnDisk?: boolean;
|
|
71
101
|
}): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* A hardware key that exists only in memory. It is not this machine's identity
|
|
104
|
+
* until `commitDeviceKeyCandidate`, and it leaves nothing behind if it is
|
|
105
|
+
* dropped: a Secure Enclave key lives in the blob and nowhere else.
|
|
106
|
+
*
|
|
107
|
+
* That is the whole safety argument for an upgrade. The server learns the new key
|
|
108
|
+
* from a proof signed with it (an interactive sign-in re-pins the row), so the
|
|
109
|
+
* order has to be: create the candidate, sign the sign-in with it, and only after
|
|
110
|
+
* the server has accepted that sign-in make it the identity. The software key
|
|
111
|
+
* stays on disk, untouched and active, through every step before the last one.
|
|
112
|
+
*/
|
|
113
|
+
export interface DeviceKeyCandidate {
|
|
114
|
+
readonly key: DeviceKeyHandle;
|
|
115
|
+
/** @internal what `commitDeviceKeyCandidate` persists. */
|
|
116
|
+
readonly record: HardwareKeyRecord;
|
|
117
|
+
}
|
|
118
|
+
export type DeviceKeyCandidateResult = {
|
|
119
|
+
ok: true;
|
|
120
|
+
candidate: DeviceKeyCandidate;
|
|
121
|
+
} | {
|
|
122
|
+
ok: false;
|
|
123
|
+
reason: 'already_hardware' | 'independent_profile' | 'unavailable' | 'create_failed';
|
|
124
|
+
capability?: HardwareKeyCapability;
|
|
125
|
+
message?: string;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Create a hardware key candidate if this machine can hold one and does not
|
|
129
|
+
* already. Never throws and never touches disk, so an interactive sign-in can
|
|
130
|
+
* call it unconditionally and fall back to the key it has on any answer but ok.
|
|
131
|
+
*/
|
|
132
|
+
export declare function prepareHardwareKeyCandidate(profile: string): Promise<DeviceKeyCandidateResult>;
|
|
133
|
+
/** Sign a request with a candidate (the upgrade's sign-in proof). Null on failure, like `signServerRequest`. */
|
|
134
|
+
export declare function signWithCandidate(candidate: DeviceKeyCandidate, opts: {
|
|
135
|
+
method: string;
|
|
136
|
+
url: string;
|
|
137
|
+
refreshToken?: string;
|
|
138
|
+
accessToken?: string;
|
|
139
|
+
}): Promise<string | null>;
|
|
140
|
+
/**
|
|
141
|
+
* Make a candidate this profile's device identity. Call it only after the
|
|
142
|
+
* server has accepted a sign-in proven with `candidate`: until then the software
|
|
143
|
+
* key is the identity the server pins, and replacing it early is the failure
|
|
144
|
+
* this API exists to prevent.
|
|
145
|
+
*
|
|
146
|
+
* Under the identity lock, in the order that survives a crash: retire the old
|
|
147
|
+
* key, write the hardware record (authoritative), then the public file, then
|
|
148
|
+
* clear the enrollment marker (it records the OLD thumbprint and would otherwise
|
|
149
|
+
* short-circuit every later enrollment as already done).
|
|
150
|
+
*/
|
|
151
|
+
export declare function commitDeviceKeyCandidate(profile: string, candidate: DeviceKeyCandidate): Promise<void>;
|
|
152
|
+
/** A sign-in that takes longer than this has been abandoned; its pending key is not committed. */
|
|
153
|
+
export declare const PENDING_HARDWARE_KEY_MAX_AGE_MS: number;
|
|
154
|
+
/** Park a candidate so a later process can commit or discard it. Replaces any earlier one. */
|
|
155
|
+
export declare function persistPendingHardwareKey(profile: string, candidate: DeviceKeyCandidate): Promise<void>;
|
|
156
|
+
export type PendingHardwareKeyOutcome = 'committed' | 'no_pending' | 'mismatch' | 'expired' | 'unreadable';
|
|
157
|
+
/**
|
|
158
|
+
* Make the parked candidate this profile's identity, if it is the one the caller
|
|
159
|
+
* proved the sign-in with (`thumbprint`) and it is not stale. Anything else
|
|
160
|
+
* commits nothing: the software key stays the identity, and the answer says why.
|
|
161
|
+
*/
|
|
162
|
+
export declare function commitPendingHardwareKey(profile: string, thumbprint: string): Promise<PendingHardwareKeyOutcome>;
|
|
163
|
+
/** Drop the parked candidate (the sign-in failed or was refused). Idempotent. */
|
|
164
|
+
export declare function discardPendingHardwareKey(profile: string): Promise<void>;
|
|
72
165
|
/** True iff init has completed and a keypair is in memory. */
|
|
73
166
|
export declare function isDeviceIdentityActive(): boolean;
|
|
74
167
|
/**
|
|
@@ -77,8 +170,10 @@ export declare function isDeviceIdentityActive(): boolean;
|
|
|
77
170
|
* on the in-flight request.
|
|
78
171
|
*/
|
|
79
172
|
export declare function getDevicePublicKey(): {
|
|
80
|
-
jwk:
|
|
173
|
+
jwk: DevicePublicJwk;
|
|
81
174
|
thumbprint: string;
|
|
175
|
+
alg: DeviceKeyAlg;
|
|
176
|
+
custody: DeviceKeyCustody;
|
|
82
177
|
} | null;
|
|
83
178
|
/**
|
|
84
179
|
* Operator-readable summary of device-identity state. Used by
|
|
@@ -90,13 +185,47 @@ export declare function describeDeviceIdentityState(): {
|
|
|
90
185
|
thumbprint?: string;
|
|
91
186
|
profile?: string;
|
|
92
187
|
envelopeProtected?: boolean;
|
|
188
|
+
alg?: DeviceKeyAlg;
|
|
189
|
+
/** Whether the private half can be copied off this machine. Absent unless active. */
|
|
190
|
+
custody?: DeviceKeyCustody;
|
|
191
|
+
/**
|
|
192
|
+
* For a hardware key: can THIS process sign with it right now? False means the
|
|
193
|
+
* key exists and is intact but the helper that signs with it is not installed
|
|
194
|
+
* where this process can find it (a CLI without the desktop app's helper, an
|
|
195
|
+
* app update that has not landed). It is a different fact from an unreadable
|
|
196
|
+
* key, and it must never lead to replacing the key.
|
|
197
|
+
*/
|
|
198
|
+
canSign?: boolean;
|
|
199
|
+
};
|
|
200
|
+
export type StoredDeviceKeyDescription = {
|
|
201
|
+
stored: false;
|
|
202
|
+
} | {
|
|
203
|
+
stored: true;
|
|
204
|
+
alg: DeviceKeyAlg;
|
|
205
|
+
custody: DeviceKeyCustody;
|
|
206
|
+
thumbprint: string;
|
|
207
|
+
/** A hardware key needs the helper to sign; a software key never does. */
|
|
208
|
+
canSign: boolean;
|
|
93
209
|
};
|
|
210
|
+
/**
|
|
211
|
+
* What a profile's device key IS, read from its plaintext public record alone. For
|
|
212
|
+
* `status`, which is a different process from the daemon and may run while the
|
|
213
|
+
* daemon is stopped: it opens nothing, needs no envelope key and writes nothing, so
|
|
214
|
+
* it answers even when the private half is unreadable. Not evidence the key can be
|
|
215
|
+
* used (only `describeDeviceIdentityState` in the process that loaded it says that);
|
|
216
|
+
* evidence of what kind of key the profile has, which nothing else showed.
|
|
217
|
+
*/
|
|
218
|
+
export declare function describeStoredDeviceKey(profile: string): StoredDeviceKeyDescription;
|
|
94
219
|
/**
|
|
95
220
|
* Sign an outgoing request with the device key. Returns the JWS Compact
|
|
96
221
|
* Serialization, or null when device identity is not active. NEVER
|
|
97
222
|
* throws — a failed sign returns null and emits a telemetry event so
|
|
98
223
|
* the caller can fall back to bearer-only auth without an outage.
|
|
99
224
|
*
|
|
225
|
+
* Asynchronous because a hardware key signs in a helper process. A software key
|
|
226
|
+
* resolves on the next microtask, so a caller that awaits pays nothing it can
|
|
227
|
+
* measure; a caller that could not await could not have a hardware key.
|
|
228
|
+
*
|
|
100
229
|
* Shape matches `DeviceProofSigner` from `./refresh.ts` so the function
|
|
101
230
|
* passes directly into `refreshDaemonToken`'s `signRequestProof` slot.
|
|
102
231
|
*/
|
|
@@ -105,7 +234,7 @@ export declare function signServerRequest(opts: {
|
|
|
105
234
|
url: string;
|
|
106
235
|
refreshToken?: string;
|
|
107
236
|
accessToken?: string;
|
|
108
|
-
}): string | null
|
|
237
|
+
}): Promise<string | null>;
|
|
109
238
|
/** @internal Test seams — file path resolvers. */
|
|
110
239
|
export declare function __publicKeyPathForTest(profile: string): string;
|
|
111
240
|
export declare function __privateKeyPathForTest(profile: string): string;
|