@wlfi-agent/cli 1.4.17 → 1.4.19

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.
Files changed (93) hide show
  1. package/Cargo.lock +5 -0
  2. package/README.md +61 -28
  3. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  4. package/crates/vault-cli-admin/src/main.rs +639 -16
  5. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  6. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  7. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  8. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  9. package/crates/vault-cli-agent/Cargo.toml +1 -0
  10. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  11. package/crates/vault-cli-agent/src/main.rs +648 -32
  12. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  13. package/crates/vault-cli-daemon/src/main.rs +617 -67
  14. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  15. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  16. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  17. package/crates/vault-daemon/src/persistence.rs +637 -100
  18. package/crates/vault-daemon/src/tests.rs +1013 -3
  19. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  20. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  21. package/crates/vault-domain/src/nonce.rs +4 -0
  22. package/crates/vault-domain/src/tests.rs +616 -0
  23. package/crates/vault-policy/src/engine.rs +55 -32
  24. package/crates/vault-policy/src/tests.rs +195 -0
  25. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  26. package/crates/vault-signer/Cargo.toml +3 -0
  27. package/crates/vault-signer/src/lib.rs +266 -40
  28. package/crates/vault-transport-unix/src/lib.rs +653 -5
  29. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  30. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  31. package/dist/cli.cjs +756 -194
  32. package/dist/cli.cjs.map +1 -1
  33. package/package.json +5 -2
  34. package/packages/cache/.turbo/turbo-build.log +20 -20
  35. package/packages/cache/coverage/clover.xml +529 -394
  36. package/packages/cache/coverage/coverage-final.json +2 -2
  37. package/packages/cache/coverage/index.html +21 -21
  38. package/packages/cache/coverage/src/client/index.html +1 -1
  39. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  40. package/packages/cache/coverage/src/errors/index.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  42. package/packages/cache/coverage/src/index.html +1 -1
  43. package/packages/cache/coverage/src/index.ts.html +1 -1
  44. package/packages/cache/coverage/src/service/index.html +21 -21
  45. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  46. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  47. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  48. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  49. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  50. package/packages/cache/dist/index.cjs +2 -2
  51. package/packages/cache/dist/index.js +1 -1
  52. package/packages/cache/dist/service/index.cjs +2 -2
  53. package/packages/cache/dist/service/index.js +1 -1
  54. package/packages/cache/node_modules/.bin/tsc +2 -2
  55. package/packages/cache/node_modules/.bin/tsserver +2 -2
  56. package/packages/cache/node_modules/.bin/tsup +2 -2
  57. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  58. package/packages/cache/node_modules/.bin/vitest +4 -4
  59. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  60. package/packages/cache/src/service/index.test.ts +165 -19
  61. package/packages/cache/src/service/index.ts +38 -1
  62. package/packages/config/.turbo/turbo-build.log +4 -4
  63. package/packages/config/dist/index.cjs +0 -17
  64. package/packages/config/dist/index.cjs.map +1 -1
  65. package/packages/config/src/index.ts +0 -17
  66. package/packages/rpc/.turbo/turbo-build.log +11 -11
  67. package/packages/rpc/dist/index.cjs +0 -17
  68. package/packages/rpc/dist/index.cjs.map +1 -1
  69. package/packages/rpc/src/index.js +1 -0
  70. package/packages/ui/node_modules/.bin/tsc +2 -2
  71. package/packages/ui/node_modules/.bin/tsserver +2 -2
  72. package/packages/ui/node_modules/.bin/tsup +2 -2
  73. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  74. package/scripts/install-cli-launcher.mjs +37 -0
  75. package/scripts/install-rust-binaries.mjs +47 -0
  76. package/scripts/run-tests-isolated.mjs +210 -0
  77. package/src/cli.ts +310 -50
  78. package/src/lib/admin-reset.ts +101 -33
  79. package/src/lib/admin-setup.ts +285 -55
  80. package/src/lib/agent-auth-migrate.ts +5 -1
  81. package/src/lib/asset-broadcast.ts +15 -4
  82. package/src/lib/config-amounts.ts +6 -4
  83. package/src/lib/hidden-tty-prompt.js +1 -0
  84. package/src/lib/hidden-tty-prompt.ts +105 -0
  85. package/src/lib/keychain.ts +1 -0
  86. package/src/lib/local-admin-access.ts +4 -29
  87. package/src/lib/rust.ts +129 -33
  88. package/src/lib/signed-tx.ts +1 -0
  89. package/src/lib/sudo.ts +15 -5
  90. package/src/lib/wallet-profile.ts +3 -0
  91. package/src/lib/wallet-setup.ts +52 -0
  92. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  93. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -24,6 +24,7 @@ import {
24
24
  LAUNCHD_UNINSTALL_SCRIPT_NAME,
25
25
  resolveLaunchDaemonHelperScriptPath,
26
26
  } from './launchd-assets.js';
