@wlfi-agent/cli 1.4.13 → 1.4.14

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 (289) hide show
  1. package/Cargo.lock +3968 -0
  2. package/Cargo.toml +50 -0
  3. package/README.md +426 -6
  4. package/crates/vault-cli-admin/Cargo.toml +26 -0
  5. package/crates/vault-cli-admin/src/io_utils.rs +500 -0
  6. package/crates/vault-cli-admin/src/main.rs +3990 -0
  7. package/crates/vault-cli-admin/src/shared_config.rs +624 -0
  8. package/crates/vault-cli-admin/src/tui/amounts.rs +180 -0
  9. package/crates/vault-cli-admin/src/tui/token_rpc.rs +250 -0
  10. package/crates/vault-cli-admin/src/tui/utils.rs +82 -0
  11. package/crates/vault-cli-admin/src/tui.rs +3410 -0
  12. package/crates/vault-cli-agent/Cargo.toml +24 -0
  13. package/crates/vault-cli-agent/src/io_utils.rs +576 -0
  14. package/crates/vault-cli-agent/src/main.rs +833 -0
  15. package/crates/vault-cli-daemon/Cargo.toml +28 -0
  16. package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +216 -0
  17. package/crates/vault-cli-daemon/src/main.rs +644 -0
  18. package/crates/vault-cli-daemon/src/relay_sync.rs +894 -0
  19. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +167 -0
  20. package/crates/vault-daemon/Cargo.toml +32 -0
  21. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +1041 -0
  22. package/crates/vault-daemon/src/daemon_parts/core_helpers.rs +1256 -0
  23. package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +622 -0
  24. package/crates/vault-daemon/src/lib.rs +54 -0
  25. package/crates/vault-daemon/src/persistence.rs +441 -0
  26. package/crates/vault-daemon/src/tests.rs +237 -0
  27. package/crates/vault-daemon/src/tests_parts/part1.rs +1224 -0
  28. package/crates/vault-daemon/src/tests_parts/part2.rs +1021 -0
  29. package/crates/vault-daemon/src/tests_parts/part3.rs +835 -0
  30. package/crates/vault-daemon/src/tests_parts/part4.rs +604 -0
  31. package/crates/vault-domain/Cargo.toml +20 -0
  32. package/crates/vault-domain/src/action.rs +849 -0
  33. package/crates/vault-domain/src/address.rs +51 -0
  34. package/crates/vault-domain/src/approval.rs +90 -0
  35. package/crates/vault-domain/src/constants.rs +4 -0
  36. package/crates/vault-domain/src/error.rs +54 -0
  37. package/crates/vault-domain/src/keys.rs +71 -0
  38. package/crates/vault-domain/src/lib.rs +42 -0
  39. package/crates/vault-domain/src/nonce.rs +102 -0
  40. package/crates/vault-domain/src/policy.rs +172 -0
  41. package/crates/vault-domain/src/request.rs +53 -0
  42. package/crates/vault-domain/src/scope.rs +24 -0
  43. package/crates/vault-domain/src/session.rs +50 -0
  44. package/crates/vault-domain/src/signature.rs +34 -0
  45. package/crates/vault-domain/src/tests.rs +651 -0
  46. package/crates/vault-domain/src/u128_as_decimal_string.rs +44 -0
  47. package/crates/vault-policy/Cargo.toml +17 -0
  48. package/crates/vault-policy/src/engine.rs +301 -0
  49. package/crates/vault-policy/src/error.rs +81 -0
  50. package/crates/vault-policy/src/lib.rs +17 -0
  51. package/crates/vault-policy/src/report.rs +34 -0
  52. package/crates/vault-policy/src/tests.rs +891 -0
  53. package/crates/vault-policy/src/tests_explain.rs +78 -0
  54. package/crates/vault-sdk-agent/Cargo.toml +21 -0
  55. package/crates/vault-sdk-agent/src/lib.rs +711 -0
  56. package/crates/vault-signer/Cargo.toml +25 -0
  57. package/crates/vault-signer/src/lib.rs +731 -0
  58. package/crates/vault-signer/tests/secure_enclave_acl.rs +54 -0
  59. package/crates/vault-transport-unix/Cargo.toml +24 -0
  60. package/crates/vault-transport-unix/src/lib.rs +1640 -0
  61. package/crates/vault-transport-xpc/Cargo.toml +25 -0
  62. package/crates/vault-transport-xpc/src/client_codec_api.rs +635 -0
  63. package/crates/vault-transport-xpc/src/lib.rs +680 -0
  64. package/crates/vault-transport-xpc/src/tests.rs +818 -0
  65. package/crates/vault-transport-xpc/tests/e2e_flow.rs +773 -0
  66. package/dist/cli.cjs +35088 -0
  67. package/dist/cli.cjs.map +1 -0
  68. package/package.json +49 -43
  69. package/packages/cache/.turbo/turbo-build.log +52 -0
  70. package/packages/cache/dist/chunk-2QFWMUXT.cjs +43 -0
  71. package/packages/cache/dist/chunk-2QFWMUXT.cjs.map +1 -0
  72. package/packages/cache/dist/chunk-4U63TZTQ.js +43 -0
  73. package/packages/cache/dist/chunk-4U63TZTQ.js.map +1 -0
  74. package/packages/cache/dist/chunk-ALQ6H7KG.cjs +404 -0
  75. package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +1 -0
  76. package/packages/cache/dist/chunk-FGJEEF5N.js +404 -0
  77. package/packages/cache/dist/chunk-FGJEEF5N.js.map +1 -0
  78. package/packages/cache/dist/chunk-UYNEHZHB.cjs +45 -0
  79. package/packages/cache/dist/chunk-UYNEHZHB.cjs.map +1 -0
  80. package/packages/cache/dist/chunk-VXVMPG3W.js +45 -0
  81. package/packages/cache/dist/chunk-VXVMPG3W.js.map +1 -0
  82. package/packages/cache/dist/client/index.cjs +11 -0
  83. package/packages/cache/dist/client/index.cjs.map +1 -0
  84. package/packages/cache/dist/client/index.d.cts +15 -0
  85. package/packages/cache/dist/client/index.d.ts +15 -0
  86. package/packages/cache/dist/client/index.js +11 -0
  87. package/packages/cache/dist/client/index.js.map +1 -0
  88. package/packages/cache/dist/errors/index.cjs +11 -0
  89. package/packages/cache/dist/errors/index.cjs.map +1 -0
  90. package/packages/cache/dist/errors/index.d.cts +26 -0
  91. package/packages/cache/dist/errors/index.d.ts +26 -0
  92. package/packages/cache/dist/errors/index.js +11 -0
  93. package/packages/cache/dist/errors/index.js.map +1 -0
  94. package/packages/cache/dist/index.cjs +29 -0
  95. package/packages/cache/dist/index.cjs.map +1 -0
  96. package/packages/cache/dist/index.d.cts +4 -0
  97. package/packages/cache/dist/index.d.ts +4 -0
  98. package/packages/cache/dist/index.js +29 -0
  99. package/packages/cache/dist/index.js.map +1 -0
  100. package/packages/cache/dist/service/index.cjs +15 -0
  101. package/packages/cache/dist/service/index.cjs.map +1 -0
  102. package/packages/cache/dist/service/index.d.cts +184 -0
  103. package/packages/cache/dist/service/index.d.ts +184 -0
  104. package/packages/cache/dist/service/index.js +15 -0
  105. package/packages/cache/dist/service/index.js.map +1 -0
  106. package/packages/cache/node_modules/.bin/jiti +17 -0
  107. package/packages/cache/node_modules/.bin/tsc +17 -0
  108. package/packages/cache/node_modules/.bin/tsserver +17 -0
  109. package/packages/cache/node_modules/.bin/tsup +17 -0
  110. package/packages/cache/node_modules/.bin/tsup-node +17 -0
  111. package/packages/cache/node_modules/.bin/tsx +17 -0
  112. package/packages/cache/node_modules/.bin/vitest +17 -0
  113. package/packages/cache/package.json +48 -0
  114. package/packages/cache/src/client/index.ts +56 -0
  115. package/packages/cache/src/errors/index.ts +53 -0
  116. package/packages/cache/src/index.ts +3 -0
  117. package/packages/cache/src/service/index.test.ts +263 -0
  118. package/packages/cache/src/service/index.ts +678 -0
  119. package/packages/cache/tsconfig.json +13 -0
  120. package/packages/cache/tsup.config.ts +13 -0
  121. package/packages/cache/vitest.config.ts +16 -0
  122. package/packages/config/.turbo/turbo-build.log +18 -0
  123. package/packages/config/dist/index.cjs +1037 -0
  124. package/packages/config/dist/index.cjs.map +1 -0
  125. package/packages/config/dist/index.d.ts +131 -0
  126. package/packages/config/node_modules/.bin/jiti +17 -0
  127. package/packages/config/node_modules/.bin/tsc +17 -0
  128. package/packages/config/node_modules/.bin/tsserver +17 -0
  129. package/packages/config/node_modules/.bin/tsup +17 -0
  130. package/packages/config/node_modules/.bin/tsup-node +17 -0
  131. package/packages/config/node_modules/.bin/tsx +17 -0
  132. package/packages/config/package.json +21 -0
  133. package/packages/config/src/index.js +1 -0
  134. package/packages/config/src/index.ts +1282 -0
  135. package/packages/config/tsconfig.json +4 -0
  136. package/packages/rpc/.turbo/turbo-build.log +32 -0
  137. package/packages/rpc/dist/_esm-BCLXDO2R.cjs +3660 -0
  138. package/packages/rpc/dist/_esm-BCLXDO2R.cjs.map +1 -0
  139. package/packages/rpc/dist/ccip-OWJLAW55.cjs +16 -0
  140. package/packages/rpc/dist/ccip-OWJLAW55.cjs.map +1 -0
  141. package/packages/rpc/dist/chunk-APQIFZ3B.cjs +6247 -0
  142. package/packages/rpc/dist/chunk-APQIFZ3B.cjs.map +1 -0
  143. package/packages/rpc/dist/chunk-CDO2GWRD.cjs +410 -0
  144. package/packages/rpc/dist/chunk-CDO2GWRD.cjs.map +1 -0
  145. package/packages/rpc/dist/chunk-QGTNTFJ7.cjs +2249 -0
  146. package/packages/rpc/dist/chunk-QGTNTFJ7.cjs.map +1 -0
  147. package/packages/rpc/dist/chunk-TZDTAHWR.cjs +44 -0
  148. package/packages/rpc/dist/chunk-TZDTAHWR.cjs.map +1 -0
  149. package/packages/rpc/dist/index.cjs +7342 -0
  150. package/packages/rpc/dist/index.cjs.map +1 -0
  151. package/packages/rpc/dist/index.d.ts +3857 -0
  152. package/packages/rpc/dist/secp256k1-WCNM675D.cjs +18 -0
  153. package/packages/rpc/dist/secp256k1-WCNM675D.cjs.map +1 -0
  154. package/packages/rpc/node_modules/.bin/jiti +17 -0
  155. package/packages/rpc/node_modules/.bin/tsc +17 -0
  156. package/packages/rpc/node_modules/.bin/tsserver +17 -0
  157. package/packages/rpc/node_modules/.bin/tsup +17 -0
  158. package/packages/rpc/node_modules/.bin/tsup-node +17 -0
  159. package/packages/rpc/node_modules/.bin/tsx +17 -0
  160. package/packages/rpc/package.json +25 -0
  161. package/packages/rpc/src/index.ts +206 -0
  162. package/packages/rpc/tsconfig.json +4 -0
  163. package/packages/typescript/base.json +36 -0
  164. package/packages/typescript/nextjs.json +17 -0
  165. package/packages/typescript/package.json +10 -0
  166. package/packages/ui/.turbo/turbo-build.log +44 -0
  167. package/packages/ui/dist/chunk-MOAFBKSA.js +11 -0
  168. package/packages/ui/dist/chunk-MOAFBKSA.js.map +1 -0
  169. package/packages/ui/dist/components/badge.d.ts +12 -0
  170. package/packages/ui/dist/components/badge.js +31 -0
  171. package/packages/ui/dist/components/badge.js.map +1 -0
  172. package/packages/ui/dist/components/button.d.ts +13 -0
  173. package/packages/ui/dist/components/button.js +40 -0
  174. package/packages/ui/dist/components/button.js.map +1 -0
  175. package/packages/ui/dist/components/card.d.ts +10 -0
  176. package/packages/ui/dist/components/card.js +39 -0
  177. package/packages/ui/dist/components/card.js.map +1 -0
  178. package/packages/ui/dist/components/input.d.ts +5 -0
  179. package/packages/ui/dist/components/input.js +28 -0
  180. package/packages/ui/dist/components/input.js.map +1 -0
  181. package/packages/ui/dist/components/label.d.ts +5 -0
  182. package/packages/ui/dist/components/label.js +13 -0
  183. package/packages/ui/dist/components/label.js.map +1 -0
  184. package/packages/ui/dist/components/separator.d.ts +5 -0
  185. package/packages/ui/dist/components/separator.js +13 -0
  186. package/packages/ui/dist/components/separator.js.map +1 -0
  187. package/packages/ui/dist/components/textarea.d.ts +5 -0
  188. package/packages/ui/dist/components/textarea.js +27 -0
  189. package/packages/ui/dist/components/textarea.js.map +1 -0
  190. package/packages/ui/dist/tailwind.d.ts +56 -0
  191. package/packages/ui/dist/tailwind.js +60 -0
  192. package/packages/ui/dist/tailwind.js.map +1 -0
  193. package/packages/ui/dist/utils/cn.d.ts +5 -0
  194. package/packages/ui/dist/utils/cn.js +7 -0
  195. package/packages/ui/dist/utils/cn.js.map +1 -0
  196. package/packages/ui/node_modules/.bin/jiti +17 -0
  197. package/packages/ui/node_modules/.bin/tsc +17 -0
  198. package/packages/ui/node_modules/.bin/tsserver +17 -0
  199. package/packages/ui/node_modules/.bin/tsup +17 -0
  200. package/packages/ui/node_modules/.bin/tsup-node +17 -0
  201. package/packages/ui/node_modules/.bin/tsx +17 -0
  202. package/packages/ui/package.json +69 -0
  203. package/packages/ui/src/components/badge.tsx +27 -0
  204. package/packages/ui/src/components/button.tsx +40 -0
  205. package/packages/ui/src/components/card.tsx +31 -0
  206. package/packages/ui/src/components/input.tsx +21 -0
  207. package/packages/ui/src/components/label.tsx +6 -0
  208. package/packages/ui/src/components/separator.tsx +6 -0
  209. package/packages/ui/src/components/textarea.tsx +20 -0
  210. package/packages/ui/src/globals.css +70 -0
  211. package/packages/ui/src/tailwind.ts +56 -0
  212. package/packages/ui/src/utils/cn.ts +6 -0
  213. package/packages/ui/tsconfig.json +20 -0
  214. package/packages/ui/tsup.config.ts +20 -0
  215. package/pnpm-workspace.yaml +4 -0
  216. package/scripts/install-rust-binaries.mjs +84 -0
  217. package/scripts/launchd/install-user-daemon.sh +358 -0
  218. package/scripts/launchd/run-vault-daemon.sh +5 -0
  219. package/scripts/launchd/run-wlfi-agent-daemon.sh +73 -0
  220. package/scripts/launchd/uninstall-user-daemon.sh +103 -0
  221. package/src/cli.ts +2121 -0
  222. package/src/lib/admin-guard.js +1 -0
  223. package/src/lib/admin-guard.ts +185 -0
  224. package/src/lib/admin-passthrough.ts +33 -0
  225. package/src/lib/admin-reset.ts +751 -0
  226. package/src/lib/admin-setup.ts +1612 -0
  227. package/src/lib/agent-auth-clear.js +1 -0
  228. package/src/lib/agent-auth-clear.ts +58 -0
  229. package/src/lib/agent-auth-forwarding.js +1 -0
  230. package/src/lib/agent-auth-forwarding.ts +149 -0
  231. package/src/lib/agent-auth-migrate.js +1 -0
  232. package/src/lib/agent-auth-migrate.ts +150 -0
  233. package/src/lib/agent-auth-revoke.ts +103 -0
  234. package/src/lib/agent-auth-rotate.ts +107 -0
  235. package/src/lib/agent-auth-token.js +1 -0
  236. package/src/lib/agent-auth-token.ts +25 -0
  237. package/src/lib/agent-auth.ts +89 -0
  238. package/src/lib/asset-broadcast.js +1 -0
  239. package/src/lib/asset-broadcast.ts +285 -0
  240. package/src/lib/bootstrap-artifacts.js +1 -0
  241. package/src/lib/bootstrap-artifacts.ts +205 -0
  242. package/src/lib/bootstrap-credentials.js +1 -0
  243. package/src/lib/bootstrap-credentials.ts +832 -0
  244. package/src/lib/config-amounts.js +1 -0
  245. package/src/lib/config-amounts.ts +189 -0
  246. package/src/lib/config-mutation.ts +27 -0
  247. package/src/lib/fs-trust.js +1 -0
  248. package/src/lib/fs-trust.ts +537 -0
  249. package/src/lib/keychain.js +1 -0
  250. package/src/lib/keychain.ts +225 -0
  251. package/src/lib/local-admin-access.ts +106 -0
  252. package/src/lib/network-selection.js +1 -0
  253. package/src/lib/network-selection.ts +71 -0
  254. package/src/lib/passthrough-security.js +1 -0
  255. package/src/lib/passthrough-security.ts +114 -0
  256. package/src/lib/rpc-guard.js +1 -0
  257. package/src/lib/rpc-guard.ts +7 -0
  258. package/src/lib/rust-spawn-options.js +1 -0
  259. package/src/lib/rust-spawn-options.ts +98 -0
  260. package/src/lib/rust.js +1 -0
  261. package/src/lib/rust.ts +143 -0
  262. package/src/lib/signed-tx.js +1 -0
  263. package/src/lib/signed-tx.ts +116 -0
  264. package/src/lib/status-repair-cli.ts +116 -0
  265. package/src/lib/sudo.js +1 -0
  266. package/src/lib/sudo.ts +172 -0
  267. package/src/lib/vault-password-forwarding.js +1 -0
  268. package/src/lib/vault-password-forwarding.ts +155 -0
  269. package/src/lib/wallet-profile.js +1 -0
  270. package/src/lib/wallet-profile.ts +332 -0
  271. package/src/lib/wallet-repair.js +1 -0
  272. package/src/lib/wallet-repair.ts +304 -0
  273. package/src/lib/wallet-setup.js +1 -0
  274. package/src/lib/wallet-setup.ts +1466 -0
  275. package/src/lib/wallet-status.js +1 -0
  276. package/src/lib/wallet-status.ts +640 -0
  277. package/tsconfig.base.json +17 -0
  278. package/tsconfig.json +10 -0
  279. package/tsup.config.ts +25 -0
  280. package/turbo.json +41 -0
  281. package/LICENSE.md +0 -1
  282. package/dist/wlfa/index.cjs +0 -250
  283. package/dist/wlfa/index.d.cts +0 -1
  284. package/dist/wlfa/index.d.ts +0 -1
  285. package/dist/wlfa/index.js +0 -250
  286. package/dist/wlfc/index.cjs +0 -1839
  287. package/dist/wlfc/index.d.cts +0 -1
  288. package/dist/wlfc/index.d.ts +0 -1
  289. package/dist/wlfc/index.js +0 -1839
