@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,773 @@
1
+ #![cfg(target_os = "macos")]
2
+
3
+ use std::sync::Arc;
4
+ use std::time::Duration;
5
+
6
+ use serde_json::to_vec;
7
+ use time::OffsetDateTime;
8
+ use vault_daemon::{DaemonConfig, DaemonError, InMemoryDaemon, KeyManagerDaemonApi};
9
+ use vault_domain::{
10
+ AdminSession, AgentAction, BroadcastTx, EntityScope, EvmAddress, NonceReservationRequest,
11
+ PolicyAttachment, PolicyType, SignRequest, SpendingPolicy,
12
+ };
13
+ use vault_signer::{KeyCreateRequest, SoftwareSignerBackend};
14
+ use vault_transport_xpc::{XpcDaemonClient, XpcDaemonServer};
15
+
16
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
17
+ async fn e2e_policy_enforced_over_xpc() {
18
+ #[cfg(not(debug_assertions))]
19
+ if unsafe { libc::geteuid() } != 0 {
20
+ return;
21
+ }
22
+
23
+ let daemon = Arc::new(
24
+ InMemoryDaemon::new(
25
+ "vault-password",
26
+ SoftwareSignerBackend::default(),
27
+ DaemonConfig::default(),
28
+ )
29
+ .expect("daemon"),
30
+ );
31
+
32
+ #[cfg(debug_assertions)]
33
+ let server = XpcDaemonServer::start_inmemory_with_allowed_euid(
34
+ daemon,
35
+ tokio::runtime::Handle::current(),
36
+ unsafe { libc::geteuid() },
37
+ )
38
+ .expect("server");
39
+ #[cfg(not(debug_assertions))]
40
+ let server =
41
+ XpcDaemonServer::start_inmemory(daemon, tokio::runtime::Handle::current()).expect("server");
42
+ let client =
43
+ XpcDaemonClient::connect(&server.endpoint(), Duration::from_secs(5)).expect("client");
44
+
45
+ let lease = client.issue_lease("vault-password").await.expect("lease");
46
+ let admin = AdminSession {
47
+ vault_password: "vault-password".to_string(),
48
+ lease,
49
+ };
50
+
51
+ let policy = SpendingPolicy::new(
52
+ 0,
53
+ PolicyType::PerTxMaxSpending,
54
+ 100,
55
+ EntityScope::All,
56
+ EntityScope::All,
57
+ EntityScope::All,
58
+ )
59
+ .expect("policy");
60
+
61
+ client.add_policy(&admin, policy).await.expect("add_policy");
62
+
63
+ let vault_key = client
64
+ .create_vault_key(&admin, KeyCreateRequest::Generate)
65
+ .await
66
+ .expect("create_vault_key");
67
+
68
+ let agent_credentials = client
69
+ .create_agent_key(&admin, vault_key.id, PolicyAttachment::AllPolicies)
70
+ .await
71
+ .expect("create_agent_key");
72
+
73
+ let token: EvmAddress = "0x1000000000000000000000000000000000000000"
74
+ .parse()
75
+ .expect("token");
76
+ let to: EvmAddress = "0x2000000000000000000000000000000000000000"
77
+ .parse()
78
+ .expect("recipient");
79
+
80
+ let allow_request = SignRequest {
81
+ request_id: uuid::Uuid::new_v4(),
82
+ agent_key_id: agent_credentials.agent_key.id,
83
+ agent_auth_token: agent_credentials.auth_token.clone(),
84
+ payload: to_vec(&AgentAction::Transfer {
85
+ chain_id: 1,
86
+ token: token.clone(),
87
+ to: to.clone(),
88
+ amount_wei: 100,
89
+ })
90
+ .expect("payload"),
91
+ action: AgentAction::Transfer {
92
+ chain_id: 1,
93
+ token: token.clone(),
94
+ to: to.clone(),
95
+ amount_wei: 100,
96
+ },
97
+ requested_at: OffsetDateTime::now_utc(),
98
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
99
+ };
100
+ client
101
+ .reserve_nonce(NonceReservationRequest {
102
+ request_id: uuid::Uuid::new_v4(),
103
+ agent_key_id: agent_credentials.agent_key.id,
104
+ agent_auth_token: agent_credentials.auth_token.clone(),
105
+ chain_id: 1,
106
+ min_nonce: 0,
107
+ requested_at: OffsetDateTime::now_utc(),
108
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
109
+ })
110
+ .await
111
+ .expect("nonce reservation must pass");
112
+
113
+ let signature = client
114
+ .sign_for_agent(allow_request)
115
+ .await
116
+ .expect("signature should pass");
117
+ assert!(!signature.bytes.is_empty());
118
+
119
+ let deny_request = SignRequest {
120
+ request_id: uuid::Uuid::new_v4(),
121
+ agent_key_id: agent_credentials.agent_key.id,
122
+ agent_auth_token: agent_credentials.auth_token.clone(),
123
+ payload: to_vec(&AgentAction::Transfer {
124
+ chain_id: 1,
125
+ token: token.clone(),
126
+ to: to.clone(),
127
+ amount_wei: 101,
128
+ })
129
+ .expect("payload"),
130
+ action: AgentAction::Transfer {
131
+ chain_id: 1,
132
+ token,
133
+ to,
134
+ amount_wei: 101,
135
+ },
136
+ requested_at: OffsetDateTime::now_utc(),
137
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
138
+ };
139
+
140
+ let err = client
141
+ .sign_for_agent(deny_request)
142
+ .await
143
+ .expect_err("request must be denied by policy");
144
+
145
+ assert!(matches!(err, DaemonError::Policy(_)));
146
+
147
+ let bad_auth_request = SignRequest {
148
+ request_id: uuid::Uuid::new_v4(),
149
+ agent_key_id: agent_credentials.agent_key.id,
150
+ agent_auth_token: "not-the-issued-token".to_string(),
151
+ payload: to_vec(&AgentAction::Transfer {
152
+ chain_id: 1,
153
+ token: "0x1000000000000000000000000000000000000000"
154
+ .parse()
155
+ .expect("token"),
156
+ to: "0x2000000000000000000000000000000000000000"
157
+ .parse()
158
+ .expect("recipient"),
159
+ amount_wei: 1,
160
+ })
161
+ .expect("payload"),
162
+ action: AgentAction::Transfer {
163
+ chain_id: 1,
164
+ token: "0x1000000000000000000000000000000000000000"
165
+ .parse()
166
+ .expect("token"),
167
+ to: "0x2000000000000000000000000000000000000000"
168
+ .parse()
169
+ .expect("recipient"),
170
+ amount_wei: 1,
171
+ },
172
+ requested_at: OffsetDateTime::now_utc(),
173
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
174
+ };
175
+
176
+ let err = client
177
+ .sign_for_agent(bad_auth_request)
178
+ .await
179
+ .expect_err("request with bad token must fail");
180
+ assert!(matches!(err, DaemonError::AgentAuthenticationFailed));
181
+
182
+ let payload_mismatch_request = SignRequest {
183
+ request_id: uuid::Uuid::new_v4(),
184
+ agent_key_id: agent_credentials.agent_key.id,
185
+ agent_auth_token: agent_credentials.auth_token.clone(),
186
+ payload: to_vec(&AgentAction::Transfer {
187
+ chain_id: 1,
188
+ token: "0x1000000000000000000000000000000000000000"
189
+ .parse()
190
+ .expect("token"),
191
+ to: "0x2000000000000000000000000000000000000000"
192
+ .parse()
193
+ .expect("recipient"),
194
+ amount_wei: 99,
195
+ })
196
+ .expect("payload"),
197
+ action: AgentAction::Transfer {
198
+ chain_id: 1,
199
+ token: "0x1000000000000000000000000000000000000000"
200
+ .parse()
201
+ .expect("token"),
202
+ to: "0x2000000000000000000000000000000000000000"
203
+ .parse()
204
+ .expect("recipient"),
205
+ amount_wei: 1,
206
+ },
207
+ requested_at: OffsetDateTime::now_utc(),
208
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
209
+ };
210
+
211
+ let err = client
212
+ .sign_for_agent(payload_mismatch_request)
213
+ .await
214
+ .expect_err("payload/action mismatch must fail");
215
+ assert!(matches!(err, DaemonError::PayloadActionMismatch));
216
+
217
+ let malformed_payload_request = SignRequest {
218
+ request_id: uuid::Uuid::new_v4(),
219
+ agent_key_id: agent_credentials.agent_key.id,
220
+ agent_auth_token: agent_credentials.auth_token,
221
+ payload: b"not-json".to_vec(),
222
+ action: AgentAction::Transfer {
223
+ chain_id: 1,
224
+ token: "0x1000000000000000000000000000000000000000"
225
+ .parse()
226
+ .expect("token"),
227
+ to: "0x2000000000000000000000000000000000000000"
228
+ .parse()
229
+ .expect("recipient"),
230
+ amount_wei: 1,
231
+ },
232
+ requested_at: OffsetDateTime::now_utc(),
233
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
234
+ };
235
+
236
+ let err = client
237
+ .sign_for_agent(malformed_payload_request)
238
+ .await
239
+ .expect_err("malformed payload must fail");
240
+ assert!(matches!(err, DaemonError::PayloadActionMismatch));
241
+ }
242
+
243
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
244
+ async fn e2e_broadcast_gas_policy_enforced_over_xpc() {
245
+ #[cfg(not(debug_assertions))]
246
+ if unsafe { libc::geteuid() } != 0 {
247
+ return;
248
+ }
249
+
250
+ let daemon = Arc::new(
251
+ InMemoryDaemon::new(
252
+ "vault-password",
253
+ SoftwareSignerBackend::default(),
254
+ DaemonConfig::default(),
255
+ )
256
+ .expect("daemon"),
257
+ );
258
+
259
+ #[cfg(debug_assertions)]
260
+ let server = XpcDaemonServer::start_inmemory_with_allowed_euid(
261
+ daemon,
262
+ tokio::runtime::Handle::current(),
263
+ unsafe { libc::geteuid() },
264
+ )
265
+ .expect("server");
266
+ #[cfg(not(debug_assertions))]
267
+ let server =
268
+ XpcDaemonServer::start_inmemory(daemon, tokio::runtime::Handle::current()).expect("server");
269
+ let client =
270
+ XpcDaemonClient::connect(&server.endpoint(), Duration::from_secs(5)).expect("client");
271
+
272
+ let lease = client.issue_lease("vault-password").await.expect("lease");
273
+ let admin = AdminSession {
274
+ vault_password: "vault-password".to_string(),
275
+ lease,
276
+ };
277
+
278
+ client
279
+ .add_policy(
280
+ &admin,
281
+ SpendingPolicy::new(
282
+ 0,
283
+ PolicyType::PerTxMaxSpending,
284
+ 1_000_000_000_000_000_000,
285
+ EntityScope::All,
286
+ EntityScope::All,
287
+ EntityScope::Set(std::collections::BTreeSet::from([1_u64])),
288
+ )
289
+ .expect("per-tx policy"),
290
+ )
291
+ .await
292
+ .expect("add per-tx policy");
293
+ client
294
+ .add_policy(
295
+ &admin,
296
+ SpendingPolicy::new(
297
+ 1,
298
+ PolicyType::PerChainMaxGasSpend,
299
+ 1_000_000_000_000_000,
300
+ EntityScope::All,
301
+ EntityScope::All,
302
+ EntityScope::Set(std::collections::BTreeSet::from([1_u64])),
303
+ )
304
+ .expect("gas policy"),
305
+ )
306
+ .await
307
+ .expect("add gas policy");
308
+
309
+ let vault_key = client
310
+ .create_vault_key(&admin, KeyCreateRequest::Generate)
311
+ .await
312
+ .expect("create_vault_key");
313
+ let agent_credentials = client
314
+ .create_agent_key(&admin, vault_key.id, PolicyAttachment::AllPolicies)
315
+ .await
316
+ .expect("create_agent_key");
317
+
318
+ let allow_action = AgentAction::BroadcastTx {
319
+ tx: BroadcastTx {
320
+ chain_id: 1,
321
+ nonce: 0,
322
+ to: "0x3000000000000000000000000000000000000000"
323
+ .parse()
324
+ .expect("to"),
325
+ value_wei: 0,
326
+ data_hex: "0xdeadbeef".to_string(),
327
+ gas_limit: 200_000,
328
+ max_fee_per_gas_wei: 1_000_000_000,
329
+ max_priority_fee_per_gas_wei: 1_000_000_000,
330
+ tx_type: 0x02,
331
+ delegation_enabled: false,
332
+ },
333
+ };
334
+ let allow_reservation = client
335
+ .reserve_nonce(NonceReservationRequest {
336
+ request_id: uuid::Uuid::new_v4(),
337
+ agent_key_id: agent_credentials.agent_key.id,
338
+ agent_auth_token: agent_credentials.auth_token.clone(),
339
+ chain_id: 1,
340
+ min_nonce: 0,
341
+ requested_at: OffsetDateTime::now_utc(),
342
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
343
+ })
344
+ .await
345
+ .expect("reserve nonce for allow broadcast");
346
+ assert_eq!(allow_reservation.nonce, 0);
347
+
348
+ let allow_request = SignRequest {
349
+ request_id: uuid::Uuid::new_v4(),
350
+ agent_key_id: agent_credentials.agent_key.id,
351
+ agent_auth_token: agent_credentials.auth_token.clone(),
352
+ payload: to_vec(&allow_action).expect("payload"),
353
+ action: allow_action,
354
+ requested_at: OffsetDateTime::now_utc(),
355
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
356
+ };
357
+
358
+ let signature = client
359
+ .sign_for_agent(allow_request)
360
+ .await
361
+ .expect("broadcast request should pass");
362
+ assert!(!signature.bytes.is_empty());
363
+
364
+ let deny_action = AgentAction::BroadcastTx {
365
+ tx: BroadcastTx {
366
+ chain_id: 1,
367
+ nonce: 1,
368
+ to: "0x3000000000000000000000000000000000000000"
369
+ .parse()
370
+ .expect("to"),
371
+ value_wei: 0,
372
+ data_hex: "0xdeadbeef".to_string(),
373
+ gas_limit: 2_000_000,
374
+ max_fee_per_gas_wei: 1_000_000_000,
375
+ max_priority_fee_per_gas_wei: 1_000_000_000,
376
+ tx_type: 0x02,
377
+ delegation_enabled: false,
378
+ },
379
+ };
380
+ let deny_reservation = client
381
+ .reserve_nonce(NonceReservationRequest {
382
+ request_id: uuid::Uuid::new_v4(),
383
+ agent_key_id: agent_credentials.agent_key.id,
384
+ agent_auth_token: agent_credentials.auth_token.clone(),
385
+ chain_id: 1,
386
+ min_nonce: 1,
387
+ requested_at: OffsetDateTime::now_utc(),
388
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
389
+ })
390
+ .await
391
+ .expect("reserve nonce for deny broadcast");
392
+ assert_eq!(deny_reservation.nonce, 1);
393
+
394
+ let deny_request = SignRequest {
395
+ request_id: uuid::Uuid::new_v4(),
396
+ agent_key_id: agent_credentials.agent_key.id,
397
+ agent_auth_token: agent_credentials.auth_token.clone(),
398
+ payload: to_vec(&deny_action).expect("payload"),
399
+ action: deny_action,
400
+ requested_at: OffsetDateTime::now_utc(),
401
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
402
+ };
403
+
404
+ let err = client
405
+ .sign_for_agent(deny_request)
406
+ .await
407
+ .expect_err("gas policy should reject request");
408
+ assert!(matches!(err, DaemonError::Policy(_)));
409
+
410
+ client
411
+ .release_nonce(vault_domain::NonceReleaseRequest {
412
+ request_id: uuid::Uuid::new_v4(),
413
+ agent_key_id: agent_credentials.agent_key.id,
414
+ agent_auth_token: agent_credentials.auth_token,
415
+ reservation_id: deny_reservation.reservation_id,
416
+ requested_at: OffsetDateTime::now_utc(),
417
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
418
+ })
419
+ .await
420
+ .expect("release deny reservation");
421
+ }
422
+
423
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
424
+ async fn e2e_tx_metadata_policies_skip_non_broadcast_actions_over_xpc() {
425
+ #[cfg(not(debug_assertions))]
426
+ if unsafe { libc::geteuid() } != 0 {
427
+ return;
428
+ }
429
+
430
+ let daemon = Arc::new(
431
+ InMemoryDaemon::new(
432
+ "vault-password",
433
+ SoftwareSignerBackend::default(),
434
+ DaemonConfig::default(),
435
+ )
436
+ .expect("daemon"),
437
+ );
438
+
439
+ #[cfg(debug_assertions)]
440
+ let server = XpcDaemonServer::start_inmemory_with_allowed_euid(
441
+ daemon,
442
+ tokio::runtime::Handle::current(),
443
+ unsafe { libc::geteuid() },
444
+ )
445
+ .expect("server");
446
+ #[cfg(not(debug_assertions))]
447
+ let server =
448
+ XpcDaemonServer::start_inmemory(daemon, tokio::runtime::Handle::current()).expect("server");
449
+ let client =
450
+ XpcDaemonClient::connect(&server.endpoint(), Duration::from_secs(5)).expect("client");
451
+
452
+ let lease = client.issue_lease("vault-password").await.expect("lease");
453
+ let admin = AdminSession {
454
+ vault_password: "vault-password".to_string(),
455
+ lease,
456
+ };
457
+
458
+ client
459
+ .add_policy(
460
+ &admin,
461
+ SpendingPolicy::new(
462
+ 0,
463
+ PolicyType::PerTxMaxSpending,
464
+ 1_000_000_000_000_000_000,
465
+ EntityScope::All,
466
+ EntityScope::All,
467
+ EntityScope::All,
468
+ )
469
+ .expect("per-tx policy"),
470
+ )
471
+ .await
472
+ .expect("add spend policy");
473
+ client
474
+ .add_policy(
475
+ &admin,
476
+ SpendingPolicy::new(
477
+ 1,
478
+ PolicyType::PerChainMaxGasSpend,
479
+ 1_000_000_000_000_000,
480
+ EntityScope::All,
481
+ EntityScope::All,
482
+ EntityScope::All,
483
+ )
484
+ .expect("gas policy"),
485
+ )
486
+ .await
487
+ .expect("add gas policy");
488
+
489
+ let vault_key = client
490
+ .create_vault_key(&admin, KeyCreateRequest::Generate)
491
+ .await
492
+ .expect("create_vault_key");
493
+ let agent_credentials = client
494
+ .create_agent_key(&admin, vault_key.id, PolicyAttachment::AllPolicies)
495
+ .await
496
+ .expect("create_agent_key");
497
+
498
+ let action = AgentAction::Transfer {
499
+ chain_id: 1,
500
+ token: "0x5000000000000000000000000000000000000000"
501
+ .parse()
502
+ .expect("token"),
503
+ to: "0x6000000000000000000000000000000000000000"
504
+ .parse()
505
+ .expect("recipient"),
506
+ amount_wei: 1,
507
+ };
508
+ let request = SignRequest {
509
+ request_id: uuid::Uuid::new_v4(),
510
+ agent_key_id: agent_credentials.agent_key.id,
511
+ agent_auth_token: agent_credentials.auth_token,
512
+ payload: to_vec(&action).expect("payload"),
513
+ action,
514
+ requested_at: OffsetDateTime::now_utc(),
515
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
516
+ };
517
+
518
+ client
519
+ .sign_for_agent(request)
520
+ .await
521
+ .expect("non-broadcast action should skip tx metadata policies");
522
+ }
523
+
524
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
525
+ async fn e2e_lifecycle_controls_and_policy_evaluation_over_xpc() {
526
+ #[cfg(not(debug_assertions))]
527
+ if unsafe { libc::geteuid() } != 0 {
528
+ return;
529
+ }
530
+
531
+ let daemon = Arc::new(
532
+ InMemoryDaemon::new(
533
+ "vault-password",
534
+ SoftwareSignerBackend::default(),
535
+ DaemonConfig::default(),
536
+ )
537
+ .expect("daemon"),
538
+ );
539
+
540
+ #[cfg(debug_assertions)]
541
+ let server = XpcDaemonServer::start_inmemory_with_allowed_euid(
542
+ daemon,
543
+ tokio::runtime::Handle::current(),
544
+ unsafe { libc::geteuid() },
545
+ )
546
+ .expect("server");
547
+ #[cfg(not(debug_assertions))]
548
+ let server =
549
+ XpcDaemonServer::start_inmemory(daemon, tokio::runtime::Handle::current()).expect("server");
550
+ let client =
551
+ XpcDaemonClient::connect(&server.endpoint(), Duration::from_secs(5)).expect("client");
552
+
553
+ let lease = client.issue_lease("vault-password").await.expect("lease");
554
+ let admin = AdminSession {
555
+ vault_password: "vault-password".to_string(),
556
+ lease,
557
+ };
558
+
559
+ client
560
+ .add_policy(
561
+ &admin,
562
+ SpendingPolicy::new(
563
+ 0,
564
+ PolicyType::PerTxMaxSpending,
565
+ 1_000_000_000_000_000_000,
566
+ EntityScope::All,
567
+ EntityScope::All,
568
+ EntityScope::All,
569
+ )
570
+ .expect("per-tx policy"),
571
+ )
572
+ .await
573
+ .expect("add spend policy");
574
+ client
575
+ .add_policy(
576
+ &admin,
577
+ SpendingPolicy::new(
578
+ 1,
579
+ PolicyType::DailyMaxTxCount,
580
+ 1,
581
+ EntityScope::All,
582
+ EntityScope::All,
583
+ EntityScope::All,
584
+ )
585
+ .expect("tx-count policy"),
586
+ )
587
+ .await
588
+ .expect("add tx-count policy");
589
+
590
+ let vault_key = client
591
+ .create_vault_key(&admin, KeyCreateRequest::Generate)
592
+ .await
593
+ .expect("create_vault_key");
594
+ let agent_credentials = client
595
+ .create_agent_key(&admin, vault_key.id, PolicyAttachment::AllPolicies)
596
+ .await
597
+ .expect("create_agent_key");
598
+
599
+ let action = AgentAction::Transfer {
600
+ chain_id: 1,
601
+ token: "0x5000000000000000000000000000000000000000"
602
+ .parse()
603
+ .expect("token"),
604
+ to: "0x6000000000000000000000000000000000000000"
605
+ .parse()
606
+ .expect("recipient"),
607
+ amount_wei: 1,
608
+ };
609
+ let request = SignRequest {
610
+ request_id: uuid::Uuid::new_v4(),
611
+ agent_key_id: agent_credentials.agent_key.id,
612
+ agent_auth_token: agent_credentials.auth_token.clone(),
613
+ payload: to_vec(&action).expect("payload"),
614
+ action: action.clone(),
615
+ requested_at: OffsetDateTime::now_utc(),
616
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
617
+ };
618
+
619
+ let evaluation = client
620
+ .evaluate_for_agent(request.clone())
621
+ .await
622
+ .expect("policy evaluation should pass");
623
+ assert_eq!(evaluation.evaluated_policy_ids.len(), 2);
624
+
625
+ let rotated_token = client
626
+ .rotate_agent_auth_token(&admin, agent_credentials.agent_key.id)
627
+ .await
628
+ .expect("rotate token");
629
+ assert_ne!(rotated_token, agent_credentials.auth_token);
630
+
631
+ let err = client
632
+ .sign_for_agent(request.clone())
633
+ .await
634
+ .expect_err("old auth token must fail after rotation");
635
+ assert!(matches!(err, DaemonError::AgentAuthenticationFailed));
636
+
637
+ let rotated_request = SignRequest {
638
+ request_id: uuid::Uuid::new_v4(),
639
+ agent_auth_token: rotated_token,
640
+ ..request
641
+ };
642
+ let signature = client
643
+ .sign_for_agent(rotated_request.clone())
644
+ .await
645
+ .expect("rotated token should sign");
646
+ assert!(!signature.bytes.is_empty());
647
+
648
+ let err = client
649
+ .evaluate_for_agent(rotated_request.clone())
650
+ .await
651
+ .expect_err("daily tx count policy should deny second tx after a signed spend");
652
+ assert!(matches!(err, DaemonError::Policy(_)));
653
+
654
+ client
655
+ .revoke_agent_key(&admin, agent_credentials.agent_key.id)
656
+ .await
657
+ .expect("revoke key");
658
+ let err = client
659
+ .sign_for_agent(rotated_request)
660
+ .await
661
+ .expect_err("revoked key must not sign");
662
+ assert!(matches!(err, DaemonError::AgentAuthenticationFailed));
663
+ }
664
+
665
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
666
+ async fn e2e_disable_policy_over_xpc() {
667
+ #[cfg(not(debug_assertions))]
668
+ if unsafe { libc::geteuid() } != 0 {
669
+ return;
670
+ }
671
+
672
+ let daemon = Arc::new(
673
+ InMemoryDaemon::new(
674
+ "vault-password",
675
+ SoftwareSignerBackend::default(),
676
+ DaemonConfig::default(),
677
+ )
678
+ .expect("daemon"),
679
+ );
680
+
681
+ #[cfg(debug_assertions)]
682
+ let server = XpcDaemonServer::start_inmemory_with_allowed_euid(
683
+ daemon,
684
+ tokio::runtime::Handle::current(),
685
+ unsafe { libc::geteuid() },
686
+ )
687
+ .expect("server");
688
+ #[cfg(not(debug_assertions))]
689
+ let server =
690
+ XpcDaemonServer::start_inmemory(daemon, tokio::runtime::Handle::current()).expect("server");
691
+ let client =
692
+ XpcDaemonClient::connect(&server.endpoint(), Duration::from_secs(5)).expect("client");
693
+
694
+ let lease = client.issue_lease("vault-password").await.expect("lease");
695
+ let admin = AdminSession {
696
+ vault_password: "vault-password".to_string(),
697
+ lease,
698
+ };
699
+
700
+ let strict_policy = SpendingPolicy::new(
701
+ 0,
702
+ PolicyType::PerTxMaxSpending,
703
+ 100,
704
+ EntityScope::All,
705
+ EntityScope::All,
706
+ EntityScope::All,
707
+ )
708
+ .expect("strict policy");
709
+ client
710
+ .add_policy(&admin, strict_policy.clone())
711
+ .await
712
+ .expect("add strict policy");
713
+ client
714
+ .add_policy(
715
+ &admin,
716
+ SpendingPolicy::new(
717
+ 1,
718
+ PolicyType::PerTxMaxSpending,
719
+ 1_000,
720
+ EntityScope::All,
721
+ EntityScope::All,
722
+ EntityScope::All,
723
+ )
724
+ .expect("permissive policy"),
725
+ )
726
+ .await
727
+ .expect("add permissive policy");
728
+
729
+ let vault_key = client
730
+ .create_vault_key(&admin, KeyCreateRequest::Generate)
731
+ .await
732
+ .expect("create_vault_key");
733
+ let agent_credentials = client
734
+ .create_agent_key(&admin, vault_key.id, PolicyAttachment::AllPolicies)
735
+ .await
736
+ .expect("create_agent_key");
737
+
738
+ let action = AgentAction::Transfer {
739
+ chain_id: 1,
740
+ token: "0x7000000000000000000000000000000000000000"
741
+ .parse()
742
+ .expect("token"),
743
+ to: "0x8000000000000000000000000000000000000000"
744
+ .parse()
745
+ .expect("recipient"),
746
+ amount_wei: 500,
747
+ };
748
+ let request = SignRequest {
749
+ request_id: uuid::Uuid::new_v4(),
750
+ agent_key_id: agent_credentials.agent_key.id,
751
+ agent_auth_token: agent_credentials.auth_token,
752
+ payload: to_vec(&action).expect("payload"),
753
+ action,
754
+ requested_at: OffsetDateTime::now_utc(),
755
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
756
+ };
757
+
758
+ let err = client
759
+ .sign_for_agent(request.clone())
760
+ .await
761
+ .expect_err("strict policy should reject");
762
+ assert!(matches!(err, DaemonError::Policy(_)));
763
+
764
+ client
765
+ .disable_policy(&admin, strict_policy.id)
766
+ .await
767
+ .expect("disable strict policy");
768
+ let signature = client
769
+ .sign_for_agent(request)
770
+ .await
771
+ .expect("request should pass after strict policy is disabled");
772
+ assert!(!signature.bytes.is_empty());
773
+ }