@mnemonik/cli 0.0.0 → 7.99.5
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/LICENSE +38 -0
- package/dist/auth/credentials.d.ts +38 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +7464 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/device.d.ts +23 -0
- package/dist/auth/device.d.ts.map +1 -0
- package/dist/auth/device.js +108 -0
- package/dist/auth/device.js.map +1 -0
- package/dist/auth/index.d.ts +34 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +5048 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/installSession.d.ts +22 -0
- package/dist/auth/installSession.d.ts.map +1 -0
- package/dist/auth/installSession.js +4717 -0
- package/dist/auth/installSession.js.map +1 -0
- package/dist/auth/pkce.d.ts +35 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +182 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/status.d.ts +30 -0
- package/dist/auth/status.d.ts.map +1 -0
- package/dist/auth/status.js +4799 -0
- package/dist/auth/status.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +28 -0
- package/dist/bin.js.map +1 -0
- package/dist/diagnostics.d.ts +42 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +7405 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/digests.json +175 -0
- package/dist/identity/migrate.d.ts +62 -0
- package/dist/identity/migrate.d.ts.map +1 -0
- package/dist/identity/migrate.js +5416 -0
- package/dist/identity/migrate.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/install/adapters.d.ts +65 -0
- package/dist/install/adapters.d.ts.map +1 -0
- package/dist/install/adapters.js +91 -0
- package/dist/install/adapters.js.map +1 -0
- package/dist/install/hosts.d.ts +83 -0
- package/dist/install/hosts.d.ts.map +1 -0
- package/dist/install/hosts.js +5733 -0
- package/dist/install/hosts.js.map +1 -0
- package/dist/install/journal.d.ts +113 -0
- package/dist/install/journal.d.ts.map +1 -0
- package/dist/install/journal.js +6836 -0
- package/dist/install/journal.js.map +1 -0
- package/dist/install/journey.d.ts +11 -0
- package/dist/install/journey.d.ts.map +1 -0
- package/dist/install/journey.js +5594 -0
- package/dist/install/journey.js.map +1 -0
- package/dist/install/ownership.d.ts +55 -0
- package/dist/install/ownership.d.ts.map +1 -0
- package/dist/install/ownership.js +4942 -0
- package/dist/install/ownership.js.map +1 -0
- package/dist/install/transaction.d.ts +71 -0
- package/dist/install/transaction.d.ts.map +1 -0
- package/dist/install/transaction.js +5254 -0
- package/dist/install/transaction.js.map +1 -0
- package/dist/install/ui.d.ts +14 -0
- package/dist/install/ui.d.ts.map +1 -0
- package/dist/install/ui.js +4925 -0
- package/dist/install/ui.js.map +1 -0
- package/dist/installSession.d.ts +33 -0
- package/dist/installSession.d.ts.map +1 -0
- package/dist/installSession.js +4893 -0
- package/dist/installSession.js.map +1 -0
- package/dist/installation.d.ts +11 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +6618 -0
- package/dist/installation.js.map +1 -0
- package/dist/launcher.d.ts +27 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +6862 -0
- package/dist/launcher.js.map +1 -0
- package/dist/output.d.ts +22 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +68 -0
- package/dist/output.js.map +1 -0
- package/dist/preflight.d.ts +43 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +5265 -0
- package/dist/preflight.js.map +1 -0
- package/dist/project/eligibility.d.ts +19 -0
- package/dist/project/eligibility.d.ts.map +1 -0
- package/dist/project/eligibility.js +4835 -0
- package/dist/project/eligibility.js.map +1 -0
- package/dist/project/records.d.ts +14 -0
- package/dist/project/records.d.ts.map +1 -0
- package/dist/project/records.js +6598 -0
- package/dist/project/records.js.map +1 -0
- package/dist/project.d.ts +127 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +7857 -0
- package/dist/project.js.map +1 -0
- package/dist/router.d.ts +63 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +6168 -0
- package/dist/router.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +31 -0
- package/dist/runtime/bootstrap.d.ts.map +1 -0
- package/dist/runtime/bootstrap.js +386 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/releaseSource.d.ts +26 -0
- package/dist/runtime/releaseSource.d.ts.map +1 -0
- package/dist/runtime/releaseSource.js +4992 -0
- package/dist/runtime/releaseSource.js.map +1 -0
- package/dist/runtime/selfUpdate.d.ts +12 -0
- package/dist/runtime/selfUpdate.d.ts.map +1 -0
- package/dist/runtime/selfUpdate.js +4943 -0
- package/dist/runtime/selfUpdate.js.map +1 -0
- package/dist/runtime/signers.d.ts +2 -0
- package/dist/runtime/signers.d.ts.map +1 -0
- package/dist/runtime/signers.js +1697 -0
- package/dist/runtime/signers.js.map +1 -0
- package/dist/runtime/store.d.ts +30 -0
- package/dist/runtime/store.d.ts.map +1 -0
- package/dist/runtime/store.js +977 -0
- package/dist/runtime/store.js.map +1 -0
- package/dist/sbom.json +308 -0
- package/dist/scanner/control.d.ts +38 -0
- package/dist/scanner/control.d.ts.map +1 -0
- package/dist/scanner/control.js +6568 -0
- package/dist/scanner/control.js.map +1 -0
- package/dist/scanner/data.d.ts +9 -0
- package/dist/scanner/data.d.ts.map +1 -0
- package/dist/scanner/data.js +4715 -0
- package/dist/scanner/data.js.map +1 -0
- package/dist/scanner/discover.d.ts +35 -0
- package/dist/scanner/discover.d.ts.map +1 -0
- package/dist/scanner/discover.js +5266 -0
- package/dist/scanner/discover.js.map +1 -0
- package/dist/scanner/enable.d.ts +69 -0
- package/dist/scanner/enable.d.ts.map +1 -0
- package/dist/scanner/enable.js +6923 -0
- package/dist/scanner/enable.js.map +1 -0
- package/dist/scanner/picker.d.ts +58 -0
- package/dist/scanner/picker.d.ts.map +1 -0
- package/dist/scanner/picker.js +4993 -0
- package/dist/scanner/picker.js.map +1 -0
- package/dist/scanner/service.d.ts +49 -0
- package/dist/scanner/service.d.ts.map +1 -0
- package/dist/scanner/service.js +4888 -0
- package/dist/scanner/service.js.map +1 -0
- package/dist/scanner/update.d.ts +4 -0
- package/dist/scanner/update.d.ts.map +1 -0
- package/dist/scanner/update.js +6569 -0
- package/dist/scanner/update.js.map +1 -0
- package/dist/scanner-release.json +129 -0
- package/dist/screens/journey.d.ts +23 -0
- package/dist/screens/journey.d.ts.map +1 -0
- package/dist/screens/journey.js +110 -0
- package/dist/screens/journey.js.map +1 -0
- package/dist/screens.d.ts +18 -0
- package/dist/screens.d.ts.map +1 -0
- package/dist/screens.js +79 -0
- package/dist/screens.js.map +1 -0
- package/dist/status.d.ts +64 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +5281 -0
- package/dist/status.js.map +1 -0
- package/dist/transport/server.d.ts +79 -0
- package/dist/transport/server.d.ts.map +1 -0
- package/dist/transport/server.js +136 -0
- package/dist/transport/server.js.map +1 -0
- package/dist/vendor/local-setup.js +6971 -0
- package/dist/vendor/shared/runtimeReader.js +408 -0
- package/dist/vendor/shared/runtimeSigners.js +512 -0
- package/package.json +258 -7
- package/README.md +0 -3
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// packages/shared/dist/runtimeSigners.js
|
|
13
|
+
import { lstatSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
import { basename, dirname, join, resolve, sep, win32 } from "node:path";
|
|
16
|
+
import { createHash, createPublicKey, randomUUID, timingSafeEqual, verify as verifySignature } from "node:crypto";
|
|
17
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
18
|
+
import { promisify } from "node:util";
|
|
19
|
+
function decodeBase64(text, length) {
|
|
20
|
+
const bytes = Buffer.from(text, "base64");
|
|
21
|
+
if (bytes.length !== length || bytes.toString("base64") !== text)
|
|
22
|
+
throw new Error("unsigned");
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
25
|
+
async function verifyMinisign(path, signaturePath, identity) {
|
|
26
|
+
const [message, signatureText] = await Promise.all([
|
|
27
|
+
readFile(path),
|
|
28
|
+
readFile(signaturePath, "utf8")
|
|
29
|
+
]);
|
|
30
|
+
const publicPacket = decodeBase64(identity, 42);
|
|
31
|
+
const lines = signatureText.trimEnd().split(/\r?\n/);
|
|
32
|
+
const [untrustedComment, encodedSignature, trustedLine, encodedGlobalSignature] = lines;
|
|
33
|
+
if (publicPacket.subarray(0, 2).toString() !== "Ed" || lines.length !== 4 || !untrustedComment?.startsWith("untrusted comment:") || !encodedSignature || !trustedLine?.startsWith("trusted comment: ") || !encodedGlobalSignature)
|
|
34
|
+
throw new Error("unsigned");
|
|
35
|
+
const signaturePacket = decodeBase64(encodedSignature, 74);
|
|
36
|
+
const globalSignature = decodeBase64(encodedGlobalSignature, 64);
|
|
37
|
+
const algorithm = signaturePacket.subarray(0, 2).toString();
|
|
38
|
+
if (algorithm !== "ED" && algorithm !== "Ed" || !timingSafeEqual(publicPacket.subarray(2, 10), signaturePacket.subarray(2, 10)))
|
|
39
|
+
throw new Error("unsigned");
|
|
40
|
+
const publicKey = createPublicKey({
|
|
41
|
+
key: Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), publicPacket.subarray(10)]),
|
|
42
|
+
format: "der",
|
|
43
|
+
type: "spki"
|
|
44
|
+
});
|
|
45
|
+
const fileSignature = signaturePacket.subarray(10);
|
|
46
|
+
const signedMessage = algorithm === "ED" ? createHash("blake2b512").update(message).digest() : message;
|
|
47
|
+
const trustedComment = trustedLine.slice("trusted comment: ".length);
|
|
48
|
+
if (!verifySignature(null, signedMessage, publicKey, fileSignature) || !verifySignature(null, Buffer.concat([fileSignature, Buffer.from(trustedComment)]), publicKey, globalSignature))
|
|
49
|
+
throw new Error("unsigned");
|
|
50
|
+
}
|
|
51
|
+
async function verifySigner(path, signer, run = execute) {
|
|
52
|
+
if (signer.platform === "darwin" && /^[A-Z0-9]{10}$/.test(signer.identity)) {
|
|
53
|
+
await run("/usr/bin/codesign", [
|
|
54
|
+
"--verify",
|
|
55
|
+
"--strict",
|
|
56
|
+
"-R",
|
|
57
|
+
`=anchor apple generic and certificate leaf[subject.OU] = "${signer.identity}"`,
|
|
58
|
+
path
|
|
59
|
+
]);
|
|
60
|
+
} else if (signer.platform === "win32" && /^[A-Fa-f0-9]{40}$/.test(signer.identity)) {
|
|
61
|
+
await run("powershell.exe", [
|
|
62
|
+
"-NoProfile",
|
|
63
|
+
"-NonInteractive",
|
|
64
|
+
"-Command",
|
|
65
|
+
`$ErrorActionPreference = 'Stop'; $s = Get-AuthenticodeSignature -LiteralPath ${quote(path)}; if ($s.Status -ne 'Valid' -or $s.SignerCertificate.Thumbprint -ne ${quote(signer.identity)}) { exit 1 }`
|
|
66
|
+
]);
|
|
67
|
+
} else if (signer.platform === "linux") {
|
|
68
|
+
try {
|
|
69
|
+
await verifyMinisign(path, signer.signature, signer.identity);
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error("unsigned");
|
|
72
|
+
}
|
|
73
|
+
} else
|
|
74
|
+
throw new Error("unsigned");
|
|
75
|
+
}
|
|
76
|
+
async function verifyWindowsPermission(path, run = hookExecute, state) {
|
|
77
|
+
const verdicts = await auditWindowsPermissions(path, [path], run, false, state);
|
|
78
|
+
if (verdicts.get(path) === "acl_unavailable")
|
|
79
|
+
throw new Error("acl_unavailable");
|
|
80
|
+
if (verdicts.get(path) !== "ok")
|
|
81
|
+
throw new Error("acl_permissions");
|
|
82
|
+
}
|
|
83
|
+
function windowsCommand(file) {
|
|
84
|
+
const root = process.env.SystemRoot;
|
|
85
|
+
if (!root || !win32.isAbsolute(root))
|
|
86
|
+
throw new Error("acl_system_root_unavailable");
|
|
87
|
+
return win32.join(root, "System32", file);
|
|
88
|
+
}
|
|
89
|
+
function readIdentity(stdout2) {
|
|
90
|
+
const match = /^"([^"\r\n]+)","(S-1-(?:\d+-)+\d+)"$/.exec(stdout2.trim());
|
|
91
|
+
if (!match?.[1] || !match[2] || stdout2.includes("\uFFFD"))
|
|
92
|
+
throw new Error("acl_identity_unavailable");
|
|
93
|
+
return { name: match[1], sid: match[2] };
|
|
94
|
+
}
|
|
95
|
+
async function windowsIdentity(run) {
|
|
96
|
+
if (currentIdentity)
|
|
97
|
+
return currentIdentity;
|
|
98
|
+
identityPending ??= run(windowsCommand("whoami.exe"), identityArgs).then((result) => currentIdentity = readIdentity(stdout(result))).finally(() => {
|
|
99
|
+
identityPending = void 0;
|
|
100
|
+
});
|
|
101
|
+
return identityPending;
|
|
102
|
+
}
|
|
103
|
+
async function verifyWindowsAcl(path, run = hookExecute, state) {
|
|
104
|
+
const user = await windowsIdentity(run);
|
|
105
|
+
if (!privateEntries((await drive(saveAcl(path, false, [path], state), run)).get(path), user, false))
|
|
106
|
+
throw new Error("acl_permissions");
|
|
107
|
+
}
|
|
108
|
+
function hookInvocation(file, args) {
|
|
109
|
+
if (win32.basename(file) === "whoami.exe") {
|
|
110
|
+
if ([file, ...args].some((arg) => /[%!"\r\n]/.test(arg)))
|
|
111
|
+
throw new Error("acl_path_unavailable");
|
|
112
|
+
const command = args.map((arg) => /^(?:[a-z0-9]+|\/[a-z]+)$/i.test(arg) ? arg : `"${arg}"`).join(" ");
|
|
113
|
+
return [
|
|
114
|
+
windowsCommand("cmd.exe"),
|
|
115
|
+
["/d", "/v:off", "/s", "/c", `"chcp 65001>nul & "${file}" ${command}"`]
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
if (win32.basename(file) === "cmd.exe")
|
|
119
|
+
return [file, ["/u", ...args]];
|
|
120
|
+
return [file, args];
|
|
121
|
+
}
|
|
122
|
+
function aclRecords(output, root, paths, onLookup) {
|
|
123
|
+
const records = /* @__PURE__ */ new Map();
|
|
124
|
+
const wanted = new Set(paths);
|
|
125
|
+
const lines = output.replace(/^\uFEFF/, "").trimEnd().split(/\r?\n/);
|
|
126
|
+
if (lines.length % 2)
|
|
127
|
+
throw new Error("acl_unavailable");
|
|
128
|
+
for (let index = 0; index < lines.length; index += 2) {
|
|
129
|
+
onLookup?.();
|
|
130
|
+
const relativePath = lines[index];
|
|
131
|
+
const descriptor = lines[index + 1];
|
|
132
|
+
if (relativePath === void 0 || descriptor === void 0)
|
|
133
|
+
throw new Error("acl_unavailable");
|
|
134
|
+
const path = resolve(dirname(root), relativePath.replaceAll("\\", sep));
|
|
135
|
+
if (!wanted.has(path))
|
|
136
|
+
continue;
|
|
137
|
+
if (records.has(path))
|
|
138
|
+
throw new Error("acl_unavailable");
|
|
139
|
+
const dacl = /^D:(?:P|AI|AR)*((?:\([^()]*\))*)$/.exec(descriptor);
|
|
140
|
+
records.set(path, dacl?.[1]?.match(/\([^()]*\)/g) ?? []);
|
|
141
|
+
}
|
|
142
|
+
return records;
|
|
143
|
+
}
|
|
144
|
+
function privateEntries(entries, user, directory) {
|
|
145
|
+
if (!entries?.length)
|
|
146
|
+
throw new Error("acl_unavailable");
|
|
147
|
+
return entries.every((line) => {
|
|
148
|
+
const ace = /^\(([AD]);((?:OI|CI|NP|IO|ID|SA|FA)*);[A-Za-z0-9]+;;;((?:S-1-(?:\d+-)+\d+)|[A-Z]{2})\)$/.exec(line);
|
|
149
|
+
if (!ace)
|
|
150
|
+
throw new Error("acl_unavailable");
|
|
151
|
+
return (!directory || !ace[2]?.includes("ID")) && (ace[1] === "D" || ace[3] === user.sid);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function* prepareAclDirectory(state) {
|
|
155
|
+
const directory = join(state, "audit-tmp");
|
|
156
|
+
if (auditDirectories.has(directory)) {
|
|
157
|
+
const stat2 = lstatSync(directory);
|
|
158
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
159
|
+
throw new Error("acl_permissions");
|
|
160
|
+
return directory;
|
|
161
|
+
}
|
|
162
|
+
let created = false;
|
|
163
|
+
try {
|
|
164
|
+
mkdirSync(directory, { mode: 448 });
|
|
165
|
+
created = true;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.code !== "EEXIST")
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
const stat = lstatSync(directory);
|
|
171
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
172
|
+
throw new Error("acl_permissions");
|
|
173
|
+
if (!auditDirectories.has(directory)) {
|
|
174
|
+
currentIdentity ??= readIdentity(yield [windowsCommand("whoami.exe"), identityArgs]);
|
|
175
|
+
yield [windowsCommand("icacls.exe"), aclArgs(directory, currentIdentity.sid)];
|
|
176
|
+
if (!created) {
|
|
177
|
+
const temp = join(directory, `.acl-${randomUUID()}.tmp`);
|
|
178
|
+
writeFileSync(temp, "", { flag: "wx", mode: 384 });
|
|
179
|
+
try {
|
|
180
|
+
yield [windowsCommand("icacls.exe"), [directory, "/save", temp, "/l"]];
|
|
181
|
+
if (!privateEntries(aclRecords(readFileSync(temp, "utf16le"), directory, [directory]).get(directory), currentIdentity, true))
|
|
182
|
+
throw new Error("acl_permissions");
|
|
183
|
+
} finally {
|
|
184
|
+
unlinkSync(temp);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
auditDirectories.add(directory);
|
|
188
|
+
}
|
|
189
|
+
return directory;
|
|
190
|
+
}
|
|
191
|
+
async function prepareWindowsAclDirectory(state, run = hookExecute) {
|
|
192
|
+
let pending = directoryPending.get(state);
|
|
193
|
+
if (!pending) {
|
|
194
|
+
pending = drive(prepareAclDirectory(state), run).then(() => {
|
|
195
|
+
}).finally(() => directoryPending.delete(state));
|
|
196
|
+
directoryPending.set(state, pending);
|
|
197
|
+
}
|
|
198
|
+
await pending;
|
|
199
|
+
}
|
|
200
|
+
function* saveAcl(path, recursive, paths = [path], state = lstatSync(path).isDirectory() ? path : dirname(path)) {
|
|
201
|
+
const directory = yield* prepareAclDirectory(state);
|
|
202
|
+
const temp = join(directory, `.acl-${randomUUID()}.tmp`);
|
|
203
|
+
writeFileSync(temp, "", { flag: "wx", mode: 384 });
|
|
204
|
+
try {
|
|
205
|
+
yield [windowsCommand("icacls.exe"), [path, "/save", temp, ...recursive ? ["/t"] : [], "/l"]];
|
|
206
|
+
return aclRecords(readFileSync(temp, "utf16le"), path, paths.filter((item) => item !== directory && !item.startsWith(directory + sep)));
|
|
207
|
+
} finally {
|
|
208
|
+
unlinkSync(temp);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function drive(commands, run) {
|
|
212
|
+
try {
|
|
213
|
+
let step = commands.next();
|
|
214
|
+
while (!step.done)
|
|
215
|
+
step = commands.next(stdout(await run(...step.value)));
|
|
216
|
+
return step.value;
|
|
217
|
+
} finally {
|
|
218
|
+
commands.return(void 0);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function dirEntry(line) {
|
|
222
|
+
const metadata = /^\S+\s+\S+(?:\s+\S+)?\s+(?:<DIR>|\d[\d,.\s]*)\s+/.exec(line)?.[0];
|
|
223
|
+
if (!metadata)
|
|
224
|
+
return void 0;
|
|
225
|
+
const ownerColumn = metadata.length;
|
|
226
|
+
const owner = line.slice(ownerColumn, ownerColumn + ownerWidth).trim().toLowerCase();
|
|
227
|
+
const name = line.slice(ownerColumn + ownerWidth);
|
|
228
|
+
return owner && name ? { owner, name } : void 0;
|
|
229
|
+
}
|
|
230
|
+
function ownerLookup(paths) {
|
|
231
|
+
if (paths.some((path) => /[\t\r\n]/.test(path)))
|
|
232
|
+
throw new Error("acl_path_unavailable");
|
|
233
|
+
const script = `$ErrorActionPreference = 'Stop'
|
|
234
|
+
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
235
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
236
|
+
while ($null -ne ($path = [Console]::ReadLine())) {
|
|
237
|
+
$owner = (Get-Acl -LiteralPath $path).GetOwner([System.Security.Principal.SecurityIdentifier]).Value
|
|
238
|
+
[Console]::WriteLine($path + "\`t" + $owner)
|
|
239
|
+
}`;
|
|
240
|
+
return [
|
|
241
|
+
windowsCommand("WindowsPowerShell\\v1.0\\powershell.exe"),
|
|
242
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
243
|
+
paths.join("\n") + "\n"
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
function ownerSids(output) {
|
|
247
|
+
const owners = /* @__PURE__ */ new Map();
|
|
248
|
+
for (const line of output.split(/\r?\n/)) {
|
|
249
|
+
const [path, sid] = line.split(" ");
|
|
250
|
+
if (path && sid && /^S-1-(?:\d+-)+\d+$/.test(sid))
|
|
251
|
+
owners.set(path, sid.toLowerCase());
|
|
252
|
+
}
|
|
253
|
+
return owners;
|
|
254
|
+
}
|
|
255
|
+
async function auditWindowsPermissions(state, paths, run = hookExecute, recursive = true, tempState = lstatSync(state).isDirectory() ? state : dirname(state)) {
|
|
256
|
+
const user = await windowsIdentity(run);
|
|
257
|
+
const root = resolve(state);
|
|
258
|
+
for (const path of paths)
|
|
259
|
+
if (resolve(path) !== root && !resolve(path).startsWith(root + sep))
|
|
260
|
+
throw new Error("acl_path_unavailable");
|
|
261
|
+
if (/[%!"\r\n]/.test(root))
|
|
262
|
+
throw new Error("acl_path_unavailable");
|
|
263
|
+
const owners = /* @__PURE__ */ new Map();
|
|
264
|
+
for (const [directory, descend] of [
|
|
265
|
+
[dirname(root), false],
|
|
266
|
+
...recursive ? [[root, true]] : []
|
|
267
|
+
]) {
|
|
268
|
+
if (/[%!"\r\n]/.test(directory))
|
|
269
|
+
throw new Error("acl_path_unavailable");
|
|
270
|
+
const output = stdout(await run(windowsCommand("cmd.exe"), [
|
|
271
|
+
"/d",
|
|
272
|
+
"/v:off",
|
|
273
|
+
"/s",
|
|
274
|
+
"/c",
|
|
275
|
+
`dir /q /a ${descend ? "/s " : ""}"${directory}"`
|
|
276
|
+
]));
|
|
277
|
+
let current = directory;
|
|
278
|
+
for (const line of output.split(/\r?\n/)) {
|
|
279
|
+
const entry = dirEntry(line);
|
|
280
|
+
if (entry) {
|
|
281
|
+
if (entry.name !== "..")
|
|
282
|
+
owners.set(join(current, entry.name), unknownOwner(entry.owner, user) ? "..." : entry.owner);
|
|
283
|
+
} else {
|
|
284
|
+
const header = /(?:^|\s)((?:[A-Za-z]:\\|\\\\|\/).*)$/.exec(line.trim());
|
|
285
|
+
if (header?.[1])
|
|
286
|
+
current = header[1];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
291
|
+
const ownerPaths = [...owners.keys()].filter((path) => path === root || path.startsWith(root + sep));
|
|
292
|
+
const truncated = new Set(ownerPaths.filter((path) => owners.get(path)?.includes("...")));
|
|
293
|
+
if (truncated.size) {
|
|
294
|
+
const result = await run(...ownerLookup([...truncated])).catch(() => ({ stdout: "" }));
|
|
295
|
+
for (const [path, sid] of ownerSids(stdout(result)))
|
|
296
|
+
if (truncated.has(path))
|
|
297
|
+
owners.set(path, sid);
|
|
298
|
+
}
|
|
299
|
+
for (const [path, entries] of await drive(saveAcl(root, recursive, ownerPaths, tempState), run)) {
|
|
300
|
+
const owner = owners.get(path) ?? "...";
|
|
301
|
+
const owned = owner === user.name.toLowerCase() || owner === user.sid.toLowerCase();
|
|
302
|
+
try {
|
|
303
|
+
if (!entries.length || owner.includes("..."))
|
|
304
|
+
throw new Error("acl_unavailable");
|
|
305
|
+
verdicts.set(path, !owned ? "owner" : privateEntries(entries, user, false) ? "ok" : "ace");
|
|
306
|
+
} catch {
|
|
307
|
+
verdicts.set(path, "acl_unavailable");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return verdicts;
|
|
311
|
+
}
|
|
312
|
+
var execute, quote, identityArgs, currentIdentity, ttl, identityPending, aclArgs, hookOptions, hookExecute, stdout, auditDirectories, directoryPending, ownerWidth, unknownOwner;
|
|
313
|
+
var init_runtimeSigners = __esm({
|
|
314
|
+
"packages/shared/dist/runtimeSigners.js"() {
|
|
315
|
+
"use strict";
|
|
316
|
+
execute = (file, args, input) => {
|
|
317
|
+
const result = promisify(execFile)(file, args);
|
|
318
|
+
if (input === void 0)
|
|
319
|
+
return result;
|
|
320
|
+
return new Promise((resolve3, reject) => {
|
|
321
|
+
void result.then(resolve3, reject);
|
|
322
|
+
result.child.stdin?.on("error", reject);
|
|
323
|
+
result.child.stdin?.end(input);
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
quote = (text) => `'${text.replaceAll("'", "''")}'`;
|
|
327
|
+
identityArgs = ["/user", "/fo", "csv", "/nh"];
|
|
328
|
+
ttl = 10 * 6e4;
|
|
329
|
+
aclArgs = (path, sid) => [
|
|
330
|
+
path,
|
|
331
|
+
"/inheritance:r",
|
|
332
|
+
"/grant:r",
|
|
333
|
+
`*${sid}:(OI)(CI)F`
|
|
334
|
+
];
|
|
335
|
+
hookOptions = (file, args) => ({
|
|
336
|
+
encoding: args.includes("/u") ? "utf16le" : "utf8",
|
|
337
|
+
timeout: win32.basename(file) === "powershell.exe" ? 3e4 : 5e3,
|
|
338
|
+
windowsHide: true,
|
|
339
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
340
|
+
windowsVerbatimArguments: win32.basename(file) === "cmd.exe"
|
|
341
|
+
});
|
|
342
|
+
hookExecute = (file, args, input) => {
|
|
343
|
+
const treeListing = win32.basename(file) === "icacls.exe" && args.includes("/t") || win32.basename(file) === "cmd.exe" && args.some((arg) => arg.startsWith("dir /q /a /s "));
|
|
344
|
+
[file, args] = hookInvocation(file, args);
|
|
345
|
+
const options = hookOptions(file, args);
|
|
346
|
+
if (treeListing)
|
|
347
|
+
options.timeout = 3e4;
|
|
348
|
+
return new Promise((resolve3, reject) => {
|
|
349
|
+
const child = execFile(file, args, options, (error, stdout2) => error ? reject(error) : resolve3({ stdout: stdout2 }));
|
|
350
|
+
if (input !== void 0) {
|
|
351
|
+
child.stdin?.on("error", reject);
|
|
352
|
+
child.stdin?.end(input);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
stdout = (value) => value.stdout;
|
|
357
|
+
auditDirectories = /* @__PURE__ */ new Set();
|
|
358
|
+
directoryPending = /* @__PURE__ */ new Map();
|
|
359
|
+
ownerWidth = 23;
|
|
360
|
+
unknownOwner = (owner, user) => user.name.length >= ownerWidth || owner.length >= ownerWidth || owner.includes("...");
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// packages/shared/dist/projectIdentityFile.js
|
|
365
|
+
var init_projectIdentityFile = __esm({
|
|
366
|
+
"packages/shared/dist/projectIdentityFile.js"() {
|
|
367
|
+
"use strict";
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// packages/shared/dist/repositoryRoot.js
|
|
372
|
+
var init_repositoryRoot = __esm({
|
|
373
|
+
"packages/shared/dist/repositoryRoot.js"() {
|
|
374
|
+
"use strict";
|
|
375
|
+
init_projectIdentityFile();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// packages/shared/dist/runtimeReader.js
|
|
380
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
381
|
+
import { constants } from "node:fs";
|
|
382
|
+
import { lstat, open, readdir } from "node:fs/promises";
|
|
383
|
+
import { dirname as dirname2, join as join2, parse, resolve as resolve2, sep as sep2 } from "node:path";
|
|
384
|
+
function safePath(path) {
|
|
385
|
+
if (typeof path !== "string" || !/^[a-zA-Z0-9_@.+/-]+$/.test(path) || path.split("/").some((p) => !p || p === "." || p === "..") || path.includes(":"))
|
|
386
|
+
throw new RuntimeError("permission");
|
|
387
|
+
return path;
|
|
388
|
+
}
|
|
389
|
+
var RuntimeError, hash, missing, versionName, statIdentity, RuntimeReader;
|
|
390
|
+
var init_runtimeReader = __esm({
|
|
391
|
+
"packages/shared/dist/runtimeReader.js"() {
|
|
392
|
+
"use strict";
|
|
393
|
+
init_runtimeSigners();
|
|
394
|
+
init_runtimeSigners();
|
|
395
|
+
RuntimeError = class extends Error {
|
|
396
|
+
reason;
|
|
397
|
+
constructor(reason) {
|
|
398
|
+
super(reason);
|
|
399
|
+
this.reason = reason;
|
|
400
|
+
this.name = "RuntimeError";
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
hash = (bytes) => createHash2("sha256").update(bytes).digest("hex");
|
|
404
|
+
missing = (e) => e.code === "ENOENT";
|
|
405
|
+
versionName = (v) => {
|
|
406
|
+
if (!/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(v))
|
|
407
|
+
throw new RuntimeError("permission");
|
|
408
|
+
return v;
|
|
409
|
+
};
|
|
410
|
+
statIdentity = (stat) => [stat.dev, stat.ino, stat.mode, stat.uid, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
|
|
411
|
+
RuntimeReader = class _RuntimeReader {
|
|
412
|
+
run;
|
|
413
|
+
options;
|
|
414
|
+
now;
|
|
415
|
+
state;
|
|
416
|
+
static windowsSignatures = /* @__PURE__ */ new Map();
|
|
417
|
+
// Omitted batch verdicts use the per-path fallback before entering the cache.
|
|
418
|
+
windowsAudit = /* @__PURE__ */ new Map();
|
|
419
|
+
windowsStats = /* @__PURE__ */ new Map();
|
|
420
|
+
windowsDirectories = /* @__PURE__ */ new Set();
|
|
421
|
+
audits = Promise.resolve();
|
|
422
|
+
auditsInFlight = /* @__PURE__ */ new Map();
|
|
423
|
+
activeOperation = false;
|
|
424
|
+
operations = Promise.resolve();
|
|
425
|
+
constructor(state, run, options = {}, now = () => performance.now()) {
|
|
426
|
+
this.run = run;
|
|
427
|
+
this.options = options;
|
|
428
|
+
this.now = now;
|
|
429
|
+
this.state = resolve2(state);
|
|
430
|
+
}
|
|
431
|
+
pointerPath(artifact) {
|
|
432
|
+
if (!["cli", "scanner", "claude-code", "codex", "cursor", "grok"].includes(artifact))
|
|
433
|
+
throw new RuntimeError("permission");
|
|
434
|
+
return join2(this.state, "runtimes", artifact, "current");
|
|
435
|
+
}
|
|
436
|
+
guardedPath(path) {
|
|
437
|
+
const absolute = resolve2(path);
|
|
438
|
+
if (absolute !== this.state && !absolute.startsWith(this.state + sep2) || process.platform === "win32" && /[\r\n\t]/.test(absolute))
|
|
439
|
+
throw new RuntimeError("permission");
|
|
440
|
+
return absolute;
|
|
441
|
+
}
|
|
442
|
+
auditRoot(path) {
|
|
443
|
+
const [area, artifact] = path.slice(this.state.length + 1).split(sep2);
|
|
444
|
+
return area === "runtimes" ? join2(this.state, area, ...artifact ? [artifact] : []) : path;
|
|
445
|
+
}
|
|
446
|
+
traceAudit(event) {
|
|
447
|
+
const level = process.env.MNEMONIK_AUDIT_TRACE;
|
|
448
|
+
if (level !== "1" && (level !== "misses" || event.cache === "hit"))
|
|
449
|
+
return;
|
|
450
|
+
process.stderr.write(JSON.stringify({
|
|
451
|
+
type: "runtime-audit",
|
|
452
|
+
pid: process.pid,
|
|
453
|
+
module: import.meta.url,
|
|
454
|
+
activeOperation: this.activeOperation,
|
|
455
|
+
...event
|
|
456
|
+
}) + "\n");
|
|
457
|
+
}
|
|
458
|
+
invalidateAudit(path) {
|
|
459
|
+
const root = this.auditRoot(path);
|
|
460
|
+
for (const key of this.windowsAudit.keys())
|
|
461
|
+
if (key === root || (!this.options.cacheDirectories || root.startsWith(join2(this.state, "runtimes") + sep2)) && key.startsWith(root + sep2))
|
|
462
|
+
this.windowsAudit.delete(key);
|
|
463
|
+
}
|
|
464
|
+
audit(paths) {
|
|
465
|
+
const root = this.auditRoot(paths.at(-1) ?? this.state);
|
|
466
|
+
const pending = this.auditsInFlight.get(root);
|
|
467
|
+
if (pending)
|
|
468
|
+
return pending;
|
|
469
|
+
const audit = this.audits.then(() => this.auditPending(paths)).finally(() => this.auditsInFlight.delete(root));
|
|
470
|
+
this.auditsInFlight.set(root, audit);
|
|
471
|
+
this.audits = audit.catch(() => {
|
|
472
|
+
});
|
|
473
|
+
return audit;
|
|
474
|
+
}
|
|
475
|
+
async auditPending(paths) {
|
|
476
|
+
if (process.platform !== "win32")
|
|
477
|
+
return;
|
|
478
|
+
const pending = /* @__PURE__ */ new Set();
|
|
479
|
+
for (const path of paths) {
|
|
480
|
+
let part = this.guardedPath(path);
|
|
481
|
+
while (true) {
|
|
482
|
+
const cached = this.windowsAudit.get(part);
|
|
483
|
+
const expired = !!cached && !this.activeOperation && !(this.options.cacheDirectories && this.windowsDirectories.has(part)) && this.now() - cached.checkedAt >= 2e3;
|
|
484
|
+
this.traceAudit({
|
|
485
|
+
event: "cache",
|
|
486
|
+
requested: path,
|
|
487
|
+
path: part,
|
|
488
|
+
root: this.auditRoot(path),
|
|
489
|
+
cache: !cached ? "miss" : expired ? "ttl-expired" : "hit"
|
|
490
|
+
});
|
|
491
|
+
if (!cached || expired)
|
|
492
|
+
pending.add(part);
|
|
493
|
+
if (part === this.state)
|
|
494
|
+
break;
|
|
495
|
+
part = dirname2(part);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (!pending.size)
|
|
499
|
+
return;
|
|
500
|
+
try {
|
|
501
|
+
const root = this.auditRoot(paths.at(-1) ?? this.state);
|
|
502
|
+
const ancestors = [...pending].filter((path) => path !== root && !path.startsWith(root + sep2));
|
|
503
|
+
const tree = [...pending].filter((path) => !ancestors.includes(path));
|
|
504
|
+
for (const part of [...ancestors, ...tree.length ? [root] : []]) {
|
|
505
|
+
const recursive = part === root && root.startsWith(join2(this.state, "runtimes") + sep2);
|
|
506
|
+
this.traceAudit({ event: "audit", root: part, recursive });
|
|
507
|
+
const requested = part === root ? tree : [part];
|
|
508
|
+
if (this.options.cacheDirectories && !this.windowsDirectories.has(part) && this.windowsAudit.get(part)?.verdict === "ok") {
|
|
509
|
+
await verifyWindowsAcl(part, this.run, this.state);
|
|
510
|
+
this.windowsAudit.set(part, { verdict: "ok", checkedAt: this.now() });
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
const verdicts = await auditWindowsPermissions(part, requested, this.run, recursive, this.state);
|
|
514
|
+
const checkedAt = this.now();
|
|
515
|
+
for (const path of /* @__PURE__ */ new Set([...requested, ...verdicts.keys()])) {
|
|
516
|
+
let verdict = verdicts.get(path);
|
|
517
|
+
if (verdict === void 0) {
|
|
518
|
+
await verifyWindowsPermission(path, this.run, this.state);
|
|
519
|
+
verdict = "ok";
|
|
520
|
+
}
|
|
521
|
+
this.windowsAudit.set(path, { verdict, checkedAt });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw new RuntimeError(error instanceof Error && error.message === "acl_permissions" ? "permission" : "acl_unavailable");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async inspect(path, directory = false, allowMissing = false, replacing = false) {
|
|
529
|
+
const absolute = this.guardedPath(path);
|
|
530
|
+
const windowsPaths = [];
|
|
531
|
+
let part = parse(absolute).root;
|
|
532
|
+
for (const name of absolute.slice(part.length).split(sep2)) {
|
|
533
|
+
part = join2(part, name);
|
|
534
|
+
const stat = await lstat(part).catch((e) => {
|
|
535
|
+
if (allowMissing && missing(e))
|
|
536
|
+
return null;
|
|
537
|
+
throw e;
|
|
538
|
+
});
|
|
539
|
+
if (!stat)
|
|
540
|
+
break;
|
|
541
|
+
if (stat.isSymbolicLink() || (part === absolute ? directory ? !stat.isDirectory() : !stat.isFile() : !stat.isDirectory()))
|
|
542
|
+
throw new RuntimeError("permission");
|
|
543
|
+
if (part !== this.state && !part.startsWith(this.state + sep2))
|
|
544
|
+
continue;
|
|
545
|
+
if (process.platform === "win32") {
|
|
546
|
+
if (replacing && part === absolute)
|
|
547
|
+
continue;
|
|
548
|
+
if (part === this.state && !this.windowsStats.has(part)) {
|
|
549
|
+
await prepareWindowsAclDirectory(this.state, this.run).catch((error) => {
|
|
550
|
+
throw new RuntimeError(error instanceof Error && error.message === "acl_permissions" ? "permission" : "acl_unavailable");
|
|
551
|
+
});
|
|
552
|
+
Object.assign(stat, await lstat(part));
|
|
553
|
+
}
|
|
554
|
+
if (stat.isDirectory())
|
|
555
|
+
this.windowsDirectories.add(part);
|
|
556
|
+
const identity = statIdentity(stat);
|
|
557
|
+
const previous = this.windowsStats.get(part);
|
|
558
|
+
if (previous !== void 0 && previous !== identity) {
|
|
559
|
+
this.traceAudit({
|
|
560
|
+
event: "invalidate",
|
|
561
|
+
requested: absolute,
|
|
562
|
+
path: part,
|
|
563
|
+
root: this.auditRoot(part),
|
|
564
|
+
reason: "stat-identity",
|
|
565
|
+
previous,
|
|
566
|
+
current: identity
|
|
567
|
+
});
|
|
568
|
+
this.invalidateAudit(part);
|
|
569
|
+
}
|
|
570
|
+
this.windowsStats.set(part, identity);
|
|
571
|
+
windowsPaths.push(part);
|
|
572
|
+
} else if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
573
|
+
throw new RuntimeError("permission");
|
|
574
|
+
}
|
|
575
|
+
await this.audit(windowsPaths);
|
|
576
|
+
for (const part2 of windowsPaths) {
|
|
577
|
+
const verdict = this.windowsAudit.get(part2)?.verdict;
|
|
578
|
+
if (verdict !== "ok")
|
|
579
|
+
throw new RuntimeError(verdict === void 0 || verdict === "acl_unavailable" ? "acl_unavailable" : "permission");
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/** Call only after an owned atomic replacement: its parent ctime is our own write. */
|
|
583
|
+
async recordReplacement(path) {
|
|
584
|
+
if (process.platform !== "win32" || !this.options.cacheDirectories)
|
|
585
|
+
return;
|
|
586
|
+
const absolute = this.guardedPath(path);
|
|
587
|
+
const parent = dirname2(absolute);
|
|
588
|
+
const stat = await lstat(parent);
|
|
589
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !this.windowsStats.get(parent)?.startsWith(`${stat.dev}:${stat.ino}:`))
|
|
590
|
+
throw new RuntimeError("permission");
|
|
591
|
+
this.windowsStats.set(parent, statIdentity(stat));
|
|
592
|
+
this.windowsAudit.delete(absolute);
|
|
593
|
+
this.windowsStats.delete(absolute);
|
|
594
|
+
}
|
|
595
|
+
async bytes(path) {
|
|
596
|
+
await this.inspect(path);
|
|
597
|
+
const fd = await open(path, constants.O_RDONLY | (process.platform === "win32" ? 0 : constants.O_NOFOLLOW));
|
|
598
|
+
try {
|
|
599
|
+
return await fd.readFile();
|
|
600
|
+
} finally {
|
|
601
|
+
await fd.close();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/** An owned inheritable ACL update can change every cached descendant's ctime. */
|
|
605
|
+
async recordAclWrite(path) {
|
|
606
|
+
const root = this.guardedPath(path);
|
|
607
|
+
for (const known of this.windowsStats.keys())
|
|
608
|
+
if (known === root || known.startsWith(root + sep2))
|
|
609
|
+
this.windowsStats.set(known, statIdentity(await lstat(known)));
|
|
610
|
+
this.invalidateAudit(root);
|
|
611
|
+
}
|
|
612
|
+
async pointer(artifact) {
|
|
613
|
+
try {
|
|
614
|
+
const p = JSON.parse((await this.bytes(this.pointerPath(artifact))).toString());
|
|
615
|
+
for (const ref of [p.current, ...p.previous ? [p.previous] : []]) {
|
|
616
|
+
versionName(ref.version);
|
|
617
|
+
if (!/^[a-f0-9]{64}$/.test(ref.manifestSha256))
|
|
618
|
+
throw new RuntimeError("digest_mismatch");
|
|
619
|
+
}
|
|
620
|
+
return p;
|
|
621
|
+
} catch (e) {
|
|
622
|
+
if (missing(e))
|
|
623
|
+
return void 0;
|
|
624
|
+
throw e instanceof RuntimeError ? e : new RuntimeError("digest_mismatch");
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
async operation(work) {
|
|
628
|
+
const operation = this.operations.then(async () => {
|
|
629
|
+
this.activeOperation = true;
|
|
630
|
+
try {
|
|
631
|
+
return await work();
|
|
632
|
+
} finally {
|
|
633
|
+
this.activeOperation = false;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
this.operations = operation.catch(() => void 0);
|
|
637
|
+
return operation;
|
|
638
|
+
}
|
|
639
|
+
verifyAt(artifact, ref, directory) {
|
|
640
|
+
return this.operation(() => this.verifyOpen(artifact, ref, directory));
|
|
641
|
+
}
|
|
642
|
+
async verifyOpen(artifact, ref, directory) {
|
|
643
|
+
let bytes;
|
|
644
|
+
try {
|
|
645
|
+
bytes = await this.bytes(join2(directory, "manifest.json"));
|
|
646
|
+
} catch (e) {
|
|
647
|
+
if (missing(e))
|
|
648
|
+
throw new RuntimeError("manifest_missing");
|
|
649
|
+
throw e;
|
|
650
|
+
}
|
|
651
|
+
if (hash(bytes) !== ref.manifestSha256)
|
|
652
|
+
throw new RuntimeError("digest_mismatch");
|
|
653
|
+
let m;
|
|
654
|
+
try {
|
|
655
|
+
m = JSON.parse(bytes.toString());
|
|
656
|
+
} catch {
|
|
657
|
+
throw new RuntimeError("digest_mismatch");
|
|
658
|
+
}
|
|
659
|
+
if (!m || m.schemaVersion !== 1 || !m.source || m.artifact !== artifact || m.version !== ref.version || !m.files || !m.files[safePath(m.entry)])
|
|
660
|
+
throw new RuntimeError("digest_mismatch");
|
|
661
|
+
if (artifact === "scanner" && (m.source.kind !== "release" || typeof m.source.url !== "string" || !m.source.url.startsWith("https://") || (!m.signer || m.signer.platform !== process.platform) && !(this.options.allowUnsigned && !m.signer && m.signingStatus === "unsigned")))
|
|
662
|
+
throw new RuntimeError("unsigned");
|
|
663
|
+
if (artifact === "cli" && (m.source.kind !== "npm" || !Array.isArray(m.source.packages) || !m.source.packages.some((p) => p.name === "@mnemonik/cli" && p.version === m.version)))
|
|
664
|
+
throw new RuntimeError("unsigned");
|
|
665
|
+
if (process.platform === "win32")
|
|
666
|
+
await this.audit(Object.keys(m.files).map((name) => join2(directory, safePath(name))));
|
|
667
|
+
const actual = [];
|
|
668
|
+
const walk = async (dir, prefix = "") => {
|
|
669
|
+
await this.inspect(dir, true);
|
|
670
|
+
for (const name of await readdir(dir)) {
|
|
671
|
+
const key = prefix + name;
|
|
672
|
+
if (!prefix && name === "manifest.json")
|
|
673
|
+
continue;
|
|
674
|
+
const path = join2(dir, name);
|
|
675
|
+
if ((await lstat(path)).isDirectory())
|
|
676
|
+
await walk(path, key + "/");
|
|
677
|
+
else {
|
|
678
|
+
await this.inspect(path);
|
|
679
|
+
actual.push(key);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
await walk(directory);
|
|
684
|
+
if (actual.sort().join("\n") !== Object.keys(m.files).sort().join("\n"))
|
|
685
|
+
throw new RuntimeError("digest_mismatch");
|
|
686
|
+
let total = 0;
|
|
687
|
+
for (const [name, expected] of Object.entries(m.files)) {
|
|
688
|
+
if (!expected || !/^[a-f0-9]{64}$/.test(expected.sha256) || !Number.isSafeInteger(expected.size) || expected.size < 0 || typeof expected.executable !== "boolean")
|
|
689
|
+
throw new RuntimeError("digest_mismatch");
|
|
690
|
+
const path = join2(directory, safePath(name));
|
|
691
|
+
const contents = await this.bytes(path);
|
|
692
|
+
if (hash(contents) !== expected.sha256 || contents.length !== expected.size)
|
|
693
|
+
throw new RuntimeError("digest_mismatch");
|
|
694
|
+
if (process.platform !== "win32" && ((await lstat(path)).mode & 511) !== (expected.executable ? 448 : 384))
|
|
695
|
+
throw new RuntimeError("permission");
|
|
696
|
+
total += contents.length;
|
|
697
|
+
}
|
|
698
|
+
if (total !== m.totalSize)
|
|
699
|
+
throw new RuntimeError("digest_mismatch");
|
|
700
|
+
if (m.signer) {
|
|
701
|
+
const signer = m.signer.platform === "linux" ? { ...m.signer, signature: join2(directory, safePath(m.signer.signature)) } : m.signer;
|
|
702
|
+
if (m.signer.platform === "linux" && !m.files[m.signer.signature])
|
|
703
|
+
throw new RuntimeError("unsigned");
|
|
704
|
+
try {
|
|
705
|
+
const entry = join2(directory, m.entry);
|
|
706
|
+
if (signer.platform === "win32") {
|
|
707
|
+
const key = JSON.stringify([entry, m.files[m.entry]?.sha256, signer.identity]);
|
|
708
|
+
let verification = _RuntimeReader.windowsSignatures.get(key);
|
|
709
|
+
if (!verification) {
|
|
710
|
+
verification = verifySigner(entry, signer, this.run).catch((error) => {
|
|
711
|
+
_RuntimeReader.windowsSignatures.delete(key);
|
|
712
|
+
throw error;
|
|
713
|
+
});
|
|
714
|
+
_RuntimeReader.windowsSignatures.set(key, verification);
|
|
715
|
+
}
|
|
716
|
+
await verification;
|
|
717
|
+
} else
|
|
718
|
+
await verifySigner(entry, signer, this.run);
|
|
719
|
+
} catch {
|
|
720
|
+
throw new RuntimeError("unsigned");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return { directory, manifest: m, entry: join2(directory, m.entry), reference: ref };
|
|
724
|
+
}
|
|
725
|
+
async verifyRuntime(artifact) {
|
|
726
|
+
return this.operation(async () => {
|
|
727
|
+
const p = await this.pointer(artifact);
|
|
728
|
+
if (!p)
|
|
729
|
+
throw new RuntimeError("manifest_missing");
|
|
730
|
+
return this.verifyOpen(artifact, p.current, join2(dirname2(this.pointerPath(artifact)), p.current.version));
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// packages/shared/dist/projectSetupHandoff.js
|
|
738
|
+
var init_projectSetupHandoff = __esm({
|
|
739
|
+
"packages/shared/dist/projectSetupHandoff.js"() {
|
|
740
|
+
"use strict";
|
|
741
|
+
init_runtimeSigners();
|
|
742
|
+
init_runtimeReader();
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// packages/shared/dist/hookRuntime.js
|
|
747
|
+
var init_hookRuntime = __esm({
|
|
748
|
+
"packages/shared/dist/hookRuntime.js"() {
|
|
749
|
+
"use strict";
|
|
750
|
+
init_runtimeSigners();
|
|
751
|
+
init_repositoryRoot();
|
|
752
|
+
init_runtimeReader();
|
|
753
|
+
init_runtimeSigners();
|
|
754
|
+
init_projectSetupHandoff();
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// packages/cli/src/runtime/store.ts
|
|
759
|
+
init_hookRuntime();
|
|
760
|
+
init_hookRuntime();
|
|
761
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
762
|
+
import { lstat as lstat2, mkdir, mkdtemp, open as open2, readdir as readdir2, rename, rm } from "node:fs/promises";
|
|
763
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
764
|
+
var RuntimeStore = class extends RuntimeReader {
|
|
765
|
+
async withMutationLock(artifact, work) {
|
|
766
|
+
const { withLock } = await import('../vendor/local-setup.js');
|
|
767
|
+
return withLock(join3(dirname3(this.pointerPath(artifact)), "mutation"), 5e3, work);
|
|
768
|
+
}
|
|
769
|
+
async installRuntime(artifact, version, source) {
|
|
770
|
+
const base = dirname3(this.pointerPath(artifact));
|
|
771
|
+
versionName(version);
|
|
772
|
+
if (source.manifest.version !== version || source.manifest.artifact !== artifact)
|
|
773
|
+
throw new RuntimeError("digest_mismatch");
|
|
774
|
+
const manifest = Buffer.from(JSON.stringify(source.manifest));
|
|
775
|
+
const ref = { version, manifestSha256: hash(manifest) };
|
|
776
|
+
const pointer = await this.pointer(artifact);
|
|
777
|
+
const installed = pointer?.current;
|
|
778
|
+
if (installed?.version === version && installed.manifestSha256 === ref.manifestSha256)
|
|
779
|
+
return this.verifyAt(artifact, installed, join3(base, installed.version));
|
|
780
|
+
const { atomicWrite, withLock, windowsCurrentUserAcl } = await import('../vendor/local-setup.js');
|
|
781
|
+
await this.inspect(base, true, true);
|
|
782
|
+
const created = [];
|
|
783
|
+
if (process.platform === "win32")
|
|
784
|
+
for (const path of [this.state, dirname3(base), base])
|
|
785
|
+
await lstat2(path).catch((error) => {
|
|
786
|
+
if (!missing(error)) throw error;
|
|
787
|
+
created.push(path);
|
|
788
|
+
});
|
|
789
|
+
await mkdir(base, { recursive: true, mode: 448 });
|
|
790
|
+
for (const path of created) {
|
|
791
|
+
await windowsCurrentUserAcl(path, true);
|
|
792
|
+
await this.recordAclWrite(path);
|
|
793
|
+
}
|
|
794
|
+
await this.inspect(base, true);
|
|
795
|
+
return withLock(join3(base, "mutation"), 5e3, async (assertOwned) => {
|
|
796
|
+
const previous = await this.pointer(artifact);
|
|
797
|
+
if (previous) await this.verifyRuntime(artifact);
|
|
798
|
+
const stage = await mkdtemp(join3(base, ".stage-"));
|
|
799
|
+
const target = join3(base, version);
|
|
800
|
+
try {
|
|
801
|
+
for (const [name, bytes] of Object.entries(source.files)) {
|
|
802
|
+
const path = join3(stage, safePath(name));
|
|
803
|
+
if (name === "manifest.json") throw new RuntimeError("permission");
|
|
804
|
+
await mkdir(dirname3(path), { recursive: true, mode: 448 });
|
|
805
|
+
const fd = await open2(
|
|
806
|
+
path,
|
|
807
|
+
"wx",
|
|
808
|
+
source.manifest.files[name]?.executable ? 448 : 384
|
|
809
|
+
);
|
|
810
|
+
try {
|
|
811
|
+
await fd.writeFile(bytes);
|
|
812
|
+
await fd.sync();
|
|
813
|
+
} finally {
|
|
814
|
+
await fd.close();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
await atomicWrite(join3(stage, "manifest.json"), manifest, void 0, assertOwned);
|
|
818
|
+
await this.verifyAt(artifact, ref, stage);
|
|
819
|
+
const known = [previous?.current, previous?.previous].find((r) => r?.version === version);
|
|
820
|
+
if (known) {
|
|
821
|
+
const existing = await this.verifyAt(artifact, known, target);
|
|
822
|
+
if (JSON.stringify(existing.manifest.files) !== JSON.stringify(source.manifest.files) || existing.manifest.entry !== source.manifest.entry)
|
|
823
|
+
throw new RuntimeError("digest_mismatch");
|
|
824
|
+
ref.manifestSha256 = known.manifestSha256;
|
|
825
|
+
}
|
|
826
|
+
if (process.platform !== "win32") {
|
|
827
|
+
const sync = async (dir) => {
|
|
828
|
+
for (const entry of await readdir2(dir, { withFileTypes: true }))
|
|
829
|
+
if (entry.isDirectory()) await sync(join3(dir, entry.name));
|
|
830
|
+
const fd = await open2(dir, "r");
|
|
831
|
+
try {
|
|
832
|
+
await fd.sync();
|
|
833
|
+
} finally {
|
|
834
|
+
await fd.close();
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
await sync(stage);
|
|
838
|
+
}
|
|
839
|
+
await assertOwned();
|
|
840
|
+
try {
|
|
841
|
+
await lstat2(target);
|
|
842
|
+
await this.verifyAt(artifact, ref, target);
|
|
843
|
+
} catch (e) {
|
|
844
|
+
if (!missing(e)) throw e;
|
|
845
|
+
await rename(stage, target);
|
|
846
|
+
}
|
|
847
|
+
await this.verifyAt(artifact, ref, target);
|
|
848
|
+
if (previous?.current.version !== version)
|
|
849
|
+
await atomicWrite(
|
|
850
|
+
this.pointerPath(artifact),
|
|
851
|
+
Buffer.from(JSON.stringify({ current: ref, previous: previous?.current })),
|
|
852
|
+
void 0,
|
|
853
|
+
assertOwned
|
|
854
|
+
);
|
|
855
|
+
return this.verifyRuntime(artifact);
|
|
856
|
+
} finally {
|
|
857
|
+
await rm(stage, { recursive: true, force: true });
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
async rollbackRuntime(artifact, expectedCurrent) {
|
|
862
|
+
await this.inspect(dirname3(this.pointerPath(artifact)), true);
|
|
863
|
+
const { atomicWrite, withLock } = await import('../vendor/local-setup.js');
|
|
864
|
+
return withLock(
|
|
865
|
+
join3(dirname3(this.pointerPath(artifact)), "mutation"),
|
|
866
|
+
5e3,
|
|
867
|
+
async (assertOwned) => {
|
|
868
|
+
const p = await this.pointer(artifact);
|
|
869
|
+
if (!p?.previous) throw new RuntimeError("manifest_missing");
|
|
870
|
+
if (expectedCurrent && p.current.manifestSha256 !== expectedCurrent.manifestSha256)
|
|
871
|
+
throw new RuntimeError("digest_mismatch");
|
|
872
|
+
await this.verifyAt(
|
|
873
|
+
artifact,
|
|
874
|
+
p.previous,
|
|
875
|
+
join3(dirname3(this.pointerPath(artifact)), p.previous.version)
|
|
876
|
+
);
|
|
877
|
+
await atomicWrite(
|
|
878
|
+
this.pointerPath(artifact),
|
|
879
|
+
Buffer.from(JSON.stringify({ current: p.previous, previous: p.current })),
|
|
880
|
+
void 0,
|
|
881
|
+
assertOwned
|
|
882
|
+
);
|
|
883
|
+
return this.verifyRuntime(artifact);
|
|
884
|
+
}
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
async function updateRuntime(update) {
|
|
889
|
+
const source = await update.source();
|
|
890
|
+
const { artifact, version } = source.manifest;
|
|
891
|
+
const before = await update.store.verifyRuntime(artifact);
|
|
892
|
+
const installed = await update.store.installRuntime(artifact, version, source);
|
|
893
|
+
try {
|
|
894
|
+
await update.restartManagedServices(update.store.pointerPath(artifact));
|
|
895
|
+
} catch (e) {
|
|
896
|
+
if (before.reference.version !== version)
|
|
897
|
+
await update.store.rollbackRuntime(artifact, installed.reference);
|
|
898
|
+
await update.restartManagedServices(update.store.pointerPath(artifact));
|
|
899
|
+
throw e;
|
|
900
|
+
}
|
|
901
|
+
return installed;
|
|
902
|
+
}
|
|
903
|
+
async function hostNpmSource(host, pin, fetcher = fetch) {
|
|
904
|
+
const { releaseBytes } = await import("./releaseSource.js");
|
|
905
|
+
const { unpack } = await import("./bootstrap.js");
|
|
906
|
+
if (pin.package !== `@mnemonik/${host}-hooks` || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(pin.version) || !pin.closure.length || pin.closure.length > 100 || pin.closure[0]?.name !== pin.package || pin.closure[0]?.version !== pin.version)
|
|
907
|
+
throw new RuntimeError("unsigned");
|
|
908
|
+
const files = {};
|
|
909
|
+
const packages = [];
|
|
910
|
+
const seen = /* @__PURE__ */ new Set();
|
|
911
|
+
const exactBytes = async (address, name, version) => {
|
|
912
|
+
try {
|
|
913
|
+
return await releaseBytes(address, fetcher);
|
|
914
|
+
} catch (error) {
|
|
915
|
+
if (error instanceof RuntimeError && error.reason === "manifest_missing")
|
|
916
|
+
error.message = `manifest_missing: ${name}@${version}`;
|
|
917
|
+
throw error;
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
for (const expected of pin.closure) {
|
|
921
|
+
const { name, version, integrity } = expected;
|
|
922
|
+
if (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(name) || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(version) || seen.has(name))
|
|
923
|
+
throw new RuntimeError("unsigned");
|
|
924
|
+
seen.add(name);
|
|
925
|
+
const metadata = JSON.parse(
|
|
926
|
+
(await exactBytes(
|
|
927
|
+
`https://registry.npmjs.org/${encodeURIComponent(name)}/${version}`,
|
|
928
|
+
name,
|
|
929
|
+
version
|
|
930
|
+
)).toString()
|
|
931
|
+
);
|
|
932
|
+
if (metadata.name !== name || metadata.version !== version) throw new RuntimeError("unsigned");
|
|
933
|
+
if (metadata.dist.integrity !== integrity) throw new RuntimeError("digest_mismatch");
|
|
934
|
+
const tarball = await exactBytes(metadata.dist.tarball, name, version);
|
|
935
|
+
const match = /^(sha512|sha256)-([A-Za-z0-9+/=]+)$/.exec(integrity);
|
|
936
|
+
if (!match || createHash3(match[1] ?? "sha512").update(tarball).digest("base64") !== match[2])
|
|
937
|
+
throw new RuntimeError("digest_mismatch");
|
|
938
|
+
const contents = unpack(tarball);
|
|
939
|
+
const pkg = JSON.parse(contents["package.json"]?.toString() ?? "{}");
|
|
940
|
+
if (pkg.name !== name || pkg.version !== version || ["preinstall", "install", "postinstall", "prepare"].some((k) => pkg.scripts?.[k]) || Object.keys(pkg.optionalDependencies ?? {}).length)
|
|
941
|
+
throw new RuntimeError("unsigned");
|
|
942
|
+
const prefix = `node_modules/${name}/`;
|
|
943
|
+
for (const [path, bytes] of Object.entries(contents)) files[prefix + path] = bytes;
|
|
944
|
+
packages.push({
|
|
945
|
+
name,
|
|
946
|
+
version: pkg.version,
|
|
947
|
+
integrity,
|
|
948
|
+
tarball: metadata.dist.tarball,
|
|
949
|
+
tarballSha256: hash(tarball)
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
files,
|
|
954
|
+
manifest: {
|
|
955
|
+
schemaVersion: 1,
|
|
956
|
+
artifact: host,
|
|
957
|
+
version: pin.version,
|
|
958
|
+
entry: `node_modules/${pin.package}/dist/hook.js`,
|
|
959
|
+
files: Object.fromEntries(
|
|
960
|
+
Object.entries(files).map(([name, bytes]) => [
|
|
961
|
+
name,
|
|
962
|
+
{ sha256: hash(bytes), size: bytes.length, executable: false }
|
|
963
|
+
])
|
|
964
|
+
),
|
|
965
|
+
totalSize: Object.values(files).reduce((sum, bytes) => sum + bytes.length, 0),
|
|
966
|
+
source: { kind: "npm", packages, launchedFrom: "@mnemonik/cli" }
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
export {
|
|
971
|
+
RuntimeError,
|
|
972
|
+
RuntimeStore,
|
|
973
|
+
hash,
|
|
974
|
+
hostNpmSource,
|
|
975
|
+
safePath,
|
|
976
|
+
updateRuntime
|
|
977
|
+
};
|