27
+ import { promptHiddenTty } from './hidden-tty-prompt.js';
27
28
  import { createSudoSession } from './sudo.js';
28
29
 
29
30
  const DEFAULT_LAUNCH_DAEMON_LABEL = 'com.wlfi.agent.daemon';
@@ -134,32 +135,7 @@ function validateSecret(value: string, label: string): string {
134
135
  }
135
136
 
136
137
  async function promptHidden(query: string, label: string): Promise<string> {
137
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
138
- throw new Error(`${label} is required; rerun on a local TTY`);
139
- }
140
-
141
- const rl = readline.createInterface({
142
- input: process.stdin,
143
- output: process.stdout,
144
- terminal: true,
145
- }) as readline.Interface & { stdoutMuted?: boolean; _writeToOutput?: (value: string) => void };
146
-
147
- rl.stdoutMuted = true;
148
- rl._writeToOutput = (value: string) => {
149
- if (value.includes(query)) {
150
- (rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
151
- return;
152
- }
153
- if (!rl.stdoutMuted) {
154
- (rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
155
- }
156
- };
157
-
158
- const answer = await new Promise<string>((resolve) => {
159
- rl.question(query, resolve);
160
- });
161
- rl.close();
162
- process.stdout.write('\n');
138
+ const answer = await promptHiddenTty(query, `${label} is required; rerun on a local TTY`);
163
139
  return validateSecret(answer, label);
164
140
  }
165
141
 
@@ -184,11 +160,29 @@ async function promptVisible(query: string): Promise<string> {
184
160
  const sudoSession = createSudoSession({
185
161
  promptPassword: async () =>
186
162
  await promptHidden(
187
- 'Root password (input hidden; required to uninstall the root daemon and delete its state): ',
188
- 'root password',
163
+ 'macOS admin password for sudo (input hidden; required to uninstall the root daemon and delete its state): ',
164
+ 'macOS admin password for sudo',
189
165
  ),
190
166
  });
191
167
 
168
+ function isSudoWrappedInvocation(): boolean {
169
+ return (
170
+ typeof process.geteuid === 'function' &&
171
+ process.geteuid() === 0 &&
172
+ typeof process.env.SUDO_UID === 'string' &&
173
+ process.env.SUDO_UID.trim().length > 0
174
+ );
175
+ }
176
+
177
+ function assertNotInvokedViaSudo(commandName: string): void {
178
+ if (!isSudoWrappedInvocation()) {
179
+ return;
180
+ }
181
+ throw new Error(
182
+ `run \`wlfi-agent ${commandName}\` as your normal macOS user, not with sudo; the CLI prompts for sudo internally and running it as root can target the wrong local WLFI home`,
183
+ );
184
+ }
185
+
192
186
  function createProgress(message: string, enabled = true): ProgressHandle {
193
187
  if (!enabled) {
194
188
  return {
@@ -241,11 +235,58 @@ function print(payload: unknown, asJson: boolean | undefined): void {
241
235
  console.log(JSON.stringify(payload, null, 2));
242
236
  return;
243
237
  }
238
+ /* c8 ignore start -- this module only calls print() for JSON output; non-JSON summaries bypass this helper */
244
239
  if (typeof payload === 'string') {
245
240
  console.log(payload);
246
241
  return;
247
242
  }
248
243
  console.dir(payload, { depth: null, colors: process.stdout.isTTY });
244
+ /* c8 ignore stop */
245
+ }
246
+
247
+ async function managedPathExists(targetPath: string): Promise<boolean> {
248
+ const result = await sudoSession.run(['/bin/test', '-e', targetPath]);
249
+ if (result.code === 0) {
250
+ return true;
251
+ }
252
+ if (
253
+ result.code === 1 &&
254
+ !/password is required|try again|authentication failed|sorry/iu.test(result.stderr)
255
+ ) {
256
+ return false;
257
+ }
258
+ throw new Error(
259
+ result.stderr.trim() ||
260
+ result.stdout.trim() ||
261
+ `failed to inspect managed path '${targetPath}' (exit code ${result.code})`,
262
+ );
263
+ }
264
+
265
+ async function assertManagedUninstallArtifactsRemoved(targetPaths: string[]): Promise<void> {
266
+ const remaining: string[] = [];
267
+ for (const targetPath of targetPaths) {
268
+ if (await managedPathExists(targetPath)) {
269
+ remaining.push(targetPath);
270
+ }
271
+ }
272
+ if (remaining.length > 0) {
273
+ throw new Error(
274
+ `admin uninstall left managed root-owned files behind: ${remaining.join(', ')}`,
275
+ );
276
+ }
277
+ }
278
+
279
+ function assertLocalUninstallArtifactsRemoved(result: CleanupLocalAdminUninstallStateResult): void {
280
+ const remaining: string[] = [];
281
+ if (result.config.existed && fs.existsSync(result.config.path)) {
282
+ remaining.push(result.config.path);
283
+ }
284
+ if (result.wlfiHome.existed && fs.existsSync(result.wlfiHome.path)) {
285
+ remaining.push(result.wlfiHome.path);
286
+ }
287
+ if (remaining.length > 0) {
288
+ throw new Error(`admin uninstall left local WLFI files behind: ${remaining.join(', ')}`);
289
+ }
249
290
  }
250
291
 
251
292
  function printResetSummary(result: {
@@ -332,6 +373,7 @@ export function cleanupLocalAdminResetState(
332
373
  const keychainRemoved = agentKeyId ? deleteAgentAuthTokenImpl(agentKeyId) : false;
333
374
 
334
375
  let configDeleted = false;
376
+ /* c8 ignore next -- configExists implies readConfigImpl() returned an object in this module; nullish fallback is defensive */
335
377
  let configValue: Record<string, unknown> | null = configExists ? redactConfig(currentConfig ?? {}) : null;
336
378
  if (options.deleteConfig) {
337
379
  if (configExists) {
@@ -477,13 +519,14 @@ async function confirmReset(options: AdminResetOptions): Promise<void> {
477
519
  }
478
520
 
479
521
  async function runAdminReset(options: AdminResetOptions): Promise<void> {
522
+ assertNotInvokedViaSudo('admin reset');
480
523
  await confirmReset(options);
481
524
  const showProgress = !options.json;
482
525
  const keychainAccount = os.userInfo().username;
483
526
 
484
527
  if (!options.json && typeof process.geteuid === 'function' && process.geteuid() !== 0) {
485
528
  process.stderr.write(
486
- 'Root password required: reset must uninstall the root LaunchDaemon and delete the root-managed daemon state.\n',
529
+ 'macOS admin password required: reset uses sudo to uninstall the root LaunchDaemon and delete the root-managed daemon state.\n',
487
530
  );
488
531
  }
489
532
  await sudoSession.prime();
@@ -502,6 +545,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
502
545
  keychainAccount,
503
546
  '--delete-keychain-password',
504
547
  ]);
548
+ /* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
505
549
  } catch (error) {
506
550
  uninstallProgress.fail();
507
551
  throw error;
@@ -524,6 +568,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
524
568
  '-f',
525
569
  ...managedDaemonResetArtifactPaths(),
526
570
  ]);
571
+ /* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
527
572
  } catch (error) {
528
573
  stateProgress.fail();
529
574
  throw error;
@@ -612,9 +657,11 @@ function printUninstallSummary(result: {
612
657
  result.local.agentKeyId
613
658
  ? `old agent key cleared: ${result.local.agentKeyId}`
614
659
  : 'old agent key cleared: no configured agent key was found',
660
+ /* c8 ignore start -- public uninstall summary only reaches here when a WLFI home existed or config provided the helper paths */
615
661
  result.local.wlfiHome.existed
616
662
  ? `local WLFI home removed: ${result.local.wlfiHome.path}`
617
663
  : `local WLFI home not found: ${result.local.wlfiHome.path}`,
664
+ /* c8 ignore stop */
618
665
  result.local.config.existed
619
666
  ? `config removed: ${result.local.config.path}`
620
667
  : `config not found: ${result.local.config.path}`,
@@ -624,13 +671,14 @@ function printUninstallSummary(result: {
624
671
  }
625
672
 
626
673
  async function runAdminUninstall(options: AdminUninstallOptions): Promise<void> {
674
+ assertNotInvokedViaSudo('admin uninstall');
627
675
  await confirmUninstall(options);
628
676
  const showProgress = !options.json;
629
677
  const keychainAccount = os.userInfo().username;
630
678
 
631
679
  if (!options.json && typeof process.geteuid === 'function' && process.geteuid() !== 0) {
632
680
  process.stderr.write(
633
- 'Root password required: uninstall must remove the root LaunchDaemon and all managed root-owned files.\n',
681
+ 'macOS admin password required: uninstall uses sudo to remove the root LaunchDaemon and all managed root-owned files.\n',
634
682
  );
635
683
  }
636
684
  await sudoSession.prime();
@@ -649,6 +697,7 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
649
697
  keychainAccount,
650
698
  '--delete-keychain-password',
651
699
  ]);
700
+ /* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
652
701
  } catch (error) {
653
702
  uninstallProgress.fail();
654
703
  throw error;
@@ -673,6 +722,7 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
673
722
  DEFAULT_MANAGED_STATE_DIR,
674
723
  DEFAULT_MANAGED_LOG_DIR,
675
724
  ]);
725
+ /* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
676
726
  } catch (error) {
677
727
  rootProgress.fail();
678
728
  throw error;
@@ -685,11 +735,29 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
685
735
  || `failed to delete managed root-owned files (exit code ${deleteRootArtifactsResult.code})`,
686
736
  );
687
737
  }
688
- rootProgress.succeed('Managed root-owned files removed');
738
+ try {
739
+ await assertManagedUninstallArtifactsRemoved([
740
+ DEFAULT_LAUNCH_DAEMON_PLIST,
741
+ DEFAULT_MANAGED_ROOT_DIR,
742
+ DEFAULT_MANAGED_STATE_DIR,
743
+ DEFAULT_MANAGED_LOG_DIR,
744
+ ]);
745
+ rootProgress.succeed('Managed root-owned files removed');
746
+ } catch (error) {
747
+ rootProgress.fail();
748
+ throw error;
749
+ }
689
750
 
690
751
  const localProgress = createProgress('Removing local WLFI files and credentials', showProgress);
691
- const local = cleanupLocalAdminUninstallState();
692
- localProgress.succeed('Local WLFI files and credentials removed');
752
+ let local: CleanupLocalAdminUninstallStateResult;
753
+ try {
754
+ local = cleanupLocalAdminUninstallState();
755
+ assertLocalUninstallArtifactsRemoved(local);
756
+ localProgress.succeed('Local WLFI files and credentials removed');
757
+ } catch (error) {
758
+ localProgress.fail();
759
+ throw error;
760
+ }
693
761
 
694
762
  const result = {
695
763
  command: 'uninstall',