@@ -0,0 +1,225 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { assertValidAgentAuthToken } from './agent-auth-token.js';
3
+
4
+ export const AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE = 'wlfi-agent-agent-auth-token';
5
+ export const DAEMON_PASSWORD_KEYCHAIN_SERVICE = 'wlfi-agent-daemon-password';
6
+ const SECURITY_NOT_FOUND_EXIT_CODE = 44;
7
+ const AGENT_KEY_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
8
+ const MAX_KEYCHAIN_SECRET_BYTES = 16 * 1024;
9
+ const KEYCHAIN_IDENTIFIER_CONTROL_CHAR_PATTERN = /[\u0000-\u001f\u007f]/u;
10
+
11
+ export interface SecurityCommandInvocation {
12
+ args: string[];
13
+ input?: string;
14
+ }
15
+
16
+ export type SecurityCommandRunner = (command: SecurityCommandInvocation) => string;
17
+
18
+ export function assertValidAgentKeyId(agentKeyId: string): string {
19
+ const normalized = agentKeyId.trim();
20
+ if (!AGENT_KEY_ID_PATTERN.test(normalized)) {
21
+ throw new Error('agentKeyId must be a valid UUID');
22
+ }
23
+ return normalized;
24
+ }
25
+
26
+ function defaultSecurityRunner(command: SecurityCommandInvocation): string {
27
+ const result = spawnSync('security', command.args, {
28
+ encoding: 'utf8',
29
+ input: command.input,
30
+ stdio: ['pipe', 'pipe', 'pipe']
31
+ });
32
+
33
+ if (result.error) {
34
+ throw result.error;
35
+ }
36
+ if (result.status !== 0) {
37
+ const error = new Error(result.stderr.trim() || result.stdout.trim() || `security ${command.args[0]} failed`);
38
+ Object.assign(error, {
39
+ status: result.status,
40
+ stdout: result.stdout,
41
+ stderr: result.stderr
42
+ });
43
+ throw error;
44
+ }
45
+
46
+ return result.stdout.trim();
47
+ }
48
+
49
+ function renderSecurityError(error: unknown): string | null {
50
+ if (!(error instanceof Error)) {
51
+ return null;
52
+ }
53
+
54
+ const stderr = 'stderr' in error && typeof error.stderr === 'string' ? error.stderr.trim() : '';
55
+ const stdout = 'stdout' in error && typeof error.stdout === 'string' ? error.stdout.trim() : '';
56
+ return stderr || stdout || error.message || null;
57
+ }
58
+
59
+ function isMissingSecurityItem(error: unknown): boolean {
60
+ if (!(error instanceof Error)) {
61
+ return false;
62
+ }
63
+
64
+ const status = 'status' in error && typeof error.status === 'number' ? error.status : null;
65
+ const message = renderSecurityError(error)?.toLowerCase() ?? '';
66
+ return status === SECURITY_NOT_FOUND_EXIT_CODE || message.includes('could not be found');
67
+ }
68
+
69
+ function assertMacOsKeychainAvailable(): void {
70
+ if (process.platform !== 'darwin') {
71
+ throw new Error('macOS Keychain integration is available only on macOS');
72
+ }
73
+ }
74
+
75
+ function withDefaultRunner(runner: SecurityCommandRunner): boolean {
76
+ return runner === defaultSecurityRunner;
77
+ }
78
+
79
+ function assertValidKeychainAccount(account: string): string {
80
+ const normalized = account.trim();
81
+ if (!normalized) {
82
+ throw new Error('keychain account is required');
83
+ }
84
+ if (KEYCHAIN_IDENTIFIER_CONTROL_CHAR_PATTERN.test(normalized)) {
85
+ throw new Error('keychain account must not contain control characters');
86
+ }
87
+ return normalized;
88
+ }
89
+
90
+ function assertValidKeychainService(service: string): string {
91
+ const normalized = service.trim();
92
+ if (!normalized) {
93
+ throw new Error('keychain service is required');
94
+ }
95
+ if (KEYCHAIN_IDENTIFIER_CONTROL_CHAR_PATTERN.test(normalized)) {
96
+ throw new Error('keychain service must not contain control characters');
97
+ }
98
+ return normalized;
99
+ }
100
+
101
+ function assertValidKeychainSecret(secret: string, label: string): string {
102
+ if (Buffer.byteLength(secret, 'utf8') > MAX_KEYCHAIN_SECRET_BYTES) {
103
+ throw new Error(`${label} must not exceed ${MAX_KEYCHAIN_SECRET_BYTES} bytes`);
104
+ }
105
+ if (!secret.trim()) {
106
+ throw new Error(`${label} must not be empty or whitespace`);
107
+ }
108
+ return secret;
109
+ }
110
+
111
+ function storeGenericPasswordInKeychain(
112
+ service: string,
113
+ account: string,
114
+ secret: string,
115
+ runner: SecurityCommandRunner = defaultSecurityRunner
116
+ ): void {
117
+ if (withDefaultRunner(runner)) {
118
+ assertMacOsKeychainAvailable();
119
+ }
120
+
121
+ const normalizedService = assertValidKeychainService(service);
122
+ const normalizedAccount = assertValidKeychainAccount(account);
123
+ const normalizedSecret = assertValidKeychainSecret(secret, 'keychain secret');
124
+
125
+ runner({
126
+ args: [
127
+ 'add-generic-password',
128
+ '-U',
129
+ '-s',
130
+ normalizedService,
131
+ '-a',
132
+ normalizedAccount,
133
+ '-X',
134
+ Buffer.from(normalizedSecret, 'utf8').toString('hex'),
135
+ ],
136
+ });
137
+ }
138
+
139
+ export function storeAgentAuthTokenInKeychain(
140
+ agentKeyId: string,
141
+ token: string,
142
+ runner: SecurityCommandRunner = defaultSecurityRunner
143
+ ): void {
144
+ const normalizedAgentKeyId = assertValidAgentKeyId(agentKeyId);
145
+ const normalizedToken = assertValidAgentAuthToken(token, 'agentAuthToken');
146
+ storeGenericPasswordInKeychain(
147
+ AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE,
148
+ normalizedAgentKeyId,
149
+ normalizedToken,
150
+ runner
151
+ );
152
+ }
153
+
154
+ export function storeDaemonPasswordInKeychain(
155
+ account: string,
156
+ password: string,
157
+ runner: SecurityCommandRunner = defaultSecurityRunner
158
+ ): void {
159
+ storeGenericPasswordInKeychain(DAEMON_PASSWORD_KEYCHAIN_SERVICE, account, password, runner);
160
+ }
161
+
162
+ export function readAgentAuthTokenFromKeychain(
163
+ agentKeyId: string,
164
+ runner: SecurityCommandRunner = defaultSecurityRunner
165
+ ): string | null {
166
+ if (process.platform !== 'darwin' && withDefaultRunner(runner)) {
167
+ return null;
168
+ }
169
+
170
+ const normalizedAgentKeyId = assertValidAgentKeyId(agentKeyId);
171
+
172
+ try {
173
+ return runner({
174
+ args: [
175
+ 'find-generic-password',
176
+ '-w',
177
+ '-s',
178
+ AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE,
179
+ '-a',
180
+ normalizedAgentKeyId
181
+ ]
182
+ });
183
+ } catch (error) {
184
+ if (isMissingSecurityItem(error)) {
185
+ return null;
186
+ }
187
+ throw new Error(renderSecurityError(error) ?? 'failed to read agent auth token from Keychain');
188
+ }
189
+ }
190
+
191
+ export function deleteAgentAuthTokenFromKeychain(
192
+ agentKeyId: string,
193
+ runner: SecurityCommandRunner = defaultSecurityRunner
194
+ ): boolean {
195
+ if (process.platform !== 'darwin' && withDefaultRunner(runner)) {
196
+ return false;
197
+ }
198
+
199
+ const normalizedAgentKeyId = assertValidAgentKeyId(agentKeyId);
200
+
201
+ try {
202
+ runner({
203
+ args: [
204
+ 'delete-generic-password',
205
+ '-s',
206
+ AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE,
207
+ '-a',
208
+ normalizedAgentKeyId
209
+ ]
210
+ });
211
+ return true;
212
+ } catch (error) {
213
+ if (isMissingSecurityItem(error)) {
214
+ return false;
215
+ }
216
+ throw new Error(renderSecurityError(error) ?? 'failed to delete agent auth token from Keychain');
217
+ }
218
+ }
219
+
220
+ export function hasAgentAuthTokenInKeychain(
221
+ agentKeyId: string,
222
+ runner: SecurityCommandRunner = defaultSecurityRunner
223
+ ): boolean {
224
+ return readAgentAuthTokenFromKeychain(agentKeyId, runner) !== null;
225
+ }
@@ -0,0 +1,106 @@
1
+ import readline from 'node:readline';
2
+ import { createSudoSession } from './sudo.js';
3
+
4
+ const MAX_SECRET_STDIN_BYTES = 16 * 1024;
5
+
6
+ export interface LocalAdminMutationAccessDeps {
7
+ isRoot?: () => boolean;
8
+ ensureRootAccess?: () => Promise<void>;
9
+ }
10
+
11
+ function renderError(error: unknown): string {
12
+ return error instanceof Error ? error.message : String(error);
13
+ }
14
+
15
+ function validateSecret(value: string, label: string): string {
16
+ if (Buffer.byteLength(value, 'utf8') > MAX_SECRET_STDIN_BYTES) {
17
+ throw new Error(`${label} must not exceed ${MAX_SECRET_STDIN_BYTES} bytes`);
18
+ }
19
+ if (!value.trim()) {
20
+ throw new Error(`${label} must not be empty or whitespace`);
21
+ }
22
+ return value;
23
+ }
24
+
25
+ function currentProcessIsRoot(): boolean {
26
+ return typeof process.geteuid === 'function' && process.geteuid() === 0;
27
+ }
28
+
29
+ async function promptHidden(query: string, label: string): Promise<string> {
30
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
31
+ throw new Error(`${label} is required; rerun on a local TTY`);
32
+ }
33
+
34
+ const rl = readline.createInterface({
35
+ input: process.stdin,
36
+ output: process.stdout,
37
+ terminal: true,
38
+ }) as readline.Interface & { stdoutMuted?: boolean; _writeToOutput?: (value: string) => void };
39
+
40
+ rl.stdoutMuted = true;
41
+ rl._writeToOutput = (value: string) => {
42
+ if (value.includes(query)) {
43
+ (rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
44
+ return;
45
+ }
46
+ if (!rl.stdoutMuted) {
47
+ (rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
48
+ }
49
+ };
50
+
51
+ const answer = await new Promise<string>((resolve) => {
52
+ rl.question(query, resolve);
53
+ });
54
+ rl.close();
55
+ process.stdout.write('\n');
56
+ return validateSecret(answer, label);
57
+ }
58
+
59
+ const sudoSession = createSudoSession({
60
+ promptPassword: async () =>
61
+ await promptHidden(
62
+ 'Root password (input hidden; required to change local admin chain and token configuration): ',
63
+ 'root password',
64
+ ),
65
+ });
66
+
67
+ export async function requireLocalAdminMutationAccess(
68
+ commandLabel: string,
69
+ deps: LocalAdminMutationAccessDeps = {},
70
+ ): Promise<void> {
71
+ const isRoot = deps.isRoot ?? currentProcessIsRoot;
72
+ if (isRoot()) {
73
+ return;
74
+ }
75
+
76
+ const ensureRootAccess = deps.ensureRootAccess ?? (() => sudoSession.prime());
77
+ try {
78
+ await ensureRootAccess();
79
+ } catch (error) {
80
+ throw new Error(
81
+ `${commandLabel} requires verified root access before local admin configuration can change: ${renderError(error)}`,
82
+ );
83
+ }
84
+ }
85
+
86
+ export function withLocalAdminMutationAccess<TArgs extends unknown[], TReturn>(
87
+ commandLabel: string,
88
+ action: (...args: TArgs) => TReturn | Promise<TReturn>,
89
+ deps: LocalAdminMutationAccessDeps = {},
90
+ ): (...args: TArgs) => Promise<TReturn> {
91
+ return async (...args: TArgs): Promise<TReturn> => {
92
+ await requireLocalAdminMutationAccess(commandLabel, deps);
93
+ return await action(...args);
94
+ };
95
+ }
96
+
97
+ export function withDynamicLocalAdminMutationAccess<TArgs extends unknown[], TReturn>(
98
+ resolveCommandLabel: (...args: TArgs) => string,
99
+ action: (...args: TArgs) => TReturn | Promise<TReturn>,
100
+ deps: LocalAdminMutationAccessDeps = {},
101
+ ): (...args: TArgs) => Promise<TReturn> {
102
+ return async (...args: TArgs): Promise<TReturn> => {
103
+ await requireLocalAdminMutationAccess(resolveCommandLabel(...args), deps);
104
+ return await action(...args);
105
+ };
106
+ }
@@ -0,0 +1 @@
1
+ export * from './network-selection.ts';
@@ -0,0 +1,71 @@
1
+ import {
2
+ assertSafeRpcUrl,
3
+ resolveChainProfile,
4
+ type WlfiConfig,
5
+ } from '../../packages/config/src/index.js';
6
+
7
+ function presentString(value: string | undefined | null): string | undefined {
8
+ const normalized = value?.trim();
9
+ return normalized ? normalized : undefined;
10
+ }
11
+
12
+ function isNumericSelector(value: string): boolean {
13
+ return /^[0-9]+$/u.test(value);
14
+ }
15
+
16
+ function resolveActiveNetworkSelector(config: WlfiConfig): string | undefined {
17
+ const activeName = presentString(config.chainName);
18
+ if (activeName) {
19
+ return activeName;
20
+ }
21
+ if (config.chainId !== undefined) {
22
+ return String(config.chainId);
23
+ }
24
+ return undefined;
25
+ }
26
+
27
+ export function resolveCliNetworkProfile(
28
+ selector: string | undefined,
29
+ config: WlfiConfig,
30
+ label = 'network',
31
+ ) {
32
+ const explicitSelector = presentString(selector);
33
+ if (explicitSelector && isNumericSelector(explicitSelector)) {
34
+ throw new Error(`${label} must be a chain name, not a chain id`);
35
+ }
36
+
37
+ const resolvedSelector = explicitSelector ?? resolveActiveNetworkSelector(config);
38
+ if (!resolvedSelector) {
39
+ throw new Error(`${label} is required`);
40
+ }
41
+
42
+ const profile = resolveChainProfile(resolvedSelector, config);
43
+ if (!profile) {
44
+ throw new Error(`${label} '${resolvedSelector}' is not a configured or builtin chain name`);
45
+ }
46
+
47
+ return profile;
48
+ }
49
+
50
+ export function resolveCliRpcUrl(
51
+ rpcUrl: string | undefined,
52
+ selector: string | undefined,
53
+ config: WlfiConfig,
54
+ ): string {
55
+ const explicitRpcUrl = presentString(rpcUrl);
56
+ if (explicitRpcUrl) {
57
+ return assertSafeRpcUrl(explicitRpcUrl, 'rpcUrl');
58
+ }
59
+
60
+ const profileRpcUrl = presentString(resolveCliNetworkProfile(selector, config).rpcUrl);
61
+ if (profileRpcUrl) {
62
+ return assertSafeRpcUrl(profileRpcUrl, 'rpcUrl');
63
+ }
64
+
65
+ const configuredRpcUrl = presentString(config.rpcUrl);
66
+ if (configuredRpcUrl) {
67
+ return assertSafeRpcUrl(configuredRpcUrl, 'rpcUrl');
68
+ }
69
+
70
+ throw new Error('rpcUrl is required');
71
+ }
@@ -0,0 +1 @@
1
+ export * from './passthrough-security.ts';
@@ -0,0 +1,114 @@
1
+ import { defaultDaemonSocketPath, type WlfiConfig } from '../../packages/config/src/index.js';
2
+ import { assertTrustedAdminDaemonSocketPath, assertTrustedDaemonSocketPath } from './fs-trust.js';
3
+ import type { RustBinaryName } from './rust.js';
4
+
5
+ const HELP_FLAGS = new Set(['-h', '--help', '-V', '--version']);
6
+
7
+ function forwardedArgsPrefix(args: string[]): string[] {
8
+ const terminatorIndex = args.indexOf('--');
9
+ return terminatorIndex >= 0 ? args.slice(0, terminatorIndex) : args;
10
+ }
11
+
12
+ interface ResolvePassthroughDaemonSocketDeps {
13
+ env?: NodeJS.ProcessEnv;
14
+ assertTrustedDaemonSocketPath?: (targetPath: string, label?: string) => string;
15
+ }
16
+
17
+ interface ForwardedOptionValue {
18
+ present: boolean;
19
+ value: string | undefined;
20
+ }
21
+
22
+ function findForwardedOptionOccurrences(args: string[], optionName: string): ForwardedOptionValue[] {
23
+ const matches: ForwardedOptionValue[] = [];
24
+
25
+ for (let index = 0; index < args.length; index += 1) {
26
+ const current = args[index];
27
+ if (current === '--') {
28
+ break;
29
+ }
30
+
31
+ if (current === optionName) {
32
+ const value = args[index + 1];
33
+ if (value === undefined || value === '--' || value.startsWith('-')) {
34
+ throw new Error(
35
+ `${optionName} requires a path; use ${optionName}=<path> if the path starts with -`
36
+ );
37
+ }
38
+ matches.push({
39
+ present: true,
40
+ value
41
+ });
42
+ index += 1;
43
+ continue;
44
+ }
45
+
46
+ if (current.startsWith(`${optionName}=`)) {
47
+ matches.push({
48
+ present: true,
49
+ value: current.slice(optionName.length + 1)
50
+ });
51
+ }
52
+ }
53
+
54
+ return matches;
55
+ }
56
+
57
+ function presentString(value: string | undefined): string | null {
58
+ const normalized = value?.trim();
59
+ return normalized ? normalized : null;
60
+ }
61
+
62
+ export function forwardedArgsSkipDaemonSocketValidation(args: string[]): boolean {
63
+ const forwarded = forwardedArgsPrefix(args);
64
+ return forwarded[0] === 'help' || forwarded.some((arg) => HELP_FLAGS.has(arg));
65
+ }
66
+
67
+ export function readForwardedLongOptionValue(
68
+ args: string[],
69
+ optionName: string
70
+ ): ForwardedOptionValue {
71
+ const matches = findForwardedOptionOccurrences(args, optionName);
72
+ if (matches.length > 1) {
73
+ throw new Error(`${optionName} may only be provided once`);
74
+ }
75
+
76
+ if (matches.length === 1) {
77
+ return matches[0];
78
+ }
79
+
80
+ return {
81
+ present: false,
82
+ value: undefined
83
+ };
84
+ }
85
+
86
+ export function resolveValidatedPassthroughDaemonSocket(
87
+ binaryName: RustBinaryName,
88
+ args: string[],
89
+ config: WlfiConfig,
90
+ deps: ResolvePassthroughDaemonSocketDeps = {}
91
+ ): string | null {
92
+ if (binaryName === 'wlfi-agent-daemon' || forwardedArgsSkipDaemonSocketValidation(args)) {
93
+ return null;
94
+ }
95
+
96
+ const env = deps.env ?? process.env;
97
+ const trustDaemonSocketPath = deps.assertTrustedDaemonSocketPath
98
+ ?? (binaryName === 'wlfi-agent-admin'
99
+ ? assertTrustedAdminDaemonSocketPath
100
+ : assertTrustedDaemonSocketPath);
101
+ const forwardedValue = readForwardedLongOptionValue(args, '--daemon-socket');
102
+
103
+ if (forwardedValue.present && !presentString(forwardedValue.value)) {
104
+ throw new Error('--daemon-socket requires a path');
105
+ }
106
+
107
+ const daemonSocket =
108
+ presentString(forwardedValue.value) ??
109
+ presentString(env.WLFI_DAEMON_SOCKET) ??
110
+ presentString(config.daemonSocket) ??
111
+ defaultDaemonSocketPath();
112
+
113
+ return trustDaemonSocketPath(daemonSocket);
114
+ }
@@ -0,0 +1 @@
1
+ export * from './rpc-guard.ts';
@@ -0,0 +1,7 @@
1
+ export function assertRpcChainIdMatches(expectedChainId: number, actualChainId: number): void {
2
+ if (actualChainId !== expectedChainId) {
3
+ throw new Error(
4
+ `RPC endpoint chainId ${actualChainId} does not match expected chainId ${expectedChainId}`
5
+ );
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ export * from './rust-spawn-options.ts';
@@ -0,0 +1,98 @@
1
+ import { assertAdminAccessPreconditions } from './admin-guard.js';
2
+ import { prepareAgentAuthRelay } from './agent-auth-forwarding.js';
3
+ import { prepareVaultPasswordRelay } from './vault-password-forwarding.js';
4
+
5
+ export interface RunRustBinaryOptions {
6
+ stdin?: string;
7
+ scrubSensitiveEnv?: boolean;
8
+ preSuppliedSecretStdin?: 'vaultPassword' | 'agentAuthToken';
9
+ }
10
+
11
+ const SENSITIVE_ENV_KEYS = ['WLFI_AGENT_AUTH_TOKEN', 'WLFI_VAULT_PASSWORD'] as const;
12
+
13
+ function buildChildEnv(scrubSensitiveEnv: boolean): NodeJS.ProcessEnv {
14
+ if (!scrubSensitiveEnv) {
15
+ return process.env;
16
+ }
17
+
18
+ const env: NodeJS.ProcessEnv = { ...process.env };
19
+ for (const key of SENSITIVE_ENV_KEYS) {
20
+ delete env[key];
21
+ }
22
+ return env;
23
+ }
24
+
25
+ function supportsVaultPasswordRelay(binaryName: string): boolean {
26
+ return binaryName === 'wlfi-agent-admin' || binaryName === 'wlfi-agent-daemon';
27
+ }
28
+
29
+ function supportsAgentAuthRelay(binaryName: string): boolean {
30
+ return binaryName === 'wlfi-agent-agent';
31
+ }
32
+
33
+ function hasFlag(args: string[], flag: string): boolean {
34
+ return args.includes(flag);
35
+ }
36
+
37
+ export async function prepareSpawnOptions(
38
+ binaryName: 'wlfi-agent-daemon' | 'wlfi-agent-admin' | 'wlfi-agent-agent',
39
+ args: string[],
40
+ options: RunRustBinaryOptions
41
+ ): Promise<{
42
+ args: string[];
43
+ stdin?: string;
44
+ env: NodeJS.ProcessEnv;
45
+ }> {
46
+ let stdin = options.stdin;
47
+ let scrubSensitiveEnv = options.scrubSensitiveEnv ?? false;
48
+ let preparedArgs = [...args];
49
+ const preSuppliedSecretStdin = options.preSuppliedSecretStdin;
50
+
51
+ if (preSuppliedSecretStdin && stdin === undefined) {
52
+ throw new Error('preSuppliedSecretStdin requires an explicit stdin payload');
53
+ }
54
+
55
+ if (supportsVaultPasswordRelay(binaryName)) {
56
+ if (preSuppliedSecretStdin === 'vaultPassword') {
57
+ if (!hasFlag(preparedArgs, '--vault-password-stdin')) {
58
+ throw new Error('preSuppliedSecretStdin=vaultPassword requires --vault-password-stdin in args');
59
+ }
60
+ scrubSensitiveEnv = true;
61
+ } else {
62
+ const relay = await prepareVaultPasswordRelay(preparedArgs);
63
+ if (stdin !== undefined && relay.stdin !== undefined) {
64
+ throw new Error('vault password relay conflicts with explicit stdin payload');
65
+ }
66
+
67
+ preparedArgs = relay.args;
68
+ stdin = relay.stdin ?? stdin;
69
+ scrubSensitiveEnv = scrubSensitiveEnv || relay.scrubSensitiveEnv;
70
+ }
71
+ }
72
+
73
+ if (supportsAgentAuthRelay(binaryName)) {
74
+ if (preSuppliedSecretStdin === 'agentAuthToken') {
75
+ if (!hasFlag(preparedArgs, '--agent-auth-token-stdin')) {
76
+ throw new Error('preSuppliedSecretStdin=agentAuthToken requires --agent-auth-token-stdin in args');
77
+ }
78
+ scrubSensitiveEnv = true;
79
+ } else {
80
+ const relay = await prepareAgentAuthRelay(preparedArgs);
81
+ if (stdin !== undefined && relay.stdin !== undefined) {
82
+ throw new Error('agent auth token relay conflicts with explicit stdin payload');
83
+ }
84
+
85
+ preparedArgs = relay.args;
86
+ stdin = relay.stdin ?? stdin;
87
+ scrubSensitiveEnv = scrubSensitiveEnv || relay.scrubSensitiveEnv;
88
+ }
89
+ }
90
+
91
+ assertAdminAccessPreconditions(binaryName, preparedArgs);
92
+
93
+ return {
94
+ args: preparedArgs,
95
+ stdin,
96
+ env: buildChildEnv(scrubSensitiveEnv)
97
+ };
98
+ }
@@ -0,0 +1 @@
1
+ export * from './rust.ts';