@skrr-ai/cli 0.1.122 → 0.1.124
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 +7 -0
- package/dist/commands/agents/chat.js +32 -6
- 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/followups/act.js +3 -0
- package/dist/commands/followups/checkin.js +3 -0
- package/dist/commands/followups/remind.js +3 -0
- package/dist/commands/followups/show.js +1 -1
- package/dist/commands/followups/watch.js +3 -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/lib/agentic-stream.d.ts +46 -0
- package/dist/lib/agentic-stream.js +135 -14
- package/dist/lib/auth-core-init.d.ts +15 -2
- package/dist/lib/auth-core-init.js +47 -6
- package/dist/lib/auth-failure.js +12 -0
- package/dist/lib/command-hygiene.js +30 -1
- package/dist/lib/daemonBrokerRefusal.js +5 -1
- package/dist/lib/exec-runtime-binary.d.ts +9 -0
- package/dist/lib/exec-runtime-binary.js +15 -1
- package/dist/lib/followups.d.ts +13 -0
- package/dist/lib/followups.js +8 -0
- package/dist/lib/keychain.js +41 -4
- package/dist/lib/profile-machine-identity-guard.js +2 -1
- package/dist/lib/runtime-start-verification.js +6 -1
- 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 +69 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +135 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +440 -83
- 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 +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +13 -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 +11 -2
- 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 +14 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +67 -0
- 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/messages.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +1 -0
- 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 +24 -16
- 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/cjs/telemetryRedaction.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +7 -1
- 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 +122 -38
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +135 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +603 -90
- 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 +14 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -0
- 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 +18 -2
- 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 +23 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +134 -0
- 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/messages.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +1 -0
- 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 +45 -16
- 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/dist/esm/telemetryRedaction.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +40 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7349 -7325
- package/oclif.manifest.json +11 -2
- package/package.json +4 -1
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.CliCredentialInitializationError = exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = exports.CREDENTIAL_KEY_CHANGED_NOTICE = void 0;
|
|
7
7
|
exports.plainCredentialEnvelopeWarning = plainCredentialEnvelopeWarning;
|
|
8
|
+
exports.deviceKeyRecoveryRequiredNotice = deviceKeyRecoveryRequiredNotice;
|
|
8
9
|
exports.createCredentialEnvelopeWarningPresenter = createCredentialEnvelopeWarningPresenter;
|
|
9
10
|
exports.prepareDedicatedRuntimeAuthGeneration = prepareDedicatedRuntimeAuthGeneration;
|
|
10
11
|
exports.ensureAuthCoreConfigured = ensureAuthCoreConfigured;
|
|
@@ -38,10 +39,12 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
38
39
|
const node_path_1 = __importDefault(require("node:path"));
|
|
39
40
|
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
40
41
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
42
|
+
const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
|
|
41
43
|
const cred_envelope_1 = require("./cred-envelope");
|
|
42
44
|
const device_identity_1 = require("./device-identity");
|
|
43
45
|
const auth_storage_1 = require("./auth-storage");
|
|
44
46
|
const keychain_1 = require("./keychain");
|
|
47
|
+
const daemon_command_hint_1 = require("./daemon-command-hint");
|
|
45
48
|
function envFlag(name) {
|
|
46
49
|
const v = process.env[name];
|
|
47
50
|
return v === '1' || v === 'true' || v === 'TRUE';
|
|
@@ -117,13 +120,33 @@ const DEVICE_KEY_DECRYPT_FAILED = '[deviceIdentity] private_key_decrypt_failed';
|
|
|
117
120
|
const DEVICE_KEY_RECOVERY_REQUIRED = '[deviceIdentity] recovery_required';
|
|
118
121
|
/**
|
|
119
122
|
* The one line an unreadable device key prints when no credential-key change
|
|
120
|
-
* explains it
|
|
121
|
-
*
|
|
123
|
+
* explains it (OSK-20506).
|
|
124
|
+
*
|
|
125
|
+
* The CLI never enrolls or requires a device key (OSK-12063), so this degrades a
|
|
126
|
+
* proof, not a sign-in: say exactly that. What it must not do is point at
|
|
127
|
+
* `skrr login`. That signs this CLI in on its own and never creates or replaces a
|
|
128
|
+
* device key, so a person who followed it would be told the same thing on the next
|
|
129
|
+
* command. Only an interactive daemon sign-in mints a key and re-pins it, and which
|
|
130
|
+
* one depends on who runs the daemon, so this uses the sentence the hand-off
|
|
131
|
+
* refusals use for the same repair (`daemonSignInRemedy`) rather than a second
|
|
132
|
+
* spelling.
|
|
133
|
+
*
|
|
134
|
+
* `profile` names the command for a non-default profile: a bare `skrr daemon login`
|
|
135
|
+
* signs the default daemon in and leaves this one as it was (OSK-16435).
|
|
122
136
|
*/
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
function deviceKeyRecoveryRequiredNotice(profile = keychain_1.DEFAULT_PROFILE) {
|
|
138
|
+
const loginCommand = (0, daemon_command_hint_1.daemonCommandHint)('login', { profile });
|
|
139
|
+
return ("warning: this machine's stored device key could not be read, so skrr sends requests " +
|
|
140
|
+
`without a device proof. To replace it, ${(0, cli_handoff_wire_1.daemonSignInRemedy)({ repinsDeviceKey: true, loginCommand })}. ` +
|
|
141
|
+
'(DEBUG=1 shows the underlying error.)');
|
|
142
|
+
}
|
|
143
|
+
/** The notice for the default profile. */
|
|
144
|
+
exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = deviceKeyRecoveryRequiredNotice(keychain_1.DEFAULT_PROFILE);
|
|
125
145
|
function createCredentialEnvelopeWarningPresenter() {
|
|
126
146
|
let keyChanged = false;
|
|
147
|
+
// auth-core named a failure as the DEVICE key's (see below): the person has one
|
|
148
|
+
// line to read for it, and it is not the credential line.
|
|
149
|
+
let deviceKeyUnreadable = false;
|
|
127
150
|
return (args) => {
|
|
128
151
|
const [message, metadata] = args;
|
|
129
152
|
const event = typeof message === 'string' && message.startsWith('[credEnvelope] ')
|
|
@@ -157,10 +180,28 @@ function createCredentialEnvelopeWarningPresenter() {
|
|
|
157
180
|
(message === DEVICE_KEY_DECRYPT_FAILED || message === DEVICE_KEY_RECOVERY_REQUIRED)) {
|
|
158
181
|
return '';
|
|
159
182
|
}
|
|
183
|
+
// No key change in THIS process, yet the device key will not open (the
|
|
184
|
+
// credential key changed in an earlier one, or the file is damaged).
|
|
185
|
+
// auth-core's device identity labels that read with its own hint, so the
|
|
186
|
+
// producer says WHAT failed and this decides what a person reads. The
|
|
187
|
+
// generic line would say "signing in again rebuilds it", and `skrr login`
|
|
188
|
+
// does not rebuild a device key. Print nothing for it, and nothing for the
|
|
189
|
+
// device identity's own echo of the same failure: `recovery_required`
|
|
190
|
+
// follows and carries the one line (OSK-20506). The flag is set by that
|
|
191
|
+
// hint alone, so another credential that will not open still gets its own
|
|
192
|
+
// line below.
|
|
193
|
+
if (event === 'cred_envelope.read.decrypt_failed' &&
|
|
194
|
+
meta.hint === auth_core_1.DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT) {
|
|
195
|
+
deviceKeyUnreadable = true;
|
|
196
|
+
return '';
|
|
197
|
+
}
|
|
198
|
+
if (deviceKeyUnreadable && message === DEVICE_KEY_DECRYPT_FAILED)
|
|
199
|
+
return '';
|
|
160
200
|
// Without a key change an unreadable device key is a real fault: one
|
|
161
201
|
// plain line instead of an object dump.
|
|
162
|
-
if (message === DEVICE_KEY_RECOVERY_REQUIRED)
|
|
163
|
-
return
|
|
202
|
+
if (message === DEVICE_KEY_RECOVERY_REQUIRED) {
|
|
203
|
+
return deviceKeyRecoveryRequiredNotice((0, keychain_1.getActiveProfile)());
|
|
204
|
+
}
|
|
164
205
|
return plainCredentialEnvelopeWarning(args);
|
|
165
206
|
};
|
|
166
207
|
}
|
package/dist/lib/auth-failure.js
CHANGED
|
@@ -98,6 +98,18 @@ function describeRefreshRefusal(refusal, opts) {
|
|
|
98
98
|
`server treats that as possible theft and revokes the whole family. ${relogin}`,
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
+
// The server wanted a device proof and this machine had no key to sign one
|
|
102
|
+
// with. `skrr login` signs the CLI in on its own and never creates a device
|
|
103
|
+
// key, so naming it here sends the person round the same loop (OSK-20472): the
|
|
104
|
+
// daemon's interactive sign-in is what mints a key and re-pins the server to it.
|
|
105
|
+
if (code === 'DEVICE_KEY_UNAVAILABLE') {
|
|
106
|
+
return {
|
|
107
|
+
code,
|
|
108
|
+
message: `This sign-in could not be renewed${said}. This machine's device key is missing or ` +
|
|
109
|
+
`unreadable, so requests carry no device proof. \`${bin} login\` cannot create one; ` +
|
|
110
|
+
`sign the background service in again with \`${bin} daemon login\`.`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
101
113
|
return {
|
|
102
114
|
code,
|
|
103
115
|
message: `This sign-in could not be renewed${said}. ${relogin}`,
|
|
@@ -453,6 +453,35 @@ const AGENT_CRITICAL_COMMANDS = [
|
|
|
453
453
|
'agent:message:send',
|
|
454
454
|
'tasks:complete',
|
|
455
455
|
];
|
|
456
|
+
/**
|
|
457
|
+
* The text of a command file, with a pure re-export alias read as its target.
|
|
458
|
+
*
|
|
459
|
+
* `export { default } from '../search/tasks';` is a second spelling of another
|
|
460
|
+
* command: oclif loads the same class, so the alias has the target's flags,
|
|
461
|
+
* description and args. Reading the alias's own few lines reports a missing
|
|
462
|
+
* `--json` and a missing description that the command in fact has, and the only
|
|
463
|
+
* way to silence that would be to copy the description into the alias, where
|
|
464
|
+
* the two spellings could drift apart.
|
|
465
|
+
*
|
|
466
|
+
* One hop, and only when the re-export is the whole module (comments aside), so
|
|
467
|
+
* a file that adds its own code is still audited as itself.
|
|
468
|
+
*/
|
|
469
|
+
function readCommandSource(file) {
|
|
470
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
471
|
+
const code = source
|
|
472
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
473
|
+
.replace(/\/\/.*$/gm, '')
|
|
474
|
+
.trim();
|
|
475
|
+
const alias = code.match(/^export\s*\{\s*default\s*\}\s*from\s*['"](\.{1,2}\/[^'"]+)['"]\s*;?$/);
|
|
476
|
+
if (!alias)
|
|
477
|
+
return source;
|
|
478
|
+
const target = path.resolve(path.dirname(file), alias[1]);
|
|
479
|
+
for (const candidate of [`${target}.ts`, path.join(target, 'index.ts')]) {
|
|
480
|
+
if (fs.existsSync(candidate))
|
|
481
|
+
return fs.readFileSync(candidate, 'utf8');
|
|
482
|
+
}
|
|
483
|
+
return source;
|
|
484
|
+
}
|
|
456
485
|
function auditCommands({ rootDir, manifest, }) {
|
|
457
486
|
const issues = [];
|
|
458
487
|
const commands = manifest?.commands ?? {};
|
|
@@ -512,7 +541,7 @@ function auditCommands({ rootDir, manifest, }) {
|
|
|
512
541
|
sourceCommandFiles = files.length;
|
|
513
542
|
for (const file of files) {
|
|
514
543
|
const rel = path.relative(sourceRoot, file);
|
|
515
|
-
const source =
|
|
544
|
+
const source = readCommandSource(file);
|
|
516
545
|
const command = commandIdFromRelativePath(rel);
|
|
517
546
|
sourceCommandIds.add(command);
|
|
518
547
|
const hasFromJson = /['"]from-json['"]/.test(source);
|
|
@@ -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.`;
|
|
@@ -65,6 +65,15 @@ export declare function normalizeRuntimeGlobalArgs(args: readonly string[]): str
|
|
|
65
65
|
* part of a copy sweep.
|
|
66
66
|
*/
|
|
67
67
|
export declare function notInstalledMessage(): string;
|
|
68
|
+
/**
|
|
69
|
+
* Marks the `skrrd` process that `skrr daemon …` launches, so the daemon can print its recovery
|
|
70
|
+
* commands in the spelling the person can run: `skrr daemon login`, not `skrrd login`. The
|
|
71
|
+
* daemon lives in `~/.skrr/bin`, which this wrapper finds by itself and nothing puts on PATH.
|
|
72
|
+
* Spelled the same in `daemon/src/auth/login-hint.ts`; `scripts/__tests__/invoked-via-parity.spec.js`
|
|
73
|
+
* holds the two together.
|
|
74
|
+
*/
|
|
75
|
+
export declare const INVOKED_VIA_ENV = "SKRR_INVOKED_VIA";
|
|
76
|
+
export declare const INVOKED_VIA_CLI = "skrr-cli";
|
|
68
77
|
/**
|
|
69
78
|
* Spawn `oversky <args>` with stdio inherited. Resolves with the child's
|
|
70
79
|
* exit code so the caller can `process.exit()` with the same code,
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.RUNTIME_BINARY_NAMES = void 0;
|
|
6
|
+
exports.INVOKED_VIA_CLI = exports.INVOKED_VIA_ENV = exports.RUNTIME_BINARY_NAMES = void 0;
|
|
7
7
|
exports.forwardedFlagsNote = forwardedFlagsNote;
|
|
8
8
|
exports.findRuntimeBinary = findRuntimeBinary;
|
|
9
9
|
exports.isCurrentRuntimeBinary = isCurrentRuntimeBinary;
|
|
@@ -206,6 +206,18 @@ function notInstalledMessage() {
|
|
|
206
206
|
'Once installed, re-run your command.',
|
|
207
207
|
].join('\n');
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Marks the `skrrd` process that `skrr daemon …` launches, so the daemon can print its recovery
|
|
211
|
+
* commands in the spelling the person can run: `skrr daemon login`, not `skrrd login`. The
|
|
212
|
+
* daemon lives in `~/.skrr/bin`, which this wrapper finds by itself and nothing puts on PATH.
|
|
213
|
+
* Spelled the same in `daemon/src/auth/login-hint.ts`; `scripts/__tests__/invoked-via-parity.spec.js`
|
|
214
|
+
* holds the two together.
|
|
215
|
+
*/
|
|
216
|
+
exports.INVOKED_VIA_ENV = 'SKRR_INVOKED_VIA';
|
|
217
|
+
exports.INVOKED_VIA_CLI = 'skrr-cli';
|
|
218
|
+
function runtimeEnv() {
|
|
219
|
+
return { ...process.env, [exports.INVOKED_VIA_ENV]: exports.INVOKED_VIA_CLI };
|
|
220
|
+
}
|
|
209
221
|
/**
|
|
210
222
|
* Spawn `oversky <args>` with stdio inherited. Resolves with the child's
|
|
211
223
|
* exit code so the caller can `process.exit()` with the same code,
|
|
@@ -230,6 +242,7 @@ function execRuntimeBinary(args, opts = {}) {
|
|
|
230
242
|
const isWinShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(bin);
|
|
231
243
|
const child = (0, node_child_process_1.spawn)(bin, normalizeRuntimeGlobalArgs(args), {
|
|
232
244
|
stdio: 'inherit',
|
|
245
|
+
env: runtimeEnv(),
|
|
233
246
|
...(isWinShim ? { shell: true } : {}),
|
|
234
247
|
});
|
|
235
248
|
child.on('error', reject);
|
|
@@ -261,6 +274,7 @@ function execRuntimeBinaryCapture(args, opts = {}) {
|
|
|
261
274
|
const isWinShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(bin);
|
|
262
275
|
const child = (0, node_child_process_1.spawn)(bin, normalizeRuntimeGlobalArgs(args), {
|
|
263
276
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
277
|
+
env: runtimeEnv(),
|
|
264
278
|
...(isWinShim ? { shell: true } : {}),
|
|
265
279
|
});
|
|
266
280
|
let stdout = '';
|
package/dist/lib/followups.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface FollowUpRow {
|
|
|
49
49
|
kind?: string;
|
|
50
50
|
form?: string | null;
|
|
51
51
|
producer?: string;
|
|
52
|
+
/** The producer in words (`describeFollowUpProducer`), so no surface prints a bare enum. */
|
|
53
|
+
producerLabel?: string;
|
|
52
54
|
why?: string;
|
|
53
55
|
/** A reminder's own words. */
|
|
54
56
|
content?: string | null;
|
|
@@ -86,7 +88,18 @@ export interface FollowUpCreateResponse {
|
|
|
86
88
|
proposal?: boolean;
|
|
87
89
|
budget?: Json;
|
|
88
90
|
fallbackTriggerId?: string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Set when the credential could not carry the producer the request claimed: the
|
|
93
|
+
* server recorded `recorded` instead (never a higher one) and says why.
|
|
94
|
+
*/
|
|
95
|
+
producerDowngraded?: {
|
|
96
|
+
requested: string;
|
|
97
|
+
recorded: string;
|
|
98
|
+
note: string;
|
|
99
|
+
};
|
|
89
100
|
}
|
|
101
|
+
/** One line telling a person the follow-up was recorded with weaker provenance than claimed. */
|
|
102
|
+
export declare function producerDowngradeNotice(response: FollowUpCreateResponse): string | undefined;
|
|
90
103
|
export interface FollowUpObservability {
|
|
91
104
|
generatedAt?: string;
|
|
92
105
|
agents?: Array<{
|
package/dist/lib/followups.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.followupsApi = exports.FOLLOWUP_SUBJECT_FLAG_HELP = exports.FOLLOWUP_SUB
|
|
|
4
4
|
exports.whenFlagProblem = whenFlagProblem;
|
|
5
5
|
exports.settledLine = settledLine;
|
|
6
6
|
exports.recipeWatchBoundsRefusal = recipeWatchBoundsRefusal;
|
|
7
|
+
exports.producerDowngradeNotice = producerDowngradeNotice;
|
|
7
8
|
exports.resolveFollowUpAgent = resolveFollowUpAgent;
|
|
8
9
|
exports.parseSubjectRef = parseSubjectRef;
|
|
9
10
|
exports.followupSummaryLine = followupSummaryLine;
|
|
@@ -127,6 +128,13 @@ function recipeWatchBoundsRefusal(waitKind, passed) {
|
|
|
127
128
|
: 'A recipe watch ends through its --wait.',
|
|
128
129
|
].join(' ');
|
|
129
130
|
}
|
|
131
|
+
/** One line telling a person the follow-up was recorded with weaker provenance than claimed. */
|
|
132
|
+
function producerDowngradeNotice(response) {
|
|
133
|
+
const downgraded = response.producerDowngraded;
|
|
134
|
+
if (!downgraded)
|
|
135
|
+
return undefined;
|
|
136
|
+
return `Note: recorded as ${downgraded.recorded}, not ${downgraded.requested}. ${downgraded.note}`;
|
|
137
|
+
}
|
|
130
138
|
/**
|
|
131
139
|
* A trigger subject becomes a durable concurrency key, so it cannot be prose.
|
|
132
140
|
* Keep this grammar aligned with TriggerEngine's subject validation and the
|
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`.
|
|
@@ -5,6 +5,7 @@ exports.describeLegacySharedProfile = describeLegacySharedProfile;
|
|
|
5
5
|
const machine_uuid_1 = require("@skrr-ai/auth-core/machine-uuid");
|
|
6
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
7
7
|
const keychain_1 = require("./keychain");
|
|
8
|
+
const daemon_command_hint_1 = require("./daemon-command-hint");
|
|
8
9
|
/** An old profile may still share the default machine row and its device pin. */
|
|
9
10
|
function legacyProfileSharesDefaultMachine() {
|
|
10
11
|
const profile = (0, keychain_1.getActiveProfile)();
|
|
@@ -47,6 +48,6 @@ function describeLegacySharedProfile(profile) {
|
|
|
47
48
|
`computer the same machine identity, and it still has it. ${why}Signing in here would replace the default ` +
|
|
48
49
|
"profile's device-key pin and sign the default daemon out. Nothing was changed.",
|
|
49
50
|
`Use a new profile name instead: skrr login --profile ${next}`,
|
|
50
|
-
`(For the background service alone:
|
|
51
|
+
`(For the background service alone: ${(0, daemon_command_hint_1.daemonCommandHint)('login', { profile: next })})`,
|
|
51
52
|
];
|
|
52
53
|
}
|
|
@@ -49,7 +49,12 @@ function parseRuntimeStartStatus(value, fallbackProfile) {
|
|
|
49
49
|
return {
|
|
50
50
|
profile: asString(profile?.name) ?? fallbackProfile,
|
|
51
51
|
serverUrl: asString(root.serverUrl),
|
|
52
|
-
|
|
52
|
+
// `root.cwd` is where THIS `status` ran from (the person's shell), not where
|
|
53
|
+
// the daemon runs. The recovery command binds a login to its `--cwd`, and
|
|
54
|
+
// `skrrd status` itself prints `effectiveCwd` (live daemon, then installed
|
|
55
|
+
// service, then the stored binding) for exactly that. A runtime binary that
|
|
56
|
+
// predates `effectiveCwd` only has `cwd`.
|
|
57
|
+
cwd: asString(root.effectiveCwd) ?? asString(root.cwd),
|
|
53
58
|
reauth: {
|
|
54
59
|
needed: reauth?.needed === true,
|
|
55
60
|
reason: asString(reauth?.reason),
|
|
@@ -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"
|
|
@@ -242,6 +223,8 @@ async function loadRetiredDeks(profile, kek, aad, active) {
|
|
|
242
223
|
dek.fill(0);
|
|
243
224
|
}
|
|
244
225
|
} catch {
|
|
226
|
+
if (import_node_path.default.basename(target).startsWith(UNREADABLE_DEK_COPY_PREFIX))
|
|
227
|
+
continue;
|
|
245
228
|
emit("cred_envelope.retired_dek.unreadable", false, {
|
|
246
229
|
profile,
|
|
247
230
|
file: import_node_path.default.basename(target)
|
|
@@ -249,6 +232,41 @@ async function loadRetiredDeks(profile, kek, aad, active) {
|
|
|
249
232
|
}
|
|
250
233
|
}
|
|
251
234
|
}
|
|
235
|
+
const UNREADABLE_DEK_COPY_PREFIX = `${ACCOUNT_CRED_DEK_WRAPPED}${import_profileStateDir.RETIRED_COPY_SUFFIX}.unreadable-`;
|
|
236
|
+
const MAX_UNREADABLE_DEK_COPIES = 8;
|
|
237
|
+
function isKekUnavailableError(err) {
|
|
238
|
+
return err instanceof import_kek.KekUnavailableError || err?.name === "KekUnavailableError";
|
|
239
|
+
}
|
|
240
|
+
function keepUnreadableWrappedDek(profile, wrapped) {
|
|
241
|
+
try {
|
|
242
|
+
const dir = (0, import_profileStateDir.profileStateDir)(profile);
|
|
243
|
+
const existing = import_node_fs.default.existsSync(dir) ? import_node_fs.default.readdirSync(dir).filter((entry) => entry.startsWith(UNREADABLE_DEK_COPY_PREFIX)).sort() : [];
|
|
244
|
+
for (const entry of existing) {
|
|
245
|
+
try {
|
|
246
|
+
if (import_node_fs.default.readFileSync(import_node_path.default.join(dir, entry)).equals(wrapped))
|
|
247
|
+
return entry;
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const name = `${UNREADABLE_DEK_COPY_PREFIX}${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
252
|
+
(0, import_stateFiles.atomicWrite0600)(import_node_path.default.join(dir, name), wrapped, "keep the unreadable wrapped DEK");
|
|
253
|
+
const kept = [...existing, name].sort();
|
|
254
|
+
for (const stale of kept.slice(0, Math.max(0, kept.length - MAX_UNREADABLE_DEK_COPIES))) {
|
|
255
|
+
try {
|
|
256
|
+
(0, import_stateFiles.removeStateFile)(import_node_path.default.join(dir, stale), "prune a kept wrapped DEK");
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return name;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
263
|
+
emit("cred_envelope.unreadable_dek_copy_failed", false, {
|
|
264
|
+
profile,
|
|
265
|
+
message: err?.message ?? "unknown"
|
|
266
|
+
});
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
252
270
|
function retiredDekFiles(profile) {
|
|
253
271
|
const dir = (0, import_profileStateDir.profileStateDir)(profile);
|
|
254
272
|
const prefix = `${ACCOUNT_CRED_DEK_WRAPPED}${import_profileStateDir.RETIRED_COPY_SUFFIX}.`;
|
|
@@ -359,6 +377,7 @@ async function initNewIndependentCredEnvelope(profile) {
|
|
|
359
377
|
const dek = (0, import_credentialEnvelope.generateDek)();
|
|
360
378
|
let temp;
|
|
361
379
|
try {
|
|
380
|
+
(0, import_liveState.assertMayMutateLiveState)(dir, `enroll the wrapped DEK for background profile ${profile}`);
|
|
362
381
|
const wrapped = await kek.wrap(dek, (0, import_credentialEnvelope.buildCredentialAad)(profile));
|
|
363
382
|
if (paths.some(present))
|
|
364
383
|
return await initExistingCredEnvelope(profile);
|
|
@@ -466,6 +485,8 @@ async function initCredEnvelope(profile) {
|
|
|
466
485
|
}
|
|
467
486
|
let dek = null;
|
|
468
487
|
let unwrapAttemptedAndFailed = false;
|
|
488
|
+
let kekUnreadable = null;
|
|
489
|
+
let unreadableWrapped = null;
|
|
469
490
|
await withCredEnvelopeLock(profile, async () => {
|
|
470
491
|
let wrappedBytes = null;
|
|
471
492
|
try {
|
|
@@ -489,6 +510,7 @@ async function initCredEnvelope(profile) {
|
|
|
489
510
|
kekKind: captureKind()
|
|
490
511
|
});
|
|
491
512
|
unwrapAttemptedAndFailed = true;
|
|
513
|
+
unreadableWrapped = wrappedBytes;
|
|
492
514
|
} else {
|
|
493
515
|
dek = candidate;
|
|
494
516
|
}
|
|
@@ -499,15 +521,33 @@ async function initCredEnvelope(profile) {
|
|
|
499
521
|
kekKind: captureKind(),
|
|
500
522
|
message: err?.message ?? "unknown"
|
|
501
523
|
});
|
|
502
|
-
|
|
524
|
+
if (isKekUnavailableError(err)) {
|
|
525
|
+
kekUnreadable = err;
|
|
526
|
+
} else {
|
|
527
|
+
unwrapAttemptedAndFailed = true;
|
|
528
|
+
unreadableWrapped = wrappedBytes;
|
|
529
|
+
}
|
|
503
530
|
}
|
|
504
531
|
}
|
|
532
|
+
if (kekUnreadable !== null) {
|
|
533
|
+
_state = { kind: "disabled", reason: "kek_unavailable" };
|
|
534
|
+
emit("cred_envelope.kek_unavailable", false, {
|
|
535
|
+
kekId: kek.id,
|
|
536
|
+
profile,
|
|
537
|
+
phase: "unwrap",
|
|
538
|
+
describe: kek.describe(),
|
|
539
|
+
message: kekUnreadable?.message ?? "unknown"
|
|
540
|
+
});
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
505
543
|
if (unwrapAttemptedAndFailed && dek === null) {
|
|
506
544
|
_kekRotationDetected = true;
|
|
545
|
+
const keptAs = unreadableWrapped ? keepUnreadableWrappedDek(profile, unreadableWrapped) : null;
|
|
507
546
|
emit("cred_envelope.kek_rotated", false, {
|
|
508
547
|
kekId: kek.id,
|
|
509
548
|
kekKind: captureKind(),
|
|
510
549
|
profile,
|
|
550
|
+
...keptAs ? { previousWrappedDekKeptAs: keptAs } : {},
|
|
511
551
|
message: "wrapped DEK present but unwrap failed under available KEK; treating as rotation and re-enrolling"
|
|
512
552
|
});
|
|
513
553
|
}
|
|
@@ -515,7 +555,7 @@ async function initCredEnvelope(profile) {
|
|
|
515
555
|
try {
|
|
516
556
|
dek = (0, import_credentialEnvelope.generateDek)();
|
|
517
557
|
const wrapped = await kek.wrap(dek, aad);
|
|
518
|
-
atomicWrite0600(targetPath, wrapped);
|
|
558
|
+
(0, import_stateFiles.atomicWrite0600)(targetPath, wrapped, `write the wrapped DEK for profile ${profile}`);
|
|
519
559
|
emit("cred_envelope.dek_enrolled", true, {
|
|
520
560
|
kekId: kek.id,
|
|
521
561
|
kekKind: captureKind(),
|
|
@@ -523,6 +563,7 @@ async function initCredEnvelope(profile) {
|
|
|
523
563
|
...unwrapAttemptedAndFailed ? { reEnrolledAfterRotation: true } : {}
|
|
524
564
|
});
|
|
525
565
|
} catch (err) {
|
|
566
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
526
567
|
_state = { kind: "disabled", reason: "init_failed" };
|
|
527
568
|
emit("cred_envelope.init_failed", false, {
|
|
528
569
|
reason: "enrollment_failed",
|
|
@@ -556,8 +597,9 @@ async function resetCredEnvelope(opts) {
|
|
|
556
597
|
...retiredDekFiles(opts.profile)
|
|
557
598
|
]) {
|
|
558
599
|
try {
|
|
559
|
-
|
|
600
|
+
(0, import_stateFiles.removeStateFile)(target, `delete the wrapped DEK file ${import_node_path.default.basename(target)}`);
|
|
560
601
|
} catch (err) {
|
|
602
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
561
603
|
if (err.code !== "ENOENT") {
|
|
562
604
|
emit("cred_envelope.clear_failed", false, {
|
|
563
605
|
profile: opts.profile,
|