@wlfi-agent/cli 1.4.12 → 1.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +3968 -0
- package/Cargo.toml +50 -0
- package/README.md +426 -6
- package/crates/vault-cli-admin/Cargo.toml +26 -0
- package/crates/vault-cli-admin/src/io_utils.rs +500 -0
- package/crates/vault-cli-admin/src/main.rs +3990 -0
- package/crates/vault-cli-admin/src/shared_config.rs +624 -0
- package/crates/vault-cli-admin/src/tui/amounts.rs +180 -0
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +250 -0
- package/crates/vault-cli-admin/src/tui/utils.rs +82 -0
- package/crates/vault-cli-admin/src/tui.rs +3410 -0
- package/crates/vault-cli-agent/Cargo.toml +24 -0
- package/crates/vault-cli-agent/src/io_utils.rs +576 -0
- package/crates/vault-cli-agent/src/main.rs +833 -0
- package/crates/vault-cli-daemon/Cargo.toml +28 -0
- package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +216 -0
- package/crates/vault-cli-daemon/src/main.rs +644 -0
- package/crates/vault-cli-daemon/src/relay_sync.rs +894 -0
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +167 -0
- package/crates/vault-daemon/Cargo.toml +32 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +1041 -0
- package/crates/vault-daemon/src/daemon_parts/core_helpers.rs +1256 -0
- package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +622 -0
- package/crates/vault-daemon/src/lib.rs +54 -0
- package/crates/vault-daemon/src/persistence.rs +441 -0
- package/crates/vault-daemon/src/tests.rs +237 -0
- package/crates/vault-daemon/src/tests_parts/part1.rs +1224 -0
- package/crates/vault-daemon/src/tests_parts/part2.rs +1021 -0
- package/crates/vault-daemon/src/tests_parts/part3.rs +835 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +604 -0
- package/crates/vault-domain/Cargo.toml +20 -0
- package/crates/vault-domain/src/action.rs +849 -0
- package/crates/vault-domain/src/address.rs +51 -0
- package/crates/vault-domain/src/approval.rs +90 -0
- package/crates/vault-domain/src/constants.rs +4 -0
- package/crates/vault-domain/src/error.rs +54 -0
- package/crates/vault-domain/src/keys.rs +71 -0
- package/crates/vault-domain/src/lib.rs +42 -0
- package/crates/vault-domain/src/nonce.rs +102 -0
- package/crates/vault-domain/src/policy.rs +172 -0
- package/crates/vault-domain/src/request.rs +53 -0
- package/crates/vault-domain/src/scope.rs +24 -0
- package/crates/vault-domain/src/session.rs +50 -0
- package/crates/vault-domain/src/signature.rs +34 -0
- package/crates/vault-domain/src/tests.rs +651 -0
- package/crates/vault-domain/src/u128_as_decimal_string.rs +44 -0
- package/crates/vault-policy/Cargo.toml +17 -0
- package/crates/vault-policy/src/engine.rs +301 -0
- package/crates/vault-policy/src/error.rs +81 -0
- package/crates/vault-policy/src/lib.rs +17 -0
- package/crates/vault-policy/src/report.rs +34 -0
- package/crates/vault-policy/src/tests.rs +891 -0
- package/crates/vault-policy/src/tests_explain.rs +78 -0
- package/crates/vault-sdk-agent/Cargo.toml +21 -0
- package/crates/vault-sdk-agent/src/lib.rs +711 -0
- package/crates/vault-signer/Cargo.toml +25 -0
- package/crates/vault-signer/src/lib.rs +731 -0
- package/crates/vault-signer/tests/secure_enclave_acl.rs +54 -0
- package/crates/vault-transport-unix/Cargo.toml +24 -0
- package/crates/vault-transport-unix/src/lib.rs +1640 -0
- package/crates/vault-transport-xpc/Cargo.toml +25 -0
- package/crates/vault-transport-xpc/src/client_codec_api.rs +635 -0
- package/crates/vault-transport-xpc/src/lib.rs +680 -0
- package/crates/vault-transport-xpc/src/tests.rs +818 -0
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +773 -0
- package/dist/cli.cjs +35088 -0
- package/dist/cli.cjs.map +1 -0
- package/package.json +49 -43
- package/packages/cache/.turbo/turbo-build.log +52 -0
- package/packages/cache/dist/chunk-2QFWMUXT.cjs +43 -0
- package/packages/cache/dist/chunk-2QFWMUXT.cjs.map +1 -0
- package/packages/cache/dist/chunk-4U63TZTQ.js +43 -0
- package/packages/cache/dist/chunk-4U63TZTQ.js.map +1 -0
- package/packages/cache/dist/chunk-ALQ6H7KG.cjs +404 -0
- package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +1 -0
- package/packages/cache/dist/chunk-FGJEEF5N.js +404 -0
- package/packages/cache/dist/chunk-FGJEEF5N.js.map +1 -0
- package/packages/cache/dist/chunk-UYNEHZHB.cjs +45 -0
- package/packages/cache/dist/chunk-UYNEHZHB.cjs.map +1 -0
- package/packages/cache/dist/chunk-VXVMPG3W.js +45 -0
- package/packages/cache/dist/chunk-VXVMPG3W.js.map +1 -0
- package/packages/cache/dist/client/index.cjs +11 -0
- package/packages/cache/dist/client/index.cjs.map +1 -0
- package/packages/cache/dist/client/index.d.cts +15 -0
- package/packages/cache/dist/client/index.d.ts +15 -0
- package/packages/cache/dist/client/index.js +11 -0
- package/packages/cache/dist/client/index.js.map +1 -0
- package/packages/cache/dist/errors/index.cjs +11 -0
- package/packages/cache/dist/errors/index.cjs.map +1 -0
- package/packages/cache/dist/errors/index.d.cts +26 -0
- package/packages/cache/dist/errors/index.d.ts +26 -0
- package/packages/cache/dist/errors/index.js +11 -0
- package/packages/cache/dist/errors/index.js.map +1 -0
- package/packages/cache/dist/index.cjs +29 -0
- package/packages/cache/dist/index.cjs.map +1 -0
- package/packages/cache/dist/index.d.cts +4 -0
- package/packages/cache/dist/index.d.ts +4 -0
- package/packages/cache/dist/index.js +29 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/service/index.cjs +15 -0
- package/packages/cache/dist/service/index.cjs.map +1 -0
- package/packages/cache/dist/service/index.d.cts +184 -0
- package/packages/cache/dist/service/index.d.ts +184 -0
- package/packages/cache/dist/service/index.js +15 -0
- package/packages/cache/dist/service/index.js.map +1 -0
- package/packages/cache/node_modules/.bin/jiti +17 -0
- package/packages/cache/node_modules/.bin/tsc +17 -0
- package/packages/cache/node_modules/.bin/tsserver +17 -0
- package/packages/cache/node_modules/.bin/tsup +17 -0
- package/packages/cache/node_modules/.bin/tsup-node +17 -0
- package/packages/cache/node_modules/.bin/tsx +17 -0
- package/packages/cache/node_modules/.bin/vitest +17 -0
- package/packages/cache/package.json +48 -0
- package/packages/cache/src/client/index.ts +56 -0
- package/packages/cache/src/errors/index.ts +53 -0
- package/packages/cache/src/index.ts +3 -0
- package/packages/cache/src/service/index.test.ts +263 -0
- package/packages/cache/src/service/index.ts +678 -0
- package/packages/cache/tsconfig.json +13 -0
- package/packages/cache/tsup.config.ts +13 -0
- package/packages/cache/vitest.config.ts +16 -0
- package/packages/config/.turbo/turbo-build.log +18 -0
- package/packages/config/dist/index.cjs +1037 -0
- package/packages/config/dist/index.cjs.map +1 -0
- package/packages/config/dist/index.d.ts +131 -0
- package/packages/config/node_modules/.bin/jiti +17 -0
- package/packages/config/node_modules/.bin/tsc +17 -0
- package/packages/config/node_modules/.bin/tsserver +17 -0
- package/packages/config/node_modules/.bin/tsup +17 -0
- package/packages/config/node_modules/.bin/tsup-node +17 -0
- package/packages/config/node_modules/.bin/tsx +17 -0
- package/packages/config/package.json +21 -0
- package/packages/config/src/index.js +1 -0
- package/packages/config/src/index.ts +1282 -0
- package/packages/config/tsconfig.json +4 -0
- package/packages/rpc/.turbo/turbo-build.log +32 -0
- package/packages/rpc/dist/_esm-BCLXDO2R.cjs +3660 -0
- package/packages/rpc/dist/_esm-BCLXDO2R.cjs.map +1 -0
- package/packages/rpc/dist/ccip-OWJLAW55.cjs +16 -0
- package/packages/rpc/dist/ccip-OWJLAW55.cjs.map +1 -0
- package/packages/rpc/dist/chunk-APQIFZ3B.cjs +6247 -0
- package/packages/rpc/dist/chunk-APQIFZ3B.cjs.map +1 -0
- package/packages/rpc/dist/chunk-CDO2GWRD.cjs +410 -0
- package/packages/rpc/dist/chunk-CDO2GWRD.cjs.map +1 -0
- package/packages/rpc/dist/chunk-QGTNTFJ7.cjs +2249 -0
- package/packages/rpc/dist/chunk-QGTNTFJ7.cjs.map +1 -0
- package/packages/rpc/dist/chunk-TZDTAHWR.cjs +44 -0
- package/packages/rpc/dist/chunk-TZDTAHWR.cjs.map +1 -0
- package/packages/rpc/dist/index.cjs +7342 -0
- package/packages/rpc/dist/index.cjs.map +1 -0
- package/packages/rpc/dist/index.d.ts +3857 -0
- package/packages/rpc/dist/secp256k1-WCNM675D.cjs +18 -0
- package/packages/rpc/dist/secp256k1-WCNM675D.cjs.map +1 -0
- package/packages/rpc/node_modules/.bin/jiti +17 -0
- package/packages/rpc/node_modules/.bin/tsc +17 -0
- package/packages/rpc/node_modules/.bin/tsserver +17 -0
- package/packages/rpc/node_modules/.bin/tsup +17 -0
- package/packages/rpc/node_modules/.bin/tsup-node +17 -0
- package/packages/rpc/node_modules/.bin/tsx +17 -0
- package/packages/rpc/package.json +25 -0
- package/packages/rpc/src/index.ts +206 -0
- package/packages/rpc/tsconfig.json +4 -0
- package/packages/typescript/base.json +36 -0
- package/packages/typescript/nextjs.json +17 -0
- package/packages/typescript/package.json +10 -0
- package/packages/ui/.turbo/turbo-build.log +44 -0
- package/packages/ui/dist/chunk-MOAFBKSA.js +11 -0
- package/packages/ui/dist/chunk-MOAFBKSA.js.map +1 -0
- package/packages/ui/dist/components/badge.d.ts +12 -0
- package/packages/ui/dist/components/badge.js +31 -0
- package/packages/ui/dist/components/badge.js.map +1 -0
- package/packages/ui/dist/components/button.d.ts +13 -0
- package/packages/ui/dist/components/button.js +40 -0
- package/packages/ui/dist/components/button.js.map +1 -0
- package/packages/ui/dist/components/card.d.ts +10 -0
- package/packages/ui/dist/components/card.js +39 -0
- package/packages/ui/dist/components/card.js.map +1 -0
- package/packages/ui/dist/components/input.d.ts +5 -0
- package/packages/ui/dist/components/input.js +28 -0
- package/packages/ui/dist/components/input.js.map +1 -0
- package/packages/ui/dist/components/label.d.ts +5 -0
- package/packages/ui/dist/components/label.js +13 -0
- package/packages/ui/dist/components/label.js.map +1 -0
- package/packages/ui/dist/components/separator.d.ts +5 -0
- package/packages/ui/dist/components/separator.js +13 -0
- package/packages/ui/dist/components/separator.js.map +1 -0
- package/packages/ui/dist/components/textarea.d.ts +5 -0
- package/packages/ui/dist/components/textarea.js +27 -0
- package/packages/ui/dist/components/textarea.js.map +1 -0
- package/packages/ui/dist/tailwind.d.ts +56 -0
- package/packages/ui/dist/tailwind.js +60 -0
- package/packages/ui/dist/tailwind.js.map +1 -0
- package/packages/ui/dist/utils/cn.d.ts +5 -0
- package/packages/ui/dist/utils/cn.js +7 -0
- package/packages/ui/dist/utils/cn.js.map +1 -0
- package/packages/ui/node_modules/.bin/jiti +17 -0
- package/packages/ui/node_modules/.bin/tsc +17 -0
- package/packages/ui/node_modules/.bin/tsserver +17 -0
- package/packages/ui/node_modules/.bin/tsup +17 -0
- package/packages/ui/node_modules/.bin/tsup-node +17 -0
- package/packages/ui/node_modules/.bin/tsx +17 -0
- package/packages/ui/package.json +69 -0
- package/packages/ui/src/components/badge.tsx +27 -0
- package/packages/ui/src/components/button.tsx +40 -0
- package/packages/ui/src/components/card.tsx +31 -0
- package/packages/ui/src/components/input.tsx +21 -0
- package/packages/ui/src/components/label.tsx +6 -0
- package/packages/ui/src/components/separator.tsx +6 -0
- package/packages/ui/src/components/textarea.tsx +20 -0
- package/packages/ui/src/globals.css +70 -0
- package/packages/ui/src/tailwind.ts +56 -0
- package/packages/ui/src/utils/cn.ts +6 -0
- package/packages/ui/tsconfig.json +20 -0
- package/packages/ui/tsup.config.ts +20 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/install-rust-binaries.mjs +84 -0
- package/scripts/launchd/install-user-daemon.sh +358 -0
- package/scripts/launchd/run-vault-daemon.sh +5 -0
- package/scripts/launchd/run-wlfi-agent-daemon.sh +73 -0
- package/scripts/launchd/uninstall-user-daemon.sh +103 -0
- package/src/cli.ts +2121 -0
- package/src/lib/admin-guard.js +1 -0
- package/src/lib/admin-guard.ts +185 -0
- package/src/lib/admin-passthrough.ts +33 -0
- package/src/lib/admin-reset.ts +751 -0
- package/src/lib/admin-setup.ts +1612 -0
- package/src/lib/agent-auth-clear.js +1 -0
- package/src/lib/agent-auth-clear.ts +58 -0
- package/src/lib/agent-auth-forwarding.js +1 -0
- package/src/lib/agent-auth-forwarding.ts +149 -0
- package/src/lib/agent-auth-migrate.js +1 -0
- package/src/lib/agent-auth-migrate.ts +150 -0
- package/src/lib/agent-auth-revoke.ts +103 -0
- package/src/lib/agent-auth-rotate.ts +107 -0
- package/src/lib/agent-auth-token.js +1 -0
- package/src/lib/agent-auth-token.ts +25 -0
- package/src/lib/agent-auth.ts +89 -0
- package/src/lib/asset-broadcast.js +1 -0
- package/src/lib/asset-broadcast.ts +285 -0
- package/src/lib/bootstrap-artifacts.js +1 -0
- package/src/lib/bootstrap-artifacts.ts +205 -0
- package/src/lib/bootstrap-credentials.js +1 -0
- package/src/lib/bootstrap-credentials.ts +832 -0
- package/src/lib/config-amounts.js +1 -0
- package/src/lib/config-amounts.ts +189 -0
- package/src/lib/config-mutation.ts +27 -0
- package/src/lib/fs-trust.js +1 -0
- package/src/lib/fs-trust.ts +537 -0
- package/src/lib/keychain.js +1 -0
- package/src/lib/keychain.ts +225 -0
- package/src/lib/local-admin-access.ts +106 -0
- package/src/lib/network-selection.js +1 -0
- package/src/lib/network-selection.ts +71 -0
- package/src/lib/passthrough-security.js +1 -0
- package/src/lib/passthrough-security.ts +114 -0
- package/src/lib/rpc-guard.js +1 -0
- package/src/lib/rpc-guard.ts +7 -0
- package/src/lib/rust-spawn-options.js +1 -0
- package/src/lib/rust-spawn-options.ts +98 -0
- package/src/lib/rust.js +1 -0
- package/src/lib/rust.ts +143 -0
- package/src/lib/signed-tx.js +1 -0
- package/src/lib/signed-tx.ts +116 -0
- package/src/lib/status-repair-cli.ts +116 -0
- package/src/lib/sudo.js +1 -0
- package/src/lib/sudo.ts +172 -0
- package/src/lib/vault-password-forwarding.js +1 -0
- package/src/lib/vault-password-forwarding.ts +155 -0
- package/src/lib/wallet-profile.js +1 -0
- package/src/lib/wallet-profile.ts +332 -0
- package/src/lib/wallet-repair.js +1 -0
- package/src/lib/wallet-repair.ts +304 -0
- package/src/lib/wallet-setup.js +1 -0
- package/src/lib/wallet-setup.ts +1466 -0
- package/src/lib/wallet-status.js +1 -0
- package/src/lib/wallet-status.ts +640 -0
- package/tsconfig.base.json +17 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +25 -0
- package/turbo.json +41 -0
- package/LICENSE.md +0 -1
- package/dist/wlfa/index.cjs +0 -250
- package/dist/wlfa/index.d.cts +0 -1
- package/dist/wlfa/index.d.ts +0 -1
- package/dist/wlfa/index.js +0 -250
- package/dist/wlfc/index.cjs +0 -1894
- package/dist/wlfc/index.d.cts +0 -1
- package/dist/wlfc/index.d.ts +0 -1
- package/dist/wlfc/index.js +0 -1894
package/src/lib/rust.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import type { WlfiConfig } from '../../packages/config/src/index.js';
|
|
4
|
+
import * as configModule from '../../packages/config/src/index.js';
|
|
5
|
+
import { assertTrustedExecutablePath } from './fs-trust.js';
|
|
6
|
+
import { resolveValidatedPassthroughDaemonSocket } from './passthrough-security.js';
|
|
7
|
+
import { prepareSpawnOptions, type RunRustBinaryOptions } from './rust-spawn-options.js';
|
|
8
|
+
|
|
9
|
+
const { readConfig, resolveRustBinaryPath } =
|
|
10
|
+
configModule as typeof import('../../packages/config/src/index.js');
|
|
11
|
+
|
|
12
|
+
export type RustBinaryName = 'wlfi-agent-daemon' | 'wlfi-agent-admin' | 'wlfi-agent-agent';
|
|
13
|
+
|
|
14
|
+
export class RustBinaryExitError extends Error {
|
|
15
|
+
readonly binaryName: RustBinaryName;
|
|
16
|
+
readonly code: number;
|
|
17
|
+
readonly stdout: string;
|
|
18
|
+
readonly stderr: string;
|
|
19
|
+
|
|
20
|
+
constructor(binaryName: RustBinaryName, code: number, stdout: string, stderr: string) {
|
|
21
|
+
super(stderr.trim() || `${binaryName} exited with code ${code}`);
|
|
22
|
+
this.name = 'RustBinaryExitError';
|
|
23
|
+
this.binaryName = binaryName;
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.stdout = stdout;
|
|
26
|
+
this.stderr = stderr;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureBinary(binaryName: RustBinaryName, config?: WlfiConfig): string {
|
|
31
|
+
const resolved = resolveRustBinaryPath(binaryName, config ?? readConfig());
|
|
32
|
+
if (!fs.existsSync(resolved)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`${binaryName} is not installed at ${resolved}. Re-run npm install -g @wlfi-agent/cli or npm run install:rust-binaries.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
assertTrustedExecutablePath(resolved);
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function forwardedArgsIncludeDaemonSocket(args: string[]): boolean {
|
|
42
|
+
return args.some(
|
|
43
|
+
(arg, _index) => arg === '--daemon-socket' || arg.startsWith('--daemon-socket='),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function passthroughRustBinary(
|
|
48
|
+
binaryName: RustBinaryName,
|
|
49
|
+
args: string[],
|
|
50
|
+
config?: WlfiConfig,
|
|
51
|
+
): Promise<number> {
|
|
52
|
+
const resolvedConfig = config ?? readConfig();
|
|
53
|
+
const resolvedDaemonSocket = resolveValidatedPassthroughDaemonSocket(
|
|
54
|
+
binaryName,
|
|
55
|
+
args,
|
|
56
|
+
resolvedConfig,
|
|
57
|
+
);
|
|
58
|
+
const executable = ensureBinary(binaryName, resolvedConfig);
|
|
59
|
+
const prepared = await prepareSpawnOptions(binaryName, args, {});
|
|
60
|
+
const env = { ...prepared.env };
|
|
61
|
+
if (
|
|
62
|
+
resolvedDaemonSocket &&
|
|
63
|
+
!forwardedArgsIncludeDaemonSocket(args) &&
|
|
64
|
+
!env.WLFI_DAEMON_SOCKET?.trim()
|
|
65
|
+
) {
|
|
66
|
+
env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
|
|
67
|
+
}
|
|
68
|
+
const child = spawn(executable, prepared.args, {
|
|
69
|
+
stdio: [prepared.stdin !== undefined ? 'pipe' : 'inherit', 'inherit', 'inherit'],
|
|
70
|
+
env,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (prepared.stdin !== undefined) {
|
|
74
|
+
child.stdin?.end(prepared.stdin);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return await new Promise((resolve, reject) => {
|
|
78
|
+
child.on('error', reject);
|
|
79
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function runRustBinary(
|
|
84
|
+
binaryName: RustBinaryName,
|
|
85
|
+
args: string[],
|
|
86
|
+
config?: WlfiConfig,
|
|
87
|
+
options: RunRustBinaryOptions = {},
|
|
88
|
+
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
89
|
+
const resolvedConfig = config ?? readConfig();
|
|
90
|
+
const resolvedDaemonSocket = resolveValidatedPassthroughDaemonSocket(
|
|
91
|
+
binaryName,
|
|
92
|
+
args,
|
|
93
|
+
resolvedConfig,
|
|
94
|
+
);
|
|
95
|
+
const executable = ensureBinary(binaryName, resolvedConfig);
|
|
96
|
+
const prepared = await prepareSpawnOptions(binaryName, args, options);
|
|
97
|
+
const env = { ...prepared.env };
|
|
98
|
+
if (
|
|
99
|
+
resolvedDaemonSocket &&
|
|
100
|
+
!forwardedArgsIncludeDaemonSocket(args) &&
|
|
101
|
+
!env.WLFI_DAEMON_SOCKET?.trim()
|
|
102
|
+
) {
|
|
103
|
+
env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
|
|
104
|
+
}
|
|
105
|
+
const child = spawn(executable, prepared.args, {
|
|
106
|
+
stdio: [prepared.stdin !== undefined ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
107
|
+
env,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let stdout = '';
|
|
111
|
+
let stderr = '';
|
|
112
|
+
child.stdout?.on('data', (chunk) => {
|
|
113
|
+
stdout += chunk.toString();
|
|
114
|
+
});
|
|
115
|
+
child.stderr?.on('data', (chunk) => {
|
|
116
|
+
stderr += chunk.toString();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (prepared.stdin !== undefined) {
|
|
120
|
+
child.stdin?.end(prepared.stdin);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const code = await new Promise<number>((resolve, reject) => {
|
|
124
|
+
child.on('error', reject);
|
|
125
|
+
child.on('close', (value) => resolve(value ?? 1));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (code !== 0) {
|
|
129
|
+
throw new RustBinaryExitError(binaryName, code, stdout, stderr);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { stdout, stderr, code };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function runRustBinaryJson<T>(
|
|
136
|
+
binaryName: RustBinaryName,
|
|
137
|
+
args: string[],
|
|
138
|
+
config?: WlfiConfig,
|
|
139
|
+
options: RunRustBinaryOptions = {},
|
|
140
|
+
): Promise<T> {
|
|
141
|
+
const { stdout } = await runRustBinary(binaryName, args, config, options);
|
|
142
|
+
return JSON.parse(stdout) as T;
|
|
143
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './signed-tx.ts';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAddressEqual,
|
|
3
|
+
parseTransaction,
|
|
4
|
+
recoverTransactionAddress,
|
|
5
|
+
type Address,
|
|
6
|
+
type Hex
|
|
7
|
+
} from 'viem';
|
|
8
|
+
|
|
9
|
+
export interface ExpectedSignedBroadcastTransaction {
|
|
10
|
+
rawTxHex: Hex;
|
|
11
|
+
from: Address;
|
|
12
|
+
to: Address;
|
|
13
|
+
chainId: number;
|
|
14
|
+
nonce: number;
|
|
15
|
+
allowHigherNonce?: boolean;
|
|
16
|
+
value: bigint;
|
|
17
|
+
data: Hex;
|
|
18
|
+
gasLimit: bigint;
|
|
19
|
+
maxFeePerGas: bigint;
|
|
20
|
+
maxPriorityFeePerGas: bigint;
|
|
21
|
+
txType: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SignedBroadcastInspection {
|
|
25
|
+
nonce: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeHex(value: Hex): string {
|
|
29
|
+
return value.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeTxType(value: string): string {
|
|
33
|
+
const normalized = value.trim().toLowerCase();
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
throw new Error('txType is required');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let parsed: bigint;
|
|
39
|
+
try {
|
|
40
|
+
parsed = normalized.startsWith('0x') ? BigInt(normalized) : BigInt(normalized);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(`Unsupported txType '${value}'`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
switch (parsed) {
|
|
46
|
+
case 0n:
|
|
47
|
+
return 'legacy';
|
|
48
|
+
case 1n:
|
|
49
|
+
return 'eip2930';
|
|
50
|
+
case 2n:
|
|
51
|
+
return 'eip1559';
|
|
52
|
+
case 3n:
|
|
53
|
+
return 'eip4844';
|
|
54
|
+
case 4n:
|
|
55
|
+
return 'eip7702';
|
|
56
|
+
default:
|
|
57
|
+
throw new Error(`Unsupported txType '${value}'`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function assertEqual<T>(actual: T, expected: T, label: string): void {
|
|
62
|
+
if (actual !== expected) {
|
|
63
|
+
throw new Error(`signed raw transaction ${label} mismatch: expected ${expected}, received ${actual}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function assertSignedBroadcastTransactionMatchesRequest(
|
|
68
|
+
expected: ExpectedSignedBroadcastTransaction
|
|
69
|
+
): Promise<SignedBroadcastInspection> {
|
|
70
|
+
const parsed = parseTransaction(expected.rawTxHex);
|
|
71
|
+
const recoveredFrom = await recoverTransactionAddress({
|
|
72
|
+
serializedTransaction:
|
|
73
|
+
expected.rawTxHex as Parameters<typeof recoverTransactionAddress>[0]['serializedTransaction']
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!isAddressEqual(recoveredFrom, expected.from)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`signed raw transaction from mismatch: expected ${expected.from}, received ${recoveredFrom}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const parsedTo = parsed.to;
|
|
83
|
+
if (!parsedTo || !isAddressEqual(parsedTo, expected.to)) {
|
|
84
|
+
throw new Error(`signed raw transaction to mismatch: expected ${expected.to}, received ${parsedTo ?? 'null'}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (parsed.nonce === undefined) {
|
|
88
|
+
throw new Error('signed raw transaction nonce is missing');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
assertEqual(parsed.chainId, expected.chainId, 'chainId');
|
|
92
|
+
if (expected.allowHigherNonce) {
|
|
93
|
+
if (parsed.nonce < expected.nonce) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`signed raw transaction nonce mismatch: expected at least ${expected.nonce}, received ${parsed.nonce}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
assertEqual(parsed.nonce, expected.nonce, 'nonce');
|
|
100
|
+
}
|
|
101
|
+
assertEqual(parsed.value ?? 0n, expected.value, 'value');
|
|
102
|
+
assertEqual(parsed.gas, expected.gasLimit, 'gasLimit');
|
|
103
|
+
assertEqual(parsed.maxFeePerGas, expected.maxFeePerGas, 'maxFeePerGas');
|
|
104
|
+
assertEqual(parsed.maxPriorityFeePerGas, expected.maxPriorityFeePerGas, 'maxPriorityFeePerGas');
|
|
105
|
+
assertEqual(parsed.type, normalizeTxType(expected.txType), 'txType');
|
|
106
|
+
|
|
107
|
+
const parsedData = normalizeHex((parsed.data ?? '0x') as Hex);
|
|
108
|
+
const expectedData = normalizeHex(expected.data);
|
|
109
|
+
if (parsedData !== expectedData) {
|
|
110
|
+
throw new Error(`signed raw transaction data mismatch: expected ${expectedData}, received ${parsedData}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
nonce: parsed.nonce,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import {
|
|
3
|
+
formatWalletRepairText,
|
|
4
|
+
repairWalletState,
|
|
5
|
+
type WalletRepairResult,
|
|
6
|
+
} from './wallet-repair.js';
|
|
7
|
+
import {
|
|
8
|
+
formatWalletStatusText,
|
|
9
|
+
getWalletStatus,
|
|
10
|
+
resolveWalletStatusExitCode,
|
|
11
|
+
type WalletStatusExitOptions,
|
|
12
|
+
type WalletStatusResult,
|
|
13
|
+
} from './wallet-status.js';
|
|
14
|
+
|
|
15
|
+
interface CliOutputOptions {
|
|
16
|
+
asJson: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StatusCommandDeps {
|
|
20
|
+
getWalletStatus?: () => WalletStatusResult;
|
|
21
|
+
print?: (payload: unknown, options: CliOutputOptions) => void;
|
|
22
|
+
setExitCode?: (code: number) => void;
|
|
23
|
+
resolveWalletStatusExitCode?: (
|
|
24
|
+
result: WalletStatusResult,
|
|
25
|
+
options?: WalletStatusExitOptions,
|
|
26
|
+
) => number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RepairCommandDeps {
|
|
30
|
+
repairWalletState?: (input?: {
|
|
31
|
+
agentKeyId?: string;
|
|
32
|
+
overwriteKeychain?: boolean;
|
|
33
|
+
redactBootstrap?: boolean;
|
|
34
|
+
}) => WalletRepairResult;
|
|
35
|
+
print?: (payload: unknown, options: CliOutputOptions) => void;
|
|
36
|
+
setExitCode?: (code: number) => void;
|
|
37
|
+
resolveWalletStatusExitCode?: (
|
|
38
|
+
result: WalletStatusResult,
|
|
39
|
+
options?: WalletStatusExitOptions,
|
|
40
|
+
) => number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function defaultPrint(payload: unknown, options: CliOutputOptions): void {
|
|
44
|
+
if (options.asJson) {
|
|
45
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
process.stdout.write(`${String(payload)}\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function defaultSetExitCode(code: number): void {
|
|
52
|
+
process.exitCode = code;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function registerStatusCommand(program: Command, deps: StatusCommandDeps = {}): Command {
|
|
56
|
+
const loadStatus = deps.getWalletStatus ?? getWalletStatus;
|
|
57
|
+
const print = deps.print ?? defaultPrint;
|
|
58
|
+
const setExitCode = deps.setExitCode ?? defaultSetExitCode;
|
|
59
|
+
const resolveExitCode = deps.resolveWalletStatusExitCode ?? resolveWalletStatusExitCode;
|
|
60
|
+
|
|
61
|
+
return program
|
|
62
|
+
.command('status')
|
|
63
|
+
.description('Inspect local wallet security, daemon, and credential health')
|
|
64
|
+
.option('--strict', 'Exit with status 1 when warnings are present', false)
|
|
65
|
+
.option('--json', 'Print JSON output', false)
|
|
66
|
+
.action((options) => {
|
|
67
|
+
const result = loadStatus();
|
|
68
|
+
print(options.json ? result : formatWalletStatusText(result), {
|
|
69
|
+
asJson: options.json,
|
|
70
|
+
});
|
|
71
|
+
setExitCode(
|
|
72
|
+
resolveExitCode(result, {
|
|
73
|
+
strict: options.strict,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function registerRepairCommand(program: Command, deps: RepairCommandDeps = {}): Command {
|
|
80
|
+
const runRepair = deps.repairWalletState ?? repairWalletState;
|
|
81
|
+
const print = deps.print ?? defaultPrint;
|
|
82
|
+
const setExitCode = deps.setExitCode ?? defaultSetExitCode;
|
|
83
|
+
const resolveExitCode = deps.resolveWalletStatusExitCode ?? resolveWalletStatusExitCode;
|
|
84
|
+
|
|
85
|
+
return program
|
|
86
|
+
.command('repair')
|
|
87
|
+
.description('Repair non-privileged local wallet issues and clean plaintext artifacts')
|
|
88
|
+
.option('--agent-key-id <uuid>', 'Agent key id override for legacy token migration')
|
|
89
|
+
.option(
|
|
90
|
+
'--overwrite-keychain',
|
|
91
|
+
'Replace a different existing Keychain token for this agent when migrating plaintext config storage',
|
|
92
|
+
false,
|
|
93
|
+
)
|
|
94
|
+
.option(
|
|
95
|
+
'--redact-bootstrap',
|
|
96
|
+
'Redact auto-generated bootstrap files instead of deleting them',
|
|
97
|
+
false,
|
|
98
|
+
)
|
|
99
|
+
.option('--strict', 'Exit with status 1 when warnings remain after repair', false)
|
|
100
|
+
.option('--json', 'Print JSON output', false)
|
|
101
|
+
.action((options) => {
|
|
102
|
+
const result = runRepair({
|
|
103
|
+
agentKeyId: options.agentKeyId,
|
|
104
|
+
overwriteKeychain: options.overwriteKeychain,
|
|
105
|
+
redactBootstrap: options.redactBootstrap,
|
|
106
|
+
});
|
|
107
|
+
print(options.json ? result : formatWalletRepairText(result), {
|
|
108
|
+
asJson: options.json,
|
|
109
|
+
});
|
|
110
|
+
setExitCode(
|
|
111
|
+
resolveExitCode(result.after, {
|
|
112
|
+
strict: options.strict,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
}
|
package/src/lib/sudo.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sudo.ts';
|
package/src/lib/sudo.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { spawn, type SpawnOptions } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export interface SudoCommandResult {
|
|
4
|
+
code: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RunSudoCommandOptions {
|
|
10
|
+
stdin?: string;
|
|
11
|
+
inheritOutput?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CreateSudoSessionDeps {
|
|
15
|
+
promptPassword: () => Promise<string>;
|
|
16
|
+
isRoot?: () => boolean;
|
|
17
|
+
spawnCommand?: typeof spawn;
|
|
18
|
+
stdout?: Pick<NodeJS.WriteStream, 'write'>;
|
|
19
|
+
stderr?: Pick<NodeJS.WriteStream, 'write'>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function currentProcessIsRoot(): boolean {
|
|
23
|
+
return typeof process.geteuid === 'function' && process.geteuid() === 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isSudoAuthenticationFailure(output: string): boolean {
|
|
27
|
+
return (
|
|
28
|
+
/sudo: .*password is required/iu.test(output) ||
|
|
29
|
+
/sudo: .*incorrect password/iu.test(output) ||
|
|
30
|
+
/sudo: no password was provided/iu.test(output) ||
|
|
31
|
+
/sorry, try again\./iu.test(output)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runCommand(
|
|
36
|
+
command: string,
|
|
37
|
+
args: string[],
|
|
38
|
+
options: RunSudoCommandOptions,
|
|
39
|
+
deps: {
|
|
40
|
+
spawnCommand: typeof spawn;
|
|
41
|
+
stdout: Pick<NodeJS.WriteStream, 'write'>;
|
|
42
|
+
stderr: Pick<NodeJS.WriteStream, 'write'>;
|
|
43
|
+
},
|
|
44
|
+
): Promise<SudoCommandResult> {
|
|
45
|
+
return await new Promise((resolve, reject) => {
|
|
46
|
+
const spawnOptions: SpawnOptions = {
|
|
47
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
48
|
+
};
|
|
49
|
+
const child = deps.spawnCommand(command, args, spawnOptions);
|
|
50
|
+
|
|
51
|
+
let stdout = '';
|
|
52
|
+
let stderr = '';
|
|
53
|
+
|
|
54
|
+
child.stdout?.on('data', (chunk: Buffer | string) => {
|
|
55
|
+
const text = chunk.toString();
|
|
56
|
+
stdout += text;
|
|
57
|
+
if (options.inheritOutput) {
|
|
58
|
+
deps.stdout.write(text);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
child.stderr?.on('data', (chunk: Buffer | string) => {
|
|
62
|
+
const text = chunk.toString();
|
|
63
|
+
stderr += text;
|
|
64
|
+
if (options.inheritOutput) {
|
|
65
|
+
deps.stderr.write(text);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.on('error', reject);
|
|
70
|
+
child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
71
|
+
child.stdin?.end(options.stdin ?? '');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createSudoSession(deps: CreateSudoSessionDeps) {
|
|
76
|
+
const spawnCommand = deps.spawnCommand ?? spawn;
|
|
77
|
+
const isRoot = deps.isRoot ?? currentProcessIsRoot;
|
|
78
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
79
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
80
|
+
let primed = false;
|
|
81
|
+
|
|
82
|
+
async function prime(): Promise<void> {
|
|
83
|
+
if (isRoot()) {
|
|
84
|
+
primed = true;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (primed) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const password = await deps.promptPassword();
|
|
92
|
+
const result = await runCommand(
|
|
93
|
+
'sudo',
|
|
94
|
+
['-S', '-p', '', '-v'],
|
|
95
|
+
{
|
|
96
|
+
stdin: `${password}\n`,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
spawnCommand,
|
|
100
|
+
stdout,
|
|
101
|
+
stderr,
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (result.code !== 0) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
result.stderr.trim() ||
|
|
108
|
+
result.stdout.trim() ||
|
|
109
|
+
`sudo credential check failed (exit code ${result.code})`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
primed = true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function run(
|
|
117
|
+
args: string[],
|
|
118
|
+
options: RunSudoCommandOptions = {},
|
|
119
|
+
): Promise<SudoCommandResult> {
|
|
120
|
+
if (args.length === 0) {
|
|
121
|
+
throw new Error('sudo command arguments are required');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isRoot()) {
|
|
125
|
+
return await runCommand(args[0], args.slice(1), options, {
|
|
126
|
+
spawnCommand,
|
|
127
|
+
stdout,
|
|
128
|
+
stderr,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await prime();
|
|
133
|
+
let result = await runCommand(
|
|
134
|
+
'sudo',
|
|
135
|
+
['-n', ...args],
|
|
136
|
+
options,
|
|
137
|
+
{
|
|
138
|
+
spawnCommand,
|
|
139
|
+
stdout,
|
|
140
|
+
stderr,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (result.code === 0) {
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const combinedOutput = `${result.stderr}\n${result.stdout}`;
|
|
149
|
+
if (!isSudoAuthenticationFailure(combinedOutput)) {
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
primed = false;
|
|
154
|
+
await prime();
|
|
155
|
+
result = await runCommand(
|
|
156
|
+
'sudo',
|
|
157
|
+
['-n', ...args],
|
|
158
|
+
options,
|
|
159
|
+
{
|
|
160
|
+
spawnCommand,
|
|
161
|
+
stdout,
|
|
162
|
+
stderr,
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
prime,
|
|
170
|
+
run,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './vault-password-forwarding.ts';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const MAX_SECRET_STDIN_BYTES = 16 * 1024
|
|
2
|
+
|
|
3
|
+
export interface VaultPasswordRelayOptions {
|
|
4
|
+
env?: NodeJS.ProcessEnv
|
|
5
|
+
readFromStdin?: (label: string) => Promise<string>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PreparedVaultPasswordRelay {
|
|
9
|
+
args: string[]
|
|
10
|
+
stdin?: string
|
|
11
|
+
scrubSensitiveEnv: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const HELP_FLAGS = new Set(['-h', '--help', '-V', '--version'])
|
|
15
|
+
|
|
16
|
+
function forwardedArgsPrefix(args: string[]): string[] {
|
|
17
|
+
const terminatorIndex = args.indexOf('--')
|
|
18
|
+
return terminatorIndex >= 0 ? args.slice(0, terminatorIndex) : args
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function skipEnvRelayForArgs(args: string[]): boolean {
|
|
22
|
+
const forwarded = forwardedArgsPrefix(args)
|
|
23
|
+
return forwarded[0] === 'help' || forwarded.some((arg) => HELP_FLAGS.has(arg))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveInlineVaultPasswordArg(args: string[]): {
|
|
27
|
+
index: number
|
|
28
|
+
value: string
|
|
29
|
+
} | null {
|
|
30
|
+
let match: { index: number; value: string } | null = null
|
|
31
|
+
|
|
32
|
+
for (let index = 0; index < forwardedArgsPrefix(args).length; index += 1) {
|
|
33
|
+
const current = args[index]
|
|
34
|
+
let nextMatch: { index: number; value: string } | null = null
|
|
35
|
+
if (current === '--vault-password') {
|
|
36
|
+
const value = args[index + 1]
|
|
37
|
+
if (value === undefined || value === '--' || value.startsWith('-')) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'--vault-password requires a value; use --vault-password=<value> if the value starts with -'
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
nextMatch = { index, value }
|
|
43
|
+
index += 1
|
|
44
|
+
} else if (current.startsWith('--vault-password=')) {
|
|
45
|
+
nextMatch = {
|
|
46
|
+
index,
|
|
47
|
+
value: current.slice('--vault-password='.length)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (nextMatch) {
|
|
52
|
+
if (match) {
|
|
53
|
+
throw new Error('--vault-password may only be provided once')
|
|
54
|
+
}
|
|
55
|
+
match = nextMatch
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return match
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function countVaultPasswordStdinFlags(args: string[]): number {
|
|
63
|
+
let matches = 0
|
|
64
|
+
|
|
65
|
+
for (const current of forwardedArgsPrefix(args)) {
|
|
66
|
+
if (current === '--vault-password-stdin') {
|
|
67
|
+
matches += 1
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return matches
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function withTrailingNewline(secret: string): string {
|
|
75
|
+
return `${secret}\n`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateSecret(secret: string, label: string): string {
|
|
79
|
+
if (Buffer.byteLength(secret, 'utf8') > MAX_SECRET_STDIN_BYTES) {
|
|
80
|
+
throw new Error(`${label} must not exceed ${MAX_SECRET_STDIN_BYTES} bytes`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const trimmed = secret.replace(/[\r\n]+$/u, '')
|
|
84
|
+
if (!trimmed.trim()) {
|
|
85
|
+
throw new Error(`${label} is required`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return trimmed
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function readTrimmedSecretFromProcessStdin(label: string): Promise<string> {
|
|
92
|
+
process.stdin.setEncoding('utf8')
|
|
93
|
+
let raw = ''
|
|
94
|
+
for await (const chunk of process.stdin) {
|
|
95
|
+
raw += chunk
|
|
96
|
+
if (Buffer.byteLength(raw, 'utf8') > MAX_SECRET_STDIN_BYTES) {
|
|
97
|
+
throw new Error(`${label} must not exceed ${MAX_SECRET_STDIN_BYTES} bytes`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return validateSecret(raw, label)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function prepareVaultPasswordRelay(
|
|
105
|
+
args: string[],
|
|
106
|
+
options: VaultPasswordRelayOptions = {}
|
|
107
|
+
): Promise<PreparedVaultPasswordRelay> {
|
|
108
|
+
const env = options.env ?? process.env
|
|
109
|
+
const readFromStdin = options.readFromStdin ?? readTrimmedSecretFromProcessStdin
|
|
110
|
+
const inlineArg = resolveInlineVaultPasswordArg(args)
|
|
111
|
+
const stdinFlagCount = countVaultPasswordStdinFlags(args)
|
|
112
|
+
const usesStdinFlag = stdinFlagCount > 0
|
|
113
|
+
|
|
114
|
+
if (stdinFlagCount > 1) {
|
|
115
|
+
throw new Error('--vault-password-stdin may only be provided once')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (inlineArg && usesStdinFlag) {
|
|
119
|
+
throw new Error('--vault-password conflicts with --vault-password-stdin')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (inlineArg) {
|
|
123
|
+
validateSecret(inlineArg.value, 'vaultPassword')
|
|
124
|
+
throw new Error(
|
|
125
|
+
'insecure --vault-password is disabled; use --vault-password-stdin or a local TTY prompt'
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (usesStdinFlag) {
|
|
130
|
+
return {
|
|
131
|
+
args: [...args],
|
|
132
|
+
stdin: withTrailingNewline(await readFromStdin('vaultPassword')),
|
|
133
|
+
scrubSensitiveEnv: true
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (Object.prototype.hasOwnProperty.call(env, 'WLFI_VAULT_PASSWORD')) {
|
|
138
|
+
if (skipEnvRelayForArgs(args)) {
|
|
139
|
+
return {
|
|
140
|
+
args: [...args],
|
|
141
|
+
scrubSensitiveEnv: true
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
validateSecret(env.WLFI_VAULT_PASSWORD ?? '', 'vaultPassword')
|
|
146
|
+
throw new Error(
|
|
147
|
+
'WLFI_VAULT_PASSWORD is disabled for security; use --vault-password-stdin or a local TTY prompt'
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
args: [...args],
|
|
153
|
+
scrubSensitiveEnv: true
|
|
154
|
+
}
|
|
155
|
+
}
|