@wlfi-agent/cli 1.4.13 → 1.4.15

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 +45 -41
  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,604 @@
1
+ #[tokio::test]
2
+ async fn disable_policy_stops_enforcement() {
3
+ let daemon = InMemoryDaemon::new(
4
+ "vault-password",
5
+ SoftwareSignerBackend::default(),
6
+ DaemonConfig::default(),
7
+ )
8
+ .expect("daemon");
9
+
10
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
11
+ let session = AdminSession {
12
+ vault_password: "vault-password".to_string(),
13
+ lease,
14
+ };
15
+
16
+ let mut strict = policy_all_per_tx(100);
17
+ strict.priority = 0;
18
+ let mut permissive = policy_all_per_tx(1_000);
19
+ permissive.priority = 1;
20
+ daemon
21
+ .add_policy(&session, strict.clone())
22
+ .await
23
+ .expect("add strict policy");
24
+ daemon
25
+ .add_policy(&session, permissive)
26
+ .await
27
+ .expect("add permissive policy");
28
+
29
+ let key = daemon
30
+ .create_vault_key(&session, KeyCreateRequest::Generate)
31
+ .await
32
+ .expect("key");
33
+ let agent_credentials = daemon
34
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
35
+ .await
36
+ .expect("agent");
37
+
38
+ let deny_action = AgentAction::Transfer {
39
+ chain_id: 1,
40
+ token: "0x1000000000000000000000000000000000000000"
41
+ .parse()
42
+ .expect("token"),
43
+ to: "0x2000000000000000000000000000000000000000"
44
+ .parse()
45
+ .expect("recipient"),
46
+ amount_wei: 500,
47
+ };
48
+ let err = daemon
49
+ .sign_for_agent(sign_request(&agent_credentials, deny_action.clone()))
50
+ .await
51
+ .expect_err("strict policy must deny before disable");
52
+ assert!(matches!(
53
+ err,
54
+ DaemonError::Policy(PolicyError::PerTxLimitExceeded { .. })
55
+ ));
56
+
57
+ daemon
58
+ .disable_policy(&session, strict.id)
59
+ .await
60
+ .expect("disable strict policy");
61
+
62
+ let signature = daemon
63
+ .sign_for_agent(sign_request(&agent_credentials, deny_action))
64
+ .await
65
+ .expect("request should pass after strict policy disable");
66
+ assert!(!signature.bytes.is_empty());
67
+ }
68
+
69
+ #[tokio::test]
70
+ async fn rotate_agent_auth_token_invalidates_previous_token() {
71
+ let daemon = InMemoryDaemon::new(
72
+ "vault-password",
73
+ SoftwareSignerBackend::default(),
74
+ DaemonConfig::default(),
75
+ )
76
+ .expect("daemon");
77
+
78
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
79
+ let session = AdminSession {
80
+ vault_password: "vault-password".to_string(),
81
+ lease,
82
+ };
83
+ daemon
84
+ .add_policy(&session, policy_all_per_tx(1_000))
85
+ .await
86
+ .expect("add policy");
87
+
88
+ let key = daemon
89
+ .create_vault_key(&session, KeyCreateRequest::Generate)
90
+ .await
91
+ .expect("key");
92
+ let agent_credentials = daemon
93
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
94
+ .await
95
+ .expect("agent");
96
+ let old_token = agent_credentials.auth_token.clone();
97
+
98
+ let new_token = daemon
99
+ .rotate_agent_auth_token(&session, agent_credentials.agent_key.id)
100
+ .await
101
+ .expect("rotate token");
102
+ assert_ne!(old_token, new_token);
103
+
104
+ let action = AgentAction::Transfer {
105
+ chain_id: 1,
106
+ token: "0x3000000000000000000000000000000000000000"
107
+ .parse()
108
+ .expect("token"),
109
+ to: "0x4000000000000000000000000000000000000000"
110
+ .parse()
111
+ .expect("recipient"),
112
+ amount_wei: 1,
113
+ };
114
+
115
+ let old_request = sign_request(&agent_credentials, action.clone());
116
+ let err = daemon
117
+ .sign_for_agent(old_request)
118
+ .await
119
+ .expect_err("old token must be rejected after rotation");
120
+ assert!(matches!(err, DaemonError::AgentAuthenticationFailed));
121
+
122
+ let mut rotated_credentials = agent_credentials;
123
+ rotated_credentials.auth_token = new_token;
124
+ let signature = daemon
125
+ .sign_for_agent(sign_request(&rotated_credentials, action))
126
+ .await
127
+ .expect("new token should sign");
128
+ assert!(!signature.bytes.is_empty());
129
+ }
130
+
131
+ #[tokio::test]
132
+ async fn revoke_agent_key_blocks_future_signing() {
133
+ let daemon = InMemoryDaemon::new(
134
+ "vault-password",
135
+ SoftwareSignerBackend::default(),
136
+ DaemonConfig::default(),
137
+ )
138
+ .expect("daemon");
139
+
140
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
141
+ let session = AdminSession {
142
+ vault_password: "vault-password".to_string(),
143
+ lease,
144
+ };
145
+ daemon
146
+ .add_policy(&session, policy_all_per_tx(1_000))
147
+ .await
148
+ .expect("add policy");
149
+
150
+ let key = daemon
151
+ .create_vault_key(&session, KeyCreateRequest::Generate)
152
+ .await
153
+ .expect("key");
154
+ let agent_credentials = daemon
155
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
156
+ .await
157
+ .expect("agent");
158
+
159
+ daemon
160
+ .revoke_agent_key(&session, agent_credentials.agent_key.id)
161
+ .await
162
+ .expect("revoke key");
163
+
164
+ let err = daemon
165
+ .sign_for_agent(sign_request(
166
+ &agent_credentials,
167
+ AgentAction::Transfer {
168
+ chain_id: 1,
169
+ token: "0x1000000000000000000000000000000000000000"
170
+ .parse()
171
+ .expect("token"),
172
+ to: "0x2000000000000000000000000000000000000000"
173
+ .parse()
174
+ .expect("recipient"),
175
+ amount_wei: 1,
176
+ },
177
+ ))
178
+ .await
179
+ .expect_err("revoked key must not sign");
180
+ assert!(matches!(err, DaemonError::AgentAuthenticationFailed));
181
+
182
+ let err = daemon
183
+ .revoke_agent_key(&session, agent_credentials.agent_key.id)
184
+ .await
185
+ .expect_err("second revoke should report unknown key");
186
+ assert!(matches!(err, DaemonError::UnknownAgentKey(_)));
187
+ }
188
+
189
+ #[tokio::test]
190
+ async fn evaluate_for_agent_has_no_spend_side_effects() {
191
+ let daemon = InMemoryDaemon::new(
192
+ "vault-password",
193
+ SoftwareSignerBackend::default(),
194
+ DaemonConfig::default(),
195
+ )
196
+ .expect("daemon");
197
+
198
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
199
+ let session = AdminSession {
200
+ vault_password: "vault-password".to_string(),
201
+ lease,
202
+ };
203
+ daemon
204
+ .add_policy(&session, policy_all_per_tx(1_000))
205
+ .await
206
+ .expect("add spend policy");
207
+ daemon
208
+ .add_policy(
209
+ &session,
210
+ SpendingPolicy::new(
211
+ 1,
212
+ PolicyType::DailyMaxTxCount,
213
+ 1,
214
+ EntityScope::All,
215
+ EntityScope::All,
216
+ EntityScope::All,
217
+ )
218
+ .expect("daily tx count policy"),
219
+ )
220
+ .await
221
+ .expect("add tx-count policy");
222
+
223
+ let key = daemon
224
+ .create_vault_key(&session, KeyCreateRequest::Generate)
225
+ .await
226
+ .expect("key");
227
+ let agent_credentials = daemon
228
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
229
+ .await
230
+ .expect("agent");
231
+
232
+ let action = AgentAction::Transfer {
233
+ chain_id: 1,
234
+ token: "0x5000000000000000000000000000000000000000"
235
+ .parse()
236
+ .expect("token"),
237
+ to: "0x6000000000000000000000000000000000000000"
238
+ .parse()
239
+ .expect("recipient"),
240
+ amount_wei: 1,
241
+ };
242
+ let request = sign_request(&agent_credentials, action.clone());
243
+
244
+ let first = daemon
245
+ .evaluate_for_agent(request.clone())
246
+ .await
247
+ .expect("first evaluation should pass");
248
+ assert_eq!(first.evaluated_policy_ids.len(), 2);
249
+ assert_eq!(daemon.spend_log.read().expect("log read").len(), 0);
250
+
251
+ let second = daemon
252
+ .evaluate_for_agent(request.clone())
253
+ .await
254
+ .expect("second evaluation should also pass (no spend side effects)");
255
+ assert_eq!(first.evaluated_policy_ids, second.evaluated_policy_ids);
256
+ assert_eq!(daemon.spend_log.read().expect("log read").len(), 0);
257
+
258
+ daemon
259
+ .sign_for_agent(sign_request(&agent_credentials, action.clone()))
260
+ .await
261
+ .expect("sign should pass once");
262
+ let err = daemon
263
+ .evaluate_for_agent(sign_request(&agent_credentials, action))
264
+ .await
265
+ .expect_err("tx-count policy should deny after signed spend is recorded");
266
+ assert!(matches!(
267
+ err,
268
+ DaemonError::Policy(PolicyError::TxCountLimitExceeded { .. })
269
+ ));
270
+ }
271
+
272
+ #[tokio::test]
273
+ async fn daily_spend_limit_counts_in_scope_history_across_assets_and_chains() {
274
+ let daemon = InMemoryDaemon::new(
275
+ "vault-password",
276
+ SoftwareSignerBackend::default(),
277
+ DaemonConfig::default(),
278
+ )
279
+ .expect("daemon");
280
+
281
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
282
+ let session = AdminSession {
283
+ vault_password: "vault-password".to_string(),
284
+ lease,
285
+ };
286
+ daemon
287
+ .add_policy(
288
+ &session,
289
+ SpendingPolicy::new(
290
+ 0,
291
+ PolicyType::DailyMaxSpending,
292
+ 100,
293
+ EntityScope::All,
294
+ EntityScope::All,
295
+ EntityScope::All,
296
+ )
297
+ .expect("daily spend policy"),
298
+ )
299
+ .await
300
+ .expect("add daily spend policy");
301
+
302
+ let key = daemon
303
+ .create_vault_key(&session, KeyCreateRequest::Generate)
304
+ .await
305
+ .expect("key");
306
+ let agent_credentials = daemon
307
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
308
+ .await
309
+ .expect("agent");
310
+
311
+ daemon
312
+ .sign_for_agent(sign_request(
313
+ &agent_credentials,
314
+ AgentAction::Transfer {
315
+ chain_id: 1,
316
+ token: "0x5100000000000000000000000000000000000000"
317
+ .parse()
318
+ .expect("token"),
319
+ to: "0x6100000000000000000000000000000000000000"
320
+ .parse()
321
+ .expect("recipient"),
322
+ amount_wei: 70,
323
+ },
324
+ ))
325
+ .await
326
+ .expect("first spend should pass");
327
+
328
+ daemon
329
+ .sign_for_agent(sign_request(
330
+ &agent_credentials,
331
+ AgentAction::TransferNative {
332
+ chain_id: 10,
333
+ to: "0x6200000000000000000000000000000000000000"
334
+ .parse()
335
+ .expect("recipient"),
336
+ amount_wei: 20,
337
+ },
338
+ ))
339
+ .await
340
+ .expect("second spend should pass");
341
+
342
+ let err = daemon
343
+ .sign_for_agent(sign_request(
344
+ &agent_credentials,
345
+ AgentAction::Transfer {
346
+ chain_id: 137,
347
+ token: "0x5200000000000000000000000000000000000000"
348
+ .parse()
349
+ .expect("token"),
350
+ to: "0x6200000000000000000000000000000000000000"
351
+ .parse()
352
+ .expect("recipient"),
353
+ amount_wei: 15,
354
+ },
355
+ ))
356
+ .await
357
+ .expect_err("daily limit should deny after aggregate usage reaches 90");
358
+ assert!(matches!(
359
+ err,
360
+ DaemonError::Policy(PolicyError::WindowLimitExceeded {
361
+ used_amount_wei: 90,
362
+ requested_amount_wei: 15,
363
+ ..
364
+ })
365
+ ));
366
+ }
367
+
368
+ #[tokio::test]
369
+ async fn persistent_store_restores_policies_and_agent_auth_state() {
370
+ let state_path = unique_state_path("restore");
371
+ let config = DaemonConfig::default();
372
+
373
+ let daemon = InMemoryDaemon::new_with_persistent_store(
374
+ "vault-password",
375
+ SoftwareSignerBackend::default(),
376
+ config.clone(),
377
+ PersistentStoreConfig::new(state_path.clone()),
378
+ )
379
+ .expect("daemon");
380
+
381
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
382
+ let session = AdminSession {
383
+ vault_password: "vault-password".to_string(),
384
+ lease,
385
+ };
386
+
387
+ let policy = policy_all_per_tx(1_000);
388
+ daemon
389
+ .add_policy(&session, policy.clone())
390
+ .await
391
+ .expect("add policy");
392
+
393
+ let key = daemon
394
+ .create_vault_key(&session, KeyCreateRequest::Generate)
395
+ .await
396
+ .expect("key");
397
+ let agent_credentials = daemon
398
+ .create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
399
+ .await
400
+ .expect("agent");
401
+
402
+ let action = AgentAction::Transfer {
403
+ chain_id: 1,
404
+ token: "0x1500000000000000000000000000000000000000"
405
+ .parse()
406
+ .expect("token"),
407
+ to: "0x2500000000000000000000000000000000000000"
408
+ .parse()
409
+ .expect("recipient"),
410
+ amount_wei: 1,
411
+ };
412
+ let request = sign_request(&agent_credentials, action);
413
+ daemon
414
+ .evaluate_for_agent(request.clone())
415
+ .await
416
+ .expect("evaluate before restart");
417
+ drop(daemon);
418
+
419
+ let restarted = InMemoryDaemon::new_with_persistent_store(
420
+ "vault-password",
421
+ SoftwareSignerBackend::default(),
422
+ config,
423
+ PersistentStoreConfig::new(state_path.clone()),
424
+ )
425
+ .expect("restarted daemon");
426
+ let lease = restarted
427
+ .issue_lease("vault-password")
428
+ .await
429
+ .expect("lease after restart");
430
+ let session = AdminSession {
431
+ vault_password: "vault-password".to_string(),
432
+ lease,
433
+ };
434
+ let listed = restarted
435
+ .list_policies(&session)
436
+ .await
437
+ .expect("list policies");
438
+ assert!(
439
+ listed.iter().any(|item| item.id == policy.id),
440
+ "policy should persist across restart"
441
+ );
442
+ restarted
443
+ .evaluate_for_agent(request)
444
+ .await
445
+ .expect("evaluate after restart");
446
+ restarted
447
+ .sign_for_agent(sign_request(
448
+ &agent_credentials,
449
+ AgentAction::Transfer {
450
+ chain_id: 1,
451
+ token: "0x1500000000000000000000000000000000000000"
452
+ .parse()
453
+ .expect("token"),
454
+ to: "0x2500000000000000000000000000000000000000"
455
+ .parse()
456
+ .expect("recipient"),
457
+ amount_wei: 1,
458
+ },
459
+ ))
460
+ .await
461
+ .expect("sign after restart");
462
+
463
+ std::fs::remove_file(&state_path).expect("cleanup");
464
+ }
465
+
466
+ #[tokio::test]
467
+ async fn persistent_store_rejects_wrong_password() {
468
+ let state_path = unique_state_path("wrong-password");
469
+ let config = DaemonConfig::default();
470
+
471
+ let daemon = InMemoryDaemon::new_with_persistent_store(
472
+ "vault-password",
473
+ SoftwareSignerBackend::default(),
474
+ config.clone(),
475
+ PersistentStoreConfig::new(state_path.clone()),
476
+ )
477
+ .expect("daemon");
478
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
479
+ let session = AdminSession {
480
+ vault_password: "vault-password".to_string(),
481
+ lease,
482
+ };
483
+ daemon
484
+ .add_policy(&session, policy_all_per_tx(10))
485
+ .await
486
+ .expect("add policy");
487
+ drop(daemon);
488
+
489
+ let err = match InMemoryDaemon::new_with_persistent_store(
490
+ "wrong-password",
491
+ SoftwareSignerBackend::default(),
492
+ config,
493
+ PersistentStoreConfig::new(state_path.clone()),
494
+ ) {
495
+ Ok(_) => panic!("wrong password must fail state load"),
496
+ Err(err) => err,
497
+ };
498
+ assert!(matches!(err, DaemonError::Persistence(_)));
499
+
500
+ std::fs::remove_file(&state_path).expect("cleanup");
501
+ }
502
+
503
+ #[test]
504
+ fn validate_loaded_state_rejects_insecure_remote_http_relay_url() {
505
+ let daemon = InMemoryDaemon::new(
506
+ "vault-password",
507
+ SoftwareSignerBackend::default(),
508
+ DaemonConfig::default(),
509
+ )
510
+ .expect("daemon");
511
+
512
+ let mut state = daemon.snapshot_state().expect("state snapshot");
513
+ state.relay_config.relay_url = Some("http://relay.example".to_string());
514
+
515
+ let err = validate_loaded_state(&state).expect_err("insecure persisted relay URL must fail");
516
+ assert!(
517
+ matches!(err, DaemonError::InvalidRelayConfig(message) if message.contains("must use https unless it targets localhost or a loopback address"))
518
+ );
519
+ }
520
+
521
+ #[cfg(unix)]
522
+ #[tokio::test]
523
+ async fn persistent_store_rejects_group_writable_ancestor_directory() {
524
+ use std::os::unix::fs::PermissionsExt;
525
+
526
+ let unique = SystemTime::now()
527
+ .duration_since(UNIX_EPOCH)
528
+ .expect("time")
529
+ .as_nanos();
530
+ let root = std::env::temp_dir().join(format!(
531
+ "wlfi-daemon-persistence-parent-{}-{}",
532
+ std::process::id(),
533
+ unique
534
+ ));
535
+ let insecure = root.join("insecure");
536
+ let leaf = insecure.join("leaf");
537
+ std::fs::create_dir_all(&leaf).expect("create test directories");
538
+ std::fs::set_permissions(&insecure, std::fs::Permissions::from_mode(0o770))
539
+ .expect("set insecure ancestor permissions");
540
+
541
+ let err = match InMemoryDaemon::new_with_persistent_store(
542
+ "vault-password",
543
+ SoftwareSignerBackend::default(),
544
+ DaemonConfig::default(),
545
+ PersistentStoreConfig::new(leaf.join("daemon-state.enc")),
546
+ ) {
547
+ Ok(_) => panic!("group-writable ancestor must be rejected"),
548
+ Err(err) => err,
549
+ };
550
+ assert!(
551
+ matches!(err, DaemonError::Persistence(message) if message.contains("state directory") && message.contains("must not be writable by group/other"))
552
+ );
553
+
554
+ std::fs::set_permissions(&insecure, std::fs::Permissions::from_mode(0o700))
555
+ .expect("restore ancestor permissions for cleanup");
556
+ std::fs::remove_dir_all(&root).expect("cleanup directories");
557
+ }
558
+
559
+ #[cfg(unix)]
560
+ #[tokio::test]
561
+ async fn persistent_store_rejects_group_readable_state_file() {
562
+ use std::os::unix::fs::PermissionsExt;
563
+
564
+ let state_path = unique_state_path("group-readable");
565
+ let config = DaemonConfig::default();
566
+
567
+ let daemon = InMemoryDaemon::new_with_persistent_store(
568
+ "vault-password",
569
+ SoftwareSignerBackend::default(),
570
+ config.clone(),
571
+ PersistentStoreConfig::new(state_path.clone()),
572
+ )
573
+ .expect("daemon");
574
+ let lease = daemon.issue_lease("vault-password").await.expect("lease");
575
+ let session = AdminSession {
576
+ vault_password: "vault-password".to_string(),
577
+ lease,
578
+ };
579
+ daemon
580
+ .add_policy(&session, policy_all_per_tx(10))
581
+ .await
582
+ .expect("add policy");
583
+ drop(daemon);
584
+
585
+ std::fs::set_permissions(&state_path, std::fs::Permissions::from_mode(0o640))
586
+ .expect("set insecure state file permissions");
587
+
588
+ let err = match InMemoryDaemon::new_with_persistent_store(
589
+ "vault-password",
590
+ SoftwareSignerBackend::default(),
591
+ config,
592
+ PersistentStoreConfig::new(state_path.clone()),
593
+ ) {
594
+ Ok(_) => panic!("group-readable state file must be rejected"),
595
+ Err(err) => err,
596
+ };
597
+ assert!(
598
+ matches!(err, DaemonError::Persistence(message) if message.contains("state file") && message.contains("must not grant group/other permissions"))
599
+ );
600
+
601
+ std::fs::set_permissions(&state_path, std::fs::Permissions::from_mode(0o600))
602
+ .expect("restore state file permissions for cleanup");
603
+ std::fs::remove_file(&state_path).expect("cleanup state file");
604
+ }
@@ -0,0 +1,20 @@
1
+ [package]
2
+ name = "vault-domain"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+ authors.workspace = true
7
+
8
+ [dependencies]
9
+ alloy-primitives.workspace = true
10
+ alloy-sol-types.workspace = true
11
+ hex.workspace = true
12
+ serde.workspace = true
13
+ sha2.workspace = true
14
+ thiserror.workspace = true
15
+ time.workspace = true
16
+ uuid.workspace = true
17
+ zeroize.workspace = true
18
+
19
+ [dev-dependencies]
20
+ serde_json.workspace = true