@wlfi-agent/cli 1.4.17 → 1.4.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -0
- package/README.md +61 -28
- package/crates/vault-cli-admin/src/io_utils.rs +149 -1
- package/crates/vault-cli-admin/src/main.rs +639 -16
- package/crates/vault-cli-admin/src/shared_config.rs +18 -18
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
- package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
- package/crates/vault-cli-admin/src/tui.rs +1205 -120
- package/crates/vault-cli-agent/Cargo.toml +1 -0
- package/crates/vault-cli-agent/src/io_utils.rs +163 -2
- package/crates/vault-cli-agent/src/main.rs +648 -32
- package/crates/vault-cli-daemon/Cargo.toml +4 -0
- package/crates/vault-cli-daemon/src/main.rs +617 -67
- package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
- package/crates/vault-daemon/src/persistence.rs +637 -100
- package/crates/vault-daemon/src/tests.rs +1013 -3
- package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
- package/crates/vault-domain/src/nonce.rs +4 -0
- package/crates/vault-domain/src/tests.rs +616 -0
- package/crates/vault-policy/src/engine.rs +55 -32
- package/crates/vault-policy/src/tests.rs +195 -0
- package/crates/vault-sdk-agent/src/lib.rs +415 -22
- package/crates/vault-signer/Cargo.toml +3 -0
- package/crates/vault-signer/src/lib.rs +266 -40
- package/crates/vault-transport-unix/src/lib.rs +653 -5
- package/crates/vault-transport-xpc/src/tests.rs +531 -3
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
- package/dist/cli.cjs +756 -194
- package/dist/cli.cjs.map +1 -1
- package/package.json +5 -2
- package/packages/cache/.turbo/turbo-build.log +20 -20
- package/packages/cache/coverage/clover.xml +529 -394
- package/packages/cache/coverage/coverage-final.json +2 -2
- package/packages/cache/coverage/index.html +21 -21
- package/packages/cache/coverage/src/client/index.html +1 -1
- package/packages/cache/coverage/src/client/index.ts.html +1 -1
- package/packages/cache/coverage/src/errors/index.html +1 -1
- package/packages/cache/coverage/src/errors/index.ts.html +12 -12
- package/packages/cache/coverage/src/index.html +1 -1
- package/packages/cache/coverage/src/index.ts.html +1 -1
- package/packages/cache/coverage/src/service/index.html +21 -21
- package/packages/cache/coverage/src/service/index.ts.html +769 -313
- package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
- package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
- package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
- package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
- package/packages/cache/dist/index.cjs +2 -2
- package/packages/cache/dist/index.js +1 -1
- package/packages/cache/dist/service/index.cjs +2 -2
- package/packages/cache/dist/service/index.js +1 -1
- package/packages/cache/node_modules/.bin/tsc +2 -2
- package/packages/cache/node_modules/.bin/tsserver +2 -2
- package/packages/cache/node_modules/.bin/tsup +2 -2
- package/packages/cache/node_modules/.bin/tsup-node +2 -2
- package/packages/cache/node_modules/.bin/vitest +4 -4
- package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/cache/src/service/index.test.ts +165 -19
- package/packages/cache/src/service/index.ts +38 -1
- package/packages/config/.turbo/turbo-build.log +4 -4
- package/packages/config/dist/index.cjs +0 -17
- package/packages/config/dist/index.cjs.map +1 -1
- package/packages/config/src/index.ts +0 -17
- package/packages/rpc/.turbo/turbo-build.log +11 -11
- package/packages/rpc/dist/index.cjs +0 -17
- package/packages/rpc/dist/index.cjs.map +1 -1
- package/packages/rpc/src/index.js +1 -0
- package/packages/ui/node_modules/.bin/tsc +2 -2
- package/packages/ui/node_modules/.bin/tsserver +2 -2
- package/packages/ui/node_modules/.bin/tsup +2 -2
- package/packages/ui/node_modules/.bin/tsup-node +2 -2
- package/scripts/install-cli-launcher.mjs +37 -0
- package/scripts/install-rust-binaries.mjs +47 -0
- package/scripts/run-tests-isolated.mjs +210 -0
- package/src/cli.ts +310 -50
- package/src/lib/admin-reset.ts +101 -33
- package/src/lib/admin-setup.ts +285 -55
- package/src/lib/agent-auth-migrate.ts +5 -1
- package/src/lib/asset-broadcast.ts +15 -4
- package/src/lib/config-amounts.ts +6 -4
- package/src/lib/hidden-tty-prompt.js +1 -0
- package/src/lib/hidden-tty-prompt.ts +105 -0
- package/src/lib/keychain.ts +1 -0
- package/src/lib/local-admin-access.ts +4 -29
- package/src/lib/rust.ts +129 -33
- package/src/lib/signed-tx.ts +1 -0
- package/src/lib/sudo.ts +15 -5
- package/src/lib/wallet-profile.ts +3 -0
- package/src/lib/wallet-setup.ts +52 -0
- package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
- package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
|
@@ -97,6 +97,7 @@ async fn reserve_nonce_is_monotonic_for_same_agent_and_chain() {
|
|
|
97
97
|
agent_auth_token: agent_credentials.auth_token.clone(),
|
|
98
98
|
chain_id: 1,
|
|
99
99
|
min_nonce: 7,
|
|
100
|
+
exact_nonce: false,
|
|
100
101
|
requested_at: now,
|
|
101
102
|
expires_at: now + time::Duration::minutes(2),
|
|
102
103
|
})
|
|
@@ -109,6 +110,7 @@ async fn reserve_nonce_is_monotonic_for_same_agent_and_chain() {
|
|
|
109
110
|
agent_auth_token: agent_credentials.auth_token,
|
|
110
111
|
chain_id: 1,
|
|
111
112
|
min_nonce: 7,
|
|
113
|
+
exact_nonce: false,
|
|
112
114
|
requested_at: now,
|
|
113
115
|
expires_at: now + time::Duration::minutes(2),
|
|
114
116
|
})
|
|
@@ -152,6 +154,7 @@ async fn reserve_nonce_replayed_request_id_is_rejected() {
|
|
|
152
154
|
agent_auth_token: agent_credentials.auth_token,
|
|
153
155
|
chain_id: 1,
|
|
154
156
|
min_nonce: 0,
|
|
157
|
+
exact_nonce: false,
|
|
155
158
|
requested_at: now,
|
|
156
159
|
expires_at: now + time::Duration::minutes(2),
|
|
157
160
|
};
|
|
@@ -203,6 +206,7 @@ async fn release_nonce_reclaims_latest_unused_nonce() {
|
|
|
203
206
|
agent_auth_token: agent_credentials.auth_token.clone(),
|
|
204
207
|
chain_id: 56,
|
|
205
208
|
min_nonce: 0,
|
|
209
|
+
exact_nonce: false,
|
|
206
210
|
requested_at: now,
|
|
207
211
|
expires_at: now + time::Duration::minutes(2),
|
|
208
212
|
})
|
|
@@ -229,6 +233,7 @@ async fn release_nonce_reclaims_latest_unused_nonce() {
|
|
|
229
233
|
agent_auth_token: agent_credentials.auth_token,
|
|
230
234
|
chain_id: 56,
|
|
231
235
|
min_nonce: 0,
|
|
236
|
+
exact_nonce: false,
|
|
232
237
|
requested_at: now,
|
|
233
238
|
expires_at: now + time::Duration::minutes(2),
|
|
234
239
|
})
|
|
@@ -237,6 +242,94 @@ async fn release_nonce_reclaims_latest_unused_nonce() {
|
|
|
237
242
|
assert_eq!(next.nonce, 0);
|
|
238
243
|
}
|
|
239
244
|
|
|
245
|
+
#[tokio::test]
|
|
246
|
+
async fn exact_nonce_can_reuse_chain_nonce_after_signed_request_advances_head() {
|
|
247
|
+
let daemon = InMemoryDaemon::new(
|
|
248
|
+
"vault-password",
|
|
249
|
+
SoftwareSignerBackend::default(),
|
|
250
|
+
DaemonConfig::default(),
|
|
251
|
+
)
|
|
252
|
+
.expect("daemon");
|
|
253
|
+
|
|
254
|
+
let lease = daemon.issue_lease("vault-password").await.expect("lease");
|
|
255
|
+
let session = AdminSession {
|
|
256
|
+
vault_password: "vault-password".to_string(),
|
|
257
|
+
lease,
|
|
258
|
+
};
|
|
259
|
+
daemon
|
|
260
|
+
.add_policy(&session, policy_all_per_tx(1_000_000_000_000_000_000))
|
|
261
|
+
.await
|
|
262
|
+
.expect("add per-tx policy");
|
|
263
|
+
daemon
|
|
264
|
+
.add_policy(&session, policy_per_chain_gas(1, 1_000_000_000_000_000))
|
|
265
|
+
.await
|
|
266
|
+
.expect("add gas policy");
|
|
267
|
+
|
|
268
|
+
let key = daemon
|
|
269
|
+
.create_vault_key(&session, KeyCreateRequest::Generate)
|
|
270
|
+
.await
|
|
271
|
+
.expect("key");
|
|
272
|
+
let agent_credentials = daemon
|
|
273
|
+
.create_agent_key(&session, key.id, PolicyAttachment::AllPolicies)
|
|
274
|
+
.await
|
|
275
|
+
.expect("agent");
|
|
276
|
+
|
|
277
|
+
reserve_nonce_for_agent(&daemon, &agent_credentials, 1, 0).await;
|
|
278
|
+
daemon
|
|
279
|
+
.sign_for_agent(sign_request(
|
|
280
|
+
&agent_credentials,
|
|
281
|
+
AgentAction::BroadcastTx {
|
|
282
|
+
tx: BroadcastTx {
|
|
283
|
+
chain_id: 1,
|
|
284
|
+
nonce: 0,
|
|
285
|
+
to: "0x9300000000000000000000000000000000000000"
|
|
286
|
+
.parse()
|
|
287
|
+
.expect("to"),
|
|
288
|
+
value_wei: 0,
|
|
289
|
+
data_hex: "0x".to_string(),
|
|
290
|
+
gas_limit: 21_000,
|
|
291
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
292
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
293
|
+
tx_type: 0x02,
|
|
294
|
+
delegation_enabled: false,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
))
|
|
298
|
+
.await
|
|
299
|
+
.expect("consume first reservation");
|
|
300
|
+
|
|
301
|
+
let now = time::OffsetDateTime::now_utc();
|
|
302
|
+
let exact = daemon
|
|
303
|
+
.reserve_nonce(NonceReservationRequest {
|
|
304
|
+
request_id: Uuid::new_v4(),
|
|
305
|
+
agent_key_id: agent_credentials.agent_key.id,
|
|
306
|
+
agent_auth_token: agent_credentials.auth_token.clone(),
|
|
307
|
+
chain_id: 1,
|
|
308
|
+
min_nonce: 0,
|
|
309
|
+
exact_nonce: true,
|
|
310
|
+
requested_at: now,
|
|
311
|
+
expires_at: now + time::Duration::minutes(2),
|
|
312
|
+
})
|
|
313
|
+
.await
|
|
314
|
+
.expect("exact reserve");
|
|
315
|
+
assert_eq!(exact.nonce, 0);
|
|
316
|
+
|
|
317
|
+
let next = daemon
|
|
318
|
+
.reserve_nonce(NonceReservationRequest {
|
|
319
|
+
request_id: Uuid::new_v4(),
|
|
320
|
+
agent_key_id: agent_credentials.agent_key.id,
|
|
321
|
+
agent_auth_token: agent_credentials.auth_token,
|
|
322
|
+
chain_id: 1,
|
|
323
|
+
min_nonce: 0,
|
|
324
|
+
exact_nonce: false,
|
|
325
|
+
requested_at: now,
|
|
326
|
+
expires_at: now + time::Duration::minutes(2),
|
|
327
|
+
})
|
|
328
|
+
.await
|
|
329
|
+
.expect("next reserve");
|
|
330
|
+
assert_eq!(next.nonce, 1);
|
|
331
|
+
}
|
|
332
|
+
|
|
240
333
|
#[tokio::test]
|
|
241
334
|
async fn expired_nonce_reservation_is_reclaimed_before_next_reserve() {
|
|
242
335
|
let daemon = InMemoryDaemon::new(
|
|
@@ -275,6 +368,7 @@ async fn expired_nonce_reservation_is_reclaimed_before_next_reserve() {
|
|
|
275
368
|
agent_auth_token: agent_credentials.auth_token.clone(),
|
|
276
369
|
chain_id: 56,
|
|
277
370
|
min_nonce: 0,
|
|
371
|
+
exact_nonce: false,
|
|
278
372
|
requested_at: now,
|
|
279
373
|
expires_at: now + time::Duration::minutes(2),
|
|
280
374
|
})
|
|
@@ -291,6 +385,7 @@ async fn expired_nonce_reservation_is_reclaimed_before_next_reserve() {
|
|
|
291
385
|
agent_auth_token: agent_credentials.auth_token,
|
|
292
386
|
chain_id: 56,
|
|
293
387
|
min_nonce: 0,
|
|
388
|
+
exact_nonce: false,
|
|
294
389
|
requested_at: time::OffsetDateTime::now_utc(),
|
|
295
390
|
expires_at: time::OffsetDateTime::now_utc() + time::Duration::minutes(2),
|
|
296
391
|
})
|
|
@@ -334,6 +429,7 @@ async fn reserve_nonce_rejects_zero_chain_id() {
|
|
|
334
429
|
agent_auth_token: agent_credentials.auth_token,
|
|
335
430
|
chain_id: 0,
|
|
336
431
|
min_nonce: 0,
|
|
432
|
+
exact_nonce: false,
|
|
337
433
|
requested_at: now,
|
|
338
434
|
expires_at: now + time::Duration::minutes(2),
|
|
339
435
|
})
|
|
@@ -382,6 +478,7 @@ async fn release_nonce_replayed_request_id_is_rejected() {
|
|
|
382
478
|
agent_auth_token: agent_credentials.auth_token.clone(),
|
|
383
479
|
chain_id: 1,
|
|
384
480
|
min_nonce: 0,
|
|
481
|
+
exact_nonce: false,
|
|
385
482
|
requested_at: now,
|
|
386
483
|
expires_at: now + time::Duration::minutes(2),
|
|
387
484
|
})
|
|
@@ -449,6 +546,7 @@ async fn release_nonce_rejects_non_owner_agent() {
|
|
|
449
546
|
agent_auth_token: owner_credentials.auth_token.clone(),
|
|
450
547
|
chain_id: 1,
|
|
451
548
|
min_nonce: 0,
|
|
549
|
+
exact_nonce: false,
|
|
452
550
|
requested_at: now,
|
|
453
551
|
expires_at: now + time::Duration::minutes(2),
|
|
454
552
|
})
|
|
@@ -614,6 +712,7 @@ async fn release_nonce_removes_reservation_from_snapshot() {
|
|
|
614
712
|
agent_auth_token: agent_credentials.auth_token.clone(),
|
|
615
713
|
chain_id: 1,
|
|
616
714
|
min_nonce: 0,
|
|
715
|
+
exact_nonce: false,
|
|
617
716
|
requested_at: now,
|
|
618
717
|
expires_at: now + time::Duration::minutes(2),
|
|
619
718
|
})
|
|
@@ -365,6 +365,7 @@ async fn daily_spend_limit_counts_in_scope_history_across_assets_and_chains() {
|
|
|
365
365
|
));
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
#[cfg(not(coverage))]
|
|
368
369
|
#[tokio::test]
|
|
369
370
|
async fn persistent_store_restores_policies_and_agent_auth_state() {
|
|
370
371
|
let state_path = unique_state_path("restore");
|
|
@@ -374,7 +375,7 @@ async fn persistent_store_restores_policies_and_agent_auth_state() {
|
|
|
374
375
|
"vault-password",
|
|
375
376
|
SoftwareSignerBackend::default(),
|
|
376
377
|
config.clone(),
|
|
377
|
-
PersistentStoreConfig::
|
|
378
|
+
PersistentStoreConfig::new_test(state_path.clone()),
|
|
378
379
|
)
|
|
379
380
|
.expect("daemon");
|
|
380
381
|
|
|
@@ -420,7 +421,7 @@ async fn persistent_store_restores_policies_and_agent_auth_state() {
|
|
|
420
421
|
"vault-password",
|
|
421
422
|
SoftwareSignerBackend::default(),
|
|
422
423
|
config,
|
|
423
|
-
PersistentStoreConfig::
|
|
424
|
+
PersistentStoreConfig::new_test(state_path.clone()),
|
|
424
425
|
)
|
|
425
426
|
.expect("restarted daemon");
|
|
426
427
|
let lease = restarted
|
|
@@ -463,6 +464,7 @@ async fn persistent_store_restores_policies_and_agent_auth_state() {
|
|
|
463
464
|
std::fs::remove_file(&state_path).expect("cleanup");
|
|
464
465
|
}
|
|
465
466
|
|
|
467
|
+
#[cfg(not(coverage))]
|
|
466
468
|
#[tokio::test]
|
|
467
469
|
async fn persistent_store_rejects_wrong_password() {
|
|
468
470
|
let state_path = unique_state_path("wrong-password");
|
|
@@ -472,7 +474,7 @@ async fn persistent_store_rejects_wrong_password() {
|
|
|
472
474
|
"vault-password",
|
|
473
475
|
SoftwareSignerBackend::default(),
|
|
474
476
|
config.clone(),
|
|
475
|
-
PersistentStoreConfig::
|
|
477
|
+
PersistentStoreConfig::new_test(state_path.clone()),
|
|
476
478
|
)
|
|
477
479
|
.expect("daemon");
|
|
478
480
|
let lease = daemon.issue_lease("vault-password").await.expect("lease");
|
|
@@ -490,7 +492,7 @@ async fn persistent_store_rejects_wrong_password() {
|
|
|
490
492
|
"wrong-password",
|
|
491
493
|
SoftwareSignerBackend::default(),
|
|
492
494
|
config,
|
|
493
|
-
PersistentStoreConfig::
|
|
495
|
+
PersistentStoreConfig::new_test(state_path.clone()),
|
|
494
496
|
) {
|
|
495
497
|
Ok(_) => panic!("wrong password must fail state load"),
|
|
496
498
|
Err(err) => err,
|
|
@@ -519,6 +521,7 @@ fn validate_loaded_state_rejects_insecure_remote_http_relay_url() {
|
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
#[cfg(unix)]
|
|
524
|
+
#[cfg(not(coverage))]
|
|
522
525
|
#[tokio::test]
|
|
523
526
|
async fn persistent_store_rejects_group_writable_ancestor_directory() {
|
|
524
527
|
use std::os::unix::fs::PermissionsExt;
|
|
@@ -542,7 +545,7 @@ async fn persistent_store_rejects_group_writable_ancestor_directory() {
|
|
|
542
545
|
"vault-password",
|
|
543
546
|
SoftwareSignerBackend::default(),
|
|
544
547
|
DaemonConfig::default(),
|
|
545
|
-
PersistentStoreConfig::
|
|
548
|
+
PersistentStoreConfig::new_test(leaf.join("daemon-state.enc")),
|
|
546
549
|
) {
|
|
547
550
|
Ok(_) => panic!("group-writable ancestor must be rejected"),
|
|
548
551
|
Err(err) => err,
|
|
@@ -557,6 +560,7 @@ async fn persistent_store_rejects_group_writable_ancestor_directory() {
|
|
|
557
560
|
}
|
|
558
561
|
|
|
559
562
|
#[cfg(unix)]
|
|
563
|
+
#[cfg(not(coverage))]
|
|
560
564
|
#[tokio::test]
|
|
561
565
|
async fn persistent_store_rejects_group_readable_state_file() {
|
|
562
566
|
use std::os::unix::fs::PermissionsExt;
|
|
@@ -568,7 +572,7 @@ async fn persistent_store_rejects_group_readable_state_file() {
|
|
|
568
572
|
"vault-password",
|
|
569
573
|
SoftwareSignerBackend::default(),
|
|
570
574
|
config.clone(),
|
|
571
|
-
PersistentStoreConfig::
|
|
575
|
+
PersistentStoreConfig::new_test(state_path.clone()),
|
|
572
576
|
)
|
|
573
577
|
.expect("daemon");
|
|
574
578
|
let lease = daemon.issue_lease("vault-password").await.expect("lease");
|
|
@@ -589,7 +593,7 @@ async fn persistent_store_rejects_group_readable_state_file() {
|
|
|
589
593
|
"vault-password",
|
|
590
594
|
SoftwareSignerBackend::default(),
|
|
591
595
|
config,
|
|
592
|
-
PersistentStoreConfig::
|
|
596
|
+
PersistentStoreConfig::new_test(state_path.clone()),
|
|
593
597
|
) {
|
|
594
598
|
Ok(_) => panic!("group-readable state file must be rejected"),
|
|
595
599
|
Err(err) => err,
|
|
@@ -18,6 +18,9 @@ pub struct NonceReservationRequest {
|
|
|
18
18
|
pub chain_id: u64,
|
|
19
19
|
/// Minimum nonce the caller is willing to reserve.
|
|
20
20
|
pub min_nonce: u64,
|
|
21
|
+
/// When true, reserve exactly `min_nonce` instead of allowing a higher nonce head.
|
|
22
|
+
#[serde(default)]
|
|
23
|
+
pub exact_nonce: bool,
|
|
21
24
|
/// Request timestamp.
|
|
22
25
|
pub requested_at: OffsetDateTime,
|
|
23
26
|
/// Request expiry timestamp.
|
|
@@ -33,6 +36,7 @@ impl fmt::Debug for NonceReservationRequest {
|
|
|
33
36
|
.field("agent_auth_token", &"<redacted>")
|
|
34
37
|
.field("chain_id", &self.chain_id)
|
|
35
38
|
.field("min_nonce", &self.min_nonce)
|
|
39
|
+
.field("exact_nonce", &self.exact_nonce)
|
|
36
40
|
.field("requested_at", &self.requested_at)
|
|
37
41
|
.field("expires_at", &self.expires_at)
|
|
38
42
|
.finish()
|