@wlfi-agent/cli 1.4.13 → 1.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -1839
- package/dist/wlfc/index.d.cts +0 -1
- package/dist/wlfc/index.d.ts +0 -1
- package/dist/wlfc/index.js +0 -1839
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const PRIVATE_FILE_MODE_MASK = 0o077;
|
|
5
|
+
const GROUP_OTHER_WRITE_MODE_MASK = 0o022;
|
|
6
|
+
const STICKY_BIT_MODE = 0o1000;
|
|
7
|
+
|
|
8
|
+
function runningAsRoot(): boolean {
|
|
9
|
+
return typeof process.geteuid === 'function' && process.geteuid() === 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readLstat(targetPath: string): fs.Stats | null {
|
|
13
|
+
try {
|
|
14
|
+
return fs.lstatSync(targetPath);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isStableRootOwnedSymlink(stats: fs.Stats, targetPath: string): boolean {
|
|
24
|
+
if (process.platform === 'win32' || typeof stats.uid !== 'number' || stats.uid !== 0) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const parentPath = path.dirname(targetPath);
|
|
29
|
+
const parentStats = readLstat(parentPath);
|
|
30
|
+
return Boolean(
|
|
31
|
+
parentStats?.isDirectory() &&
|
|
32
|
+
typeof parentStats.uid === 'number' &&
|
|
33
|
+
parentStats.uid === 0 &&
|
|
34
|
+
(parentStats.mode & GROUP_OTHER_WRITE_MODE_MASK) === 0,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertNoSymlinkAncestorDirectories(targetPath: string, label: string): void {
|
|
39
|
+
const normalizedPath = path.resolve(targetPath);
|
|
40
|
+
const parentPath = path.dirname(normalizedPath);
|
|
41
|
+
if (parentPath === normalizedPath) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { root } = path.parse(parentPath);
|
|
46
|
+
const relativeParent = parentPath.slice(root.length);
|
|
47
|
+
if (!relativeParent) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let currentPath = root;
|
|
52
|
+
for (const segment of relativeParent.split(path.sep).filter(Boolean)) {
|
|
53
|
+
currentPath = path.join(currentPath, segment);
|
|
54
|
+
const stats = readLstat(currentPath);
|
|
55
|
+
if (!stats) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
if (stats.isSymbolicLink()) {
|
|
59
|
+
if (isStableRootOwnedSymlink(stats, currentPath)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${label} '${normalizedPath}' must not traverse symlinked ancestor directories`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function allowedOwnerUids(): Set<number> {
|
|
70
|
+
const allowed = new Set<number>();
|
|
71
|
+
const effectiveUid = typeof process.geteuid === 'function' ? process.geteuid() : null;
|
|
72
|
+
if (effectiveUid !== null) {
|
|
73
|
+
allowed.add(effectiveUid);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const sudoUid = process.env.SUDO_UID?.trim();
|
|
77
|
+
if (effectiveUid === 0 && sudoUid && /^\d+$/u.test(sudoUid)) {
|
|
78
|
+
allowed.add(Number(sudoUid));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return allowed;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function assertTrustedOwner(stats: fs.Stats, targetPath: string, label: string): void {
|
|
85
|
+
if (process.platform === 'win32' || typeof stats.uid !== 'number') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (stats.uid === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const allowed = allowedOwnerUids();
|
|
94
|
+
if (!allowed.has(stats.uid)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`${label} '${targetPath}' must be owned by the current user, sudo caller, or root`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function assertTrustedDaemonSocketOwner(stats: fs.Stats, targetPath: string, label: string): void {
|
|
102
|
+
if (runningAsRoot()) {
|
|
103
|
+
assertRootOwned(stats, targetPath, label);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
assertTrustedOwner(stats, targetPath, label);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function assertRootOwned(stats: fs.Stats, targetPath: string, label: string): void {
|
|
111
|
+
if (process.platform === 'win32') {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof stats.uid !== 'number') {
|
|
116
|
+
throw new Error(`${label} '${targetPath}' owner could not be determined`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (stats.uid !== 0) {
|
|
120
|
+
throw new Error(`${label} '${targetPath}' must be owned by root`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isStickyDirectory(stats: fs.Stats): boolean {
|
|
125
|
+
return process.platform !== 'win32' && (stats.mode & STICKY_BIT_MODE) !== 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function assertPrivateFileStats(stats: fs.Stats, targetPath: string, label: string): void {
|
|
129
|
+
assertTrustedOwner(stats, targetPath, label);
|
|
130
|
+
|
|
131
|
+
if (process.platform === 'win32') {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if ((stats.mode & PRIVATE_FILE_MODE_MASK) !== 0) {
|
|
136
|
+
throw new Error(`${label} '${targetPath}' must not grant group/other permissions`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function assertSecureDirectoryStats(
|
|
141
|
+
stats: fs.Stats,
|
|
142
|
+
targetPath: string,
|
|
143
|
+
label: string,
|
|
144
|
+
options: { allowStickyGroupOtherWritable?: boolean } = {},
|
|
145
|
+
): void {
|
|
146
|
+
assertTrustedOwner(stats, targetPath, label);
|
|
147
|
+
if (process.platform === 'win32') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if ((stats.mode & GROUP_OTHER_WRITE_MODE_MASK) === 0) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (options.allowStickyGroupOtherWritable && isStickyDirectory(stats)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
throw new Error(`${label} '${targetPath}' must not be writable by group/other`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ancestorDirectoryPaths(targetPath: string): string[] {
|
|
163
|
+
const resolvedPath = fs.realpathSync.native(path.resolve(targetPath));
|
|
164
|
+
const segments: string[] = [];
|
|
165
|
+
let currentPath = resolvedPath;
|
|
166
|
+
|
|
167
|
+
while (true) {
|
|
168
|
+
segments.push(currentPath);
|
|
169
|
+
const parentPath = path.dirname(currentPath);
|
|
170
|
+
if (parentPath === currentPath) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
currentPath = parentPath;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return segments;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function findNearestExistingPath(targetPath: string): { path: string; stats: fs.Stats } {
|
|
180
|
+
let currentPath = path.resolve(targetPath);
|
|
181
|
+
|
|
182
|
+
while (true) {
|
|
183
|
+
const stats = readLstat(currentPath);
|
|
184
|
+
if (stats) {
|
|
185
|
+
return {
|
|
186
|
+
path: currentPath,
|
|
187
|
+
stats,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const parentPath = path.dirname(currentPath);
|
|
192
|
+
if (parentPath === currentPath) {
|
|
193
|
+
throw new Error(`No existing ancestor directory found for '${targetPath}'`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
currentPath = parentPath;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function assertTrustedDirectoryPath(targetPath: string, label: string): void {
|
|
201
|
+
const normalizedPath = path.resolve(targetPath);
|
|
202
|
+
assertNoSymlinkAncestorDirectories(normalizedPath, label);
|
|
203
|
+
const targetStats = fs.lstatSync(normalizedPath);
|
|
204
|
+
|
|
205
|
+
if (targetStats.isSymbolicLink()) {
|
|
206
|
+
throw new Error(`${label} '${normalizedPath}' must not be a symlink`);
|
|
207
|
+
}
|
|
208
|
+
if (!targetStats.isDirectory()) {
|
|
209
|
+
throw new Error(`${label} '${normalizedPath}' must be a directory`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
assertSecureDirectoryStats(targetStats, normalizedPath, label);
|
|
213
|
+
|
|
214
|
+
for (const [index, currentPath] of ancestorDirectoryPaths(normalizedPath).entries()) {
|
|
215
|
+
if (index === 0) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const stats = fs.lstatSync(currentPath);
|
|
220
|
+
if (!stats.isDirectory()) {
|
|
221
|
+
throw new Error(`${label} '${currentPath}' must be a directory`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
assertSecureDirectoryStats(stats, currentPath, label, {
|
|
225
|
+
allowStickyGroupOtherWritable: index > 0,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function assertTrustedDaemonSocketPath(targetPath: string, label = 'Daemon socket'): string {
|
|
231
|
+
const resolvedPath = path.resolve(targetPath);
|
|
232
|
+
assertTrustedDirectoryPath(path.dirname(resolvedPath), `${label} directory`);
|
|
233
|
+
|
|
234
|
+
let stats: fs.Stats;
|
|
235
|
+
try {
|
|
236
|
+
stats = fs.lstatSync(resolvedPath);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
239
|
+
throw new Error(`${label} '${resolvedPath}' does not exist`);
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (stats.isSymbolicLink()) {
|
|
245
|
+
throw new Error(`${label} '${resolvedPath}' must not be a symlink`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (process.platform !== 'win32' && !stats.isSocket()) {
|
|
249
|
+
throw new Error(`${label} '${resolvedPath}' must be a unix socket`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
assertTrustedDaemonSocketOwner(stats, resolvedPath, label);
|
|
253
|
+
return resolvedPath;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function assertTrustedAdminDaemonSocketPath(
|
|
257
|
+
targetPath: string,
|
|
258
|
+
label = 'Daemon socket',
|
|
259
|
+
): string {
|
|
260
|
+
const resolvedPath = path.resolve(targetPath);
|
|
261
|
+
assertTrustedRootDirectoryPath(path.dirname(resolvedPath), `${label} directory`);
|
|
262
|
+
|
|
263
|
+
let stats: fs.Stats;
|
|
264
|
+
try {
|
|
265
|
+
stats = fs.lstatSync(resolvedPath);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
268
|
+
throw new Error(`${label} '${resolvedPath}' does not exist`);
|
|
269
|
+
}
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (stats.isSymbolicLink()) {
|
|
274
|
+
throw new Error(`${label} '${resolvedPath}' must not be a symlink`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (process.platform !== 'win32' && !stats.isSocket()) {
|
|
278
|
+
throw new Error(`${label} '${resolvedPath}' must be a unix socket`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
assertRootOwned(stats, resolvedPath, label);
|
|
282
|
+
return resolvedPath;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function assertTrustedRootPlannedDirectoryPath(targetPath: string, label: string): string {
|
|
286
|
+
const normalizedPath = path.resolve(targetPath);
|
|
287
|
+
assertNoSymlinkAncestorDirectories(normalizedPath, label);
|
|
288
|
+
const stats = readLstat(normalizedPath);
|
|
289
|
+
|
|
290
|
+
if (stats) {
|
|
291
|
+
if (stats.isSymbolicLink()) {
|
|
292
|
+
throw new Error(`${label} '${normalizedPath}' must not be a symlink`);
|
|
293
|
+
}
|
|
294
|
+
if (!stats.isDirectory()) {
|
|
295
|
+
throw new Error(`${label} '${normalizedPath}' must be a directory`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
assertTrustedRootDirectoryPath(normalizedPath, label);
|
|
299
|
+
return normalizedPath;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const nearestExistingPath = findNearestExistingPath(normalizedPath);
|
|
303
|
+
if (nearestExistingPath.stats.isSymbolicLink()) {
|
|
304
|
+
throw new Error(`${label} '${nearestExistingPath.path}' must not be a symlink`);
|
|
305
|
+
}
|
|
306
|
+
if (!nearestExistingPath.stats.isDirectory()) {
|
|
307
|
+
throw new Error(`${label} '${nearestExistingPath.path}' must be a directory`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
assertTrustedRootDirectoryPath(nearestExistingPath.path, label);
|
|
311
|
+
return normalizedPath;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function assertTrustedRootPlannedDaemonSocketPath(
|
|
315
|
+
targetPath: string,
|
|
316
|
+
label = 'Daemon socket',
|
|
317
|
+
): string {
|
|
318
|
+
const resolvedPath = path.resolve(targetPath);
|
|
319
|
+
assertTrustedRootPlannedDirectoryPath(path.dirname(resolvedPath), `${label} directory`);
|
|
320
|
+
|
|
321
|
+
const stats = readLstat(resolvedPath);
|
|
322
|
+
if (!stats) {
|
|
323
|
+
return resolvedPath;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (stats.isSymbolicLink()) {
|
|
327
|
+
throw new Error(`${label} '${resolvedPath}' must not be a symlink`);
|
|
328
|
+
}
|
|
329
|
+
if (process.platform !== 'win32' && !stats.isSocket()) {
|
|
330
|
+
throw new Error(`${label} '${resolvedPath}' must be a unix socket`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
assertRootOwned(stats, resolvedPath, label);
|
|
334
|
+
return resolvedPath;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function assertTrustedRootPlannedPrivateFilePath(
|
|
338
|
+
targetPath: string,
|
|
339
|
+
label = 'Private file',
|
|
340
|
+
): string {
|
|
341
|
+
const resolvedPath = path.resolve(targetPath);
|
|
342
|
+
assertTrustedRootPlannedDirectoryPath(path.dirname(resolvedPath), `${label} directory`);
|
|
343
|
+
|
|
344
|
+
let stats: fs.Stats | null;
|
|
345
|
+
try {
|
|
346
|
+
stats = fs.lstatSync(resolvedPath);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
349
|
+
if (code === 'ENOENT' || code === 'EACCES' || code === 'EPERM') {
|
|
350
|
+
return resolvedPath;
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!stats) {
|
|
356
|
+
return resolvedPath;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (stats.isSymbolicLink()) {
|
|
360
|
+
throw new Error(`${label} '${resolvedPath}' must not be a symlink`);
|
|
361
|
+
}
|
|
362
|
+
if (!stats.isFile()) {
|
|
363
|
+
throw new Error(`${label} '${resolvedPath}' must be a regular file`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
assertPrivateFileStats(stats, resolvedPath, label);
|
|
367
|
+
assertRootOwned(stats, resolvedPath, label);
|
|
368
|
+
return resolvedPath;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function assertTrustedRootDirectoryPath(targetPath: string, label: string): void {
|
|
372
|
+
const normalizedPath = path.resolve(targetPath);
|
|
373
|
+
assertNoSymlinkAncestorDirectories(normalizedPath, label);
|
|
374
|
+
const targetStats = fs.lstatSync(normalizedPath);
|
|
375
|
+
|
|
376
|
+
if (targetStats.isSymbolicLink()) {
|
|
377
|
+
throw new Error(`${label} '${normalizedPath}' must not be a symlink`);
|
|
378
|
+
}
|
|
379
|
+
if (!targetStats.isDirectory()) {
|
|
380
|
+
throw new Error(`${label} '${normalizedPath}' must be a directory`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
assertSecureDirectoryStats(targetStats, normalizedPath, label);
|
|
384
|
+
assertRootOwned(targetStats, normalizedPath, label);
|
|
385
|
+
|
|
386
|
+
for (const [index, currentPath] of ancestorDirectoryPaths(normalizedPath).entries()) {
|
|
387
|
+
if (index === 0) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const stats = fs.lstatSync(currentPath);
|
|
392
|
+
if (!stats.isDirectory()) {
|
|
393
|
+
throw new Error(`${label} '${currentPath}' must be a directory`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
assertSecureDirectoryStats(stats, currentPath, label, {
|
|
397
|
+
allowStickyGroupOtherWritable: index > 0,
|
|
398
|
+
});
|
|
399
|
+
assertRootOwned(stats, currentPath, label);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function assertTrustedRootPrivateFilePath(
|
|
404
|
+
targetPath: string,
|
|
405
|
+
label = 'Private file',
|
|
406
|
+
options: { allowMissing?: boolean } = {},
|
|
407
|
+
): string {
|
|
408
|
+
const resolvedPath = path.resolve(targetPath);
|
|
409
|
+
assertTrustedRootDirectoryPath(path.dirname(resolvedPath), `${label} directory`);
|
|
410
|
+
|
|
411
|
+
let stats: fs.Stats;
|
|
412
|
+
try {
|
|
413
|
+
stats = fs.lstatSync(resolvedPath);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
416
|
+
if (code === 'ENOENT' && options.allowMissing) {
|
|
417
|
+
return resolvedPath;
|
|
418
|
+
}
|
|
419
|
+
if (code === 'ENOENT') {
|
|
420
|
+
throw new Error(`${label} '${resolvedPath}' does not exist`);
|
|
421
|
+
}
|
|
422
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
423
|
+
throw new Error(
|
|
424
|
+
`${label} '${resolvedPath}' is not accessible to the current process`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (stats.isSymbolicLink()) {
|
|
431
|
+
throw new Error(`${label} '${resolvedPath}' must not be a symlink`);
|
|
432
|
+
}
|
|
433
|
+
if (!stats.isFile()) {
|
|
434
|
+
throw new Error(`${label} '${resolvedPath}' must be a regular file`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
assertPrivateFileStats(stats, resolvedPath, label);
|
|
438
|
+
assertRootOwned(stats, resolvedPath, label);
|
|
439
|
+
return resolvedPath;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function assertTrustedPrivateFilePath(
|
|
443
|
+
targetPath: string,
|
|
444
|
+
label = 'Private file',
|
|
445
|
+
options: { allowMissing?: boolean } = {},
|
|
446
|
+
): string {
|
|
447
|
+
const resolvedPath = path.resolve(targetPath);
|
|
448
|
+
assertTrustedDirectoryPath(path.dirname(resolvedPath), `${label} directory`);
|
|
449
|
+
|
|
450
|
+
let stats: fs.Stats;
|
|
451
|
+
try {
|
|
452
|
+
stats = fs.lstatSync(resolvedPath);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT' && options.allowMissing) {
|
|
455
|
+
return resolvedPath;
|
|
456
|
+
}
|
|
457
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
458
|
+
throw new Error(`${label} '${resolvedPath}' does not exist`);
|
|
459
|
+
}
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (stats.isSymbolicLink()) {
|
|
464
|
+
throw new Error(`${label} '${resolvedPath}' must not be a symlink`);
|
|
465
|
+
}
|
|
466
|
+
if (!stats.isFile()) {
|
|
467
|
+
throw new Error(`${label} '${resolvedPath}' must be a regular file`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
assertPrivateFileStats(stats, resolvedPath, label);
|
|
471
|
+
return resolvedPath;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export function readUtf8FileSecure(targetPath: string, label: string, maxBytes?: number): string {
|
|
475
|
+
assertTrustedDirectoryPath(path.dirname(targetPath), `${label} parent directory`);
|
|
476
|
+
const openFlags =
|
|
477
|
+
process.platform === 'win32'
|
|
478
|
+
? fs.constants.O_RDONLY
|
|
479
|
+
: fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;
|
|
480
|
+
const fd = fs.openSync(targetPath, openFlags);
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const stats = fs.fstatSync(fd);
|
|
484
|
+
if (!stats.isFile()) {
|
|
485
|
+
throw new Error(`${label} '${targetPath}' must be a regular file`);
|
|
486
|
+
}
|
|
487
|
+
assertPrivateFileStats(stats, targetPath, label);
|
|
488
|
+
if (maxBytes !== undefined && stats.size > maxBytes) {
|
|
489
|
+
throw new Error(`${label} '${targetPath}' must not exceed ${maxBytes} bytes`);
|
|
490
|
+
}
|
|
491
|
+
return fs.readFileSync(fd, 'utf8');
|
|
492
|
+
} finally {
|
|
493
|
+
fs.closeSync(fd);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function assertTrustedExecutablePath(targetPath: string): void {
|
|
498
|
+
const normalizedPath = path.resolve(targetPath);
|
|
499
|
+
assertNoSymlinkAncestorDirectories(normalizedPath, 'Rust binary');
|
|
500
|
+
const stats = fs.lstatSync(normalizedPath);
|
|
501
|
+
if (stats.isSymbolicLink()) {
|
|
502
|
+
throw new Error(`Rust binary '${normalizedPath}' must not be a symlink`);
|
|
503
|
+
}
|
|
504
|
+
if (!stats.isFile()) {
|
|
505
|
+
throw new Error(`Rust binary '${normalizedPath}' must be a regular file`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (runningAsRoot()) {
|
|
509
|
+
assertRootOwned(stats, normalizedPath, 'Rust binary');
|
|
510
|
+
} else {
|
|
511
|
+
assertTrustedOwner(stats, normalizedPath, 'Rust binary');
|
|
512
|
+
}
|
|
513
|
+
if (process.platform !== 'win32' && (stats.mode & GROUP_OTHER_WRITE_MODE_MASK) !== 0) {
|
|
514
|
+
throw new Error(`Rust binary '${normalizedPath}' must not be writable by group/other`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const binaryDirectory = path.dirname(normalizedPath);
|
|
518
|
+
if (!runningAsRoot()) {
|
|
519
|
+
assertTrustedDirectoryPath(binaryDirectory, 'Rust binary directory');
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
for (const currentPath of ancestorDirectoryPaths(binaryDirectory)) {
|
|
524
|
+
const currentStats = fs.lstatSync(currentPath);
|
|
525
|
+
if (currentStats.isSymbolicLink()) {
|
|
526
|
+
throw new Error(`Rust binary directory '${currentPath}' must not be a symlink`);
|
|
527
|
+
}
|
|
528
|
+
if (!currentStats.isDirectory()) {
|
|
529
|
+
throw new Error(`Rust binary directory '${currentPath}' must be a directory`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
assertRootOwned(currentStats, currentPath, 'Rust binary directory');
|
|
533
|
+
if (process.platform !== 'win32' && (currentStats.mode & GROUP_OTHER_WRITE_MODE_MASK) !== 0) {
|
|
534
|
+
throw new Error(`Rust binary directory '${currentPath}' must not be writable by group/other`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './keychain.ts';
|