@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,408 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open, readdir } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join, parse, resolve, sep } from 'node:path';
|
|
5
|
+
import { verifySigner, verifyWindowsPermission, verifyWindowsAcl, prepareWindowsAclDirectory, auditWindowsPermissions, } from './runtimeSigners.js';
|
|
6
|
+
export class RuntimeError extends Error {
|
|
7
|
+
reason;
|
|
8
|
+
constructor(reason) {
|
|
9
|
+
super(reason);
|
|
10
|
+
this.reason = reason;
|
|
11
|
+
this.name = 'RuntimeError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const hash = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
15
|
+
export function safePath(path) {
|
|
16
|
+
if (typeof path !== 'string' ||
|
|
17
|
+
!/^[a-zA-Z0-9_@.+/-]+$/.test(path) ||
|
|
18
|
+
path.split('/').some((p) => !p || p === '.' || p === '..') ||
|
|
19
|
+
path.includes(':'))
|
|
20
|
+
throw new RuntimeError('permission');
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
export const missing = (e) => e.code === 'ENOENT';
|
|
24
|
+
export const versionName = (v) => {
|
|
25
|
+
if (!/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(v))
|
|
26
|
+
throw new RuntimeError('permission');
|
|
27
|
+
return v;
|
|
28
|
+
};
|
|
29
|
+
const statIdentity = (stat) => [stat.dev, stat.ino, stat.mode, stat.uid, stat.size, stat.mtimeMs, stat.ctimeMs].join(':');
|
|
30
|
+
/** Only stdlib and the accepted bootstrap's signer adapter may load before verifyRuntime returns. */
|
|
31
|
+
export class RuntimeReader {
|
|
32
|
+
run;
|
|
33
|
+
options;
|
|
34
|
+
now;
|
|
35
|
+
state;
|
|
36
|
+
static windowsSignatures = new Map();
|
|
37
|
+
// Omitted batch verdicts use the per-path fallback before entering the cache.
|
|
38
|
+
windowsAudit = new Map();
|
|
39
|
+
windowsStats = new Map();
|
|
40
|
+
windowsDirectories = new Set();
|
|
41
|
+
audits = Promise.resolve();
|
|
42
|
+
auditsInFlight = new Map();
|
|
43
|
+
activeOperation = false;
|
|
44
|
+
operations = Promise.resolve();
|
|
45
|
+
constructor(state, run, options = {}, now = () => performance.now()) {
|
|
46
|
+
this.run = run;
|
|
47
|
+
this.options = options;
|
|
48
|
+
this.now = now;
|
|
49
|
+
this.state = resolve(state);
|
|
50
|
+
}
|
|
51
|
+
pointerPath(artifact) {
|
|
52
|
+
if (!['cli', 'scanner', 'claude-code', 'codex', 'cursor', 'grok'].includes(artifact))
|
|
53
|
+
throw new RuntimeError('permission');
|
|
54
|
+
return join(this.state, 'runtimes', artifact, 'current');
|
|
55
|
+
}
|
|
56
|
+
guardedPath(path) {
|
|
57
|
+
const absolute = resolve(path);
|
|
58
|
+
if ((absolute !== this.state && !absolute.startsWith(this.state + sep)) ||
|
|
59
|
+
(process.platform === 'win32' && /[\r\n\t]/.test(absolute)))
|
|
60
|
+
throw new RuntimeError('permission');
|
|
61
|
+
return absolute;
|
|
62
|
+
}
|
|
63
|
+
auditRoot(path) {
|
|
64
|
+
const [area, artifact] = path.slice(this.state.length + 1).split(sep);
|
|
65
|
+
return area === 'runtimes' ? join(this.state, area, ...(artifact ? [artifact] : [])) : path;
|
|
66
|
+
}
|
|
67
|
+
traceAudit(event) {
|
|
68
|
+
const level = process.env.MNEMONIK_AUDIT_TRACE;
|
|
69
|
+
if (level !== '1' && (level !== 'misses' || event.cache === 'hit'))
|
|
70
|
+
return;
|
|
71
|
+
process.stderr.write(JSON.stringify({
|
|
72
|
+
type: 'runtime-audit',
|
|
73
|
+
pid: process.pid,
|
|
74
|
+
module: import.meta.url,
|
|
75
|
+
activeOperation: this.activeOperation,
|
|
76
|
+
...event,
|
|
77
|
+
}) + '\n');
|
|
78
|
+
}
|
|
79
|
+
invalidateAudit(path) {
|
|
80
|
+
const root = this.auditRoot(path);
|
|
81
|
+
for (const key of this.windowsAudit.keys())
|
|
82
|
+
if (key === root ||
|
|
83
|
+
((!this.options.cacheDirectories || root.startsWith(join(this.state, 'runtimes') + sep)) &&
|
|
84
|
+
key.startsWith(root + sep)))
|
|
85
|
+
this.windowsAudit.delete(key);
|
|
86
|
+
}
|
|
87
|
+
audit(paths) {
|
|
88
|
+
const root = this.auditRoot(paths.at(-1) ?? this.state);
|
|
89
|
+
const pending = this.auditsInFlight.get(root);
|
|
90
|
+
if (pending)
|
|
91
|
+
return pending;
|
|
92
|
+
const audit = this.audits
|
|
93
|
+
.then(() => this.auditPending(paths))
|
|
94
|
+
.finally(() => this.auditsInFlight.delete(root));
|
|
95
|
+
this.auditsInFlight.set(root, audit);
|
|
96
|
+
this.audits = audit.catch(() => { });
|
|
97
|
+
return audit;
|
|
98
|
+
}
|
|
99
|
+
async auditPending(paths) {
|
|
100
|
+
if (process.platform !== 'win32')
|
|
101
|
+
return;
|
|
102
|
+
const pending = new Set();
|
|
103
|
+
for (const path of paths) {
|
|
104
|
+
let part = this.guardedPath(path);
|
|
105
|
+
while (true) {
|
|
106
|
+
const cached = this.windowsAudit.get(part);
|
|
107
|
+
const expired = !!cached &&
|
|
108
|
+
!this.activeOperation &&
|
|
109
|
+
!(this.options.cacheDirectories && this.windowsDirectories.has(part)) &&
|
|
110
|
+
this.now() - cached.checkedAt >= 2_000;
|
|
111
|
+
this.traceAudit({
|
|
112
|
+
event: 'cache',
|
|
113
|
+
requested: path,
|
|
114
|
+
path: part,
|
|
115
|
+
root: this.auditRoot(path),
|
|
116
|
+
cache: !cached ? 'miss' : expired ? 'ttl-expired' : 'hit',
|
|
117
|
+
});
|
|
118
|
+
if (!cached || expired)
|
|
119
|
+
pending.add(part);
|
|
120
|
+
if (part === this.state)
|
|
121
|
+
break;
|
|
122
|
+
part = dirname(part);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!pending.size)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
const root = this.auditRoot(paths.at(-1) ?? this.state);
|
|
129
|
+
const ancestors = [...pending].filter((path) => path !== root && !path.startsWith(root + sep));
|
|
130
|
+
const tree = [...pending].filter((path) => !ancestors.includes(path));
|
|
131
|
+
for (const part of [...ancestors, ...(tree.length ? [root] : [])]) {
|
|
132
|
+
const recursive = part === root && root.startsWith(join(this.state, 'runtimes') + sep);
|
|
133
|
+
this.traceAudit({ event: 'audit', root: part, recursive });
|
|
134
|
+
const requested = part === root ? tree : [part];
|
|
135
|
+
if (this.options.cacheDirectories &&
|
|
136
|
+
!this.windowsDirectories.has(part) &&
|
|
137
|
+
this.windowsAudit.get(part)?.verdict === 'ok') {
|
|
138
|
+
await verifyWindowsAcl(part, this.run, this.state);
|
|
139
|
+
this.windowsAudit.set(part, { verdict: 'ok', checkedAt: this.now() });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const verdicts = await auditWindowsPermissions(part, requested, this.run, recursive, this.state);
|
|
143
|
+
const checkedAt = this.now();
|
|
144
|
+
for (const path of new Set([...requested, ...verdicts.keys()])) {
|
|
145
|
+
let verdict = verdicts.get(path);
|
|
146
|
+
if (verdict === undefined) {
|
|
147
|
+
await verifyWindowsPermission(path, this.run, this.state);
|
|
148
|
+
verdict = 'ok';
|
|
149
|
+
}
|
|
150
|
+
this.windowsAudit.set(path, { verdict, checkedAt });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new RuntimeError(error instanceof Error && error.message === 'acl_permissions'
|
|
156
|
+
? 'permission'
|
|
157
|
+
: 'acl_unavailable');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async inspect(path, directory = false, allowMissing = false, replacing = false) {
|
|
161
|
+
const absolute = this.guardedPath(path);
|
|
162
|
+
const windowsPaths = [];
|
|
163
|
+
let part = parse(absolute).root;
|
|
164
|
+
for (const name of absolute.slice(part.length).split(sep)) {
|
|
165
|
+
part = join(part, name);
|
|
166
|
+
const stat = await lstat(part).catch((e) => {
|
|
167
|
+
if (allowMissing && missing(e))
|
|
168
|
+
return null;
|
|
169
|
+
throw e;
|
|
170
|
+
});
|
|
171
|
+
if (!stat)
|
|
172
|
+
break;
|
|
173
|
+
if (stat.isSymbolicLink() ||
|
|
174
|
+
(part === absolute
|
|
175
|
+
? directory
|
|
176
|
+
? !stat.isDirectory()
|
|
177
|
+
: !stat.isFile()
|
|
178
|
+
: !stat.isDirectory()))
|
|
179
|
+
throw new RuntimeError('permission');
|
|
180
|
+
if (part !== this.state && !part.startsWith(this.state + sep))
|
|
181
|
+
continue;
|
|
182
|
+
if (process.platform === 'win32') {
|
|
183
|
+
if (replacing && part === absolute)
|
|
184
|
+
continue;
|
|
185
|
+
if (part === this.state && !this.windowsStats.has(part)) {
|
|
186
|
+
await prepareWindowsAclDirectory(this.state, this.run).catch((error) => {
|
|
187
|
+
throw new RuntimeError(error instanceof Error && error.message === 'acl_permissions'
|
|
188
|
+
? 'permission'
|
|
189
|
+
: 'acl_unavailable');
|
|
190
|
+
});
|
|
191
|
+
// Initial creation is our own write, before this directory enters the cache.
|
|
192
|
+
Object.assign(stat, await lstat(part));
|
|
193
|
+
}
|
|
194
|
+
if (stat.isDirectory())
|
|
195
|
+
this.windowsDirectories.add(part);
|
|
196
|
+
const identity = statIdentity(stat);
|
|
197
|
+
const previous = this.windowsStats.get(part);
|
|
198
|
+
if (previous !== undefined && previous !== identity) {
|
|
199
|
+
this.traceAudit({
|
|
200
|
+
event: 'invalidate',
|
|
201
|
+
requested: absolute,
|
|
202
|
+
path: part,
|
|
203
|
+
root: this.auditRoot(part),
|
|
204
|
+
reason: 'stat-identity',
|
|
205
|
+
previous,
|
|
206
|
+
current: identity,
|
|
207
|
+
});
|
|
208
|
+
this.invalidateAudit(part);
|
|
209
|
+
}
|
|
210
|
+
this.windowsStats.set(part, identity);
|
|
211
|
+
windowsPaths.push(part);
|
|
212
|
+
}
|
|
213
|
+
else if (stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0)
|
|
214
|
+
throw new RuntimeError('permission');
|
|
215
|
+
}
|
|
216
|
+
await this.audit(windowsPaths);
|
|
217
|
+
for (const part of windowsPaths) {
|
|
218
|
+
const verdict = this.windowsAudit.get(part)?.verdict;
|
|
219
|
+
if (verdict !== 'ok')
|
|
220
|
+
throw new RuntimeError(verdict === undefined || verdict === 'acl_unavailable' ? 'acl_unavailable' : 'permission');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** Call only after an owned atomic replacement: its parent ctime is our own write. */
|
|
224
|
+
async recordReplacement(path) {
|
|
225
|
+
if (process.platform !== 'win32' || !this.options.cacheDirectories)
|
|
226
|
+
return;
|
|
227
|
+
const absolute = this.guardedPath(path);
|
|
228
|
+
const parent = dirname(absolute);
|
|
229
|
+
const stat = await lstat(parent);
|
|
230
|
+
if (!stat.isDirectory() ||
|
|
231
|
+
stat.isSymbolicLink() ||
|
|
232
|
+
!this.windowsStats.get(parent)?.startsWith(`${stat.dev}:${stat.ino}:`))
|
|
233
|
+
throw new RuntimeError('permission');
|
|
234
|
+
this.windowsStats.set(parent, statIdentity(stat));
|
|
235
|
+
this.windowsAudit.delete(absolute);
|
|
236
|
+
this.windowsStats.delete(absolute);
|
|
237
|
+
}
|
|
238
|
+
async bytes(path) {
|
|
239
|
+
await this.inspect(path);
|
|
240
|
+
const fd = await open(path, constants.O_RDONLY | (process.platform === 'win32' ? 0 : constants.O_NOFOLLOW));
|
|
241
|
+
try {
|
|
242
|
+
return await fd.readFile();
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
await fd.close();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** An owned inheritable ACL update can change every cached descendant's ctime. */
|
|
249
|
+
async recordAclWrite(path) {
|
|
250
|
+
const root = this.guardedPath(path);
|
|
251
|
+
for (const known of this.windowsStats.keys())
|
|
252
|
+
if (known === root || known.startsWith(root + sep))
|
|
253
|
+
this.windowsStats.set(known, statIdentity(await lstat(known)));
|
|
254
|
+
this.invalidateAudit(root);
|
|
255
|
+
}
|
|
256
|
+
async pointer(artifact) {
|
|
257
|
+
try {
|
|
258
|
+
const p = JSON.parse((await this.bytes(this.pointerPath(artifact))).toString());
|
|
259
|
+
for (const ref of [p.current, ...(p.previous ? [p.previous] : [])]) {
|
|
260
|
+
versionName(ref.version);
|
|
261
|
+
if (!/^[a-f0-9]{64}$/.test(ref.manifestSha256))
|
|
262
|
+
throw new RuntimeError('digest_mismatch');
|
|
263
|
+
}
|
|
264
|
+
return p;
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
if (missing(e))
|
|
268
|
+
return undefined;
|
|
269
|
+
throw e instanceof RuntimeError ? e : new RuntimeError('digest_mismatch');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async operation(work) {
|
|
273
|
+
const operation = this.operations.then(async () => {
|
|
274
|
+
// Reuse this process's audit across inspect/install/verify; stat drift invalidates it.
|
|
275
|
+
this.activeOperation = true;
|
|
276
|
+
try {
|
|
277
|
+
return await work();
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
this.activeOperation = false;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
this.operations = operation.catch(() => undefined);
|
|
284
|
+
return operation;
|
|
285
|
+
}
|
|
286
|
+
verifyAt(artifact, ref, directory) {
|
|
287
|
+
return this.operation(() => this.verifyOpen(artifact, ref, directory));
|
|
288
|
+
}
|
|
289
|
+
async verifyOpen(artifact, ref, directory) {
|
|
290
|
+
let bytes;
|
|
291
|
+
try {
|
|
292
|
+
bytes = await this.bytes(join(directory, 'manifest.json'));
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
if (missing(e))
|
|
296
|
+
throw new RuntimeError('manifest_missing');
|
|
297
|
+
throw e;
|
|
298
|
+
}
|
|
299
|
+
if (hash(bytes) !== ref.manifestSha256)
|
|
300
|
+
throw new RuntimeError('digest_mismatch');
|
|
301
|
+
let m;
|
|
302
|
+
try {
|
|
303
|
+
m = JSON.parse(bytes.toString());
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
throw new RuntimeError('digest_mismatch');
|
|
307
|
+
}
|
|
308
|
+
if (!m ||
|
|
309
|
+
m.schemaVersion !== 1 ||
|
|
310
|
+
!m.source ||
|
|
311
|
+
m.artifact !== artifact ||
|
|
312
|
+
m.version !== ref.version ||
|
|
313
|
+
!m.files ||
|
|
314
|
+
!m.files[safePath(m.entry)])
|
|
315
|
+
throw new RuntimeError('digest_mismatch');
|
|
316
|
+
if (artifact === 'scanner' &&
|
|
317
|
+
(m.source.kind !== 'release' ||
|
|
318
|
+
typeof m.source.url !== 'string' ||
|
|
319
|
+
!m.source.url.startsWith('https://') ||
|
|
320
|
+
((!m.signer || m.signer.platform !== process.platform) &&
|
|
321
|
+
!(this.options.allowUnsigned && !m.signer && m.signingStatus === 'unsigned'))))
|
|
322
|
+
throw new RuntimeError('unsigned');
|
|
323
|
+
if (artifact === 'cli' &&
|
|
324
|
+
(m.source.kind !== 'npm' ||
|
|
325
|
+
!Array.isArray(m.source.packages) ||
|
|
326
|
+
!m.source.packages.some((p) => p.name === '@mnemonik/cli' && p.version === m.version)))
|
|
327
|
+
throw new RuntimeError('unsigned');
|
|
328
|
+
if (process.platform === 'win32')
|
|
329
|
+
await this.audit(Object.keys(m.files).map((name) => join(directory, safePath(name))));
|
|
330
|
+
const actual = [];
|
|
331
|
+
const walk = async (dir, prefix = '') => {
|
|
332
|
+
await this.inspect(dir, true);
|
|
333
|
+
for (const name of await readdir(dir)) {
|
|
334
|
+
const key = prefix + name;
|
|
335
|
+
if (!prefix && name === 'manifest.json')
|
|
336
|
+
continue;
|
|
337
|
+
const path = join(dir, name);
|
|
338
|
+
if ((await lstat(path)).isDirectory())
|
|
339
|
+
await walk(path, key + '/');
|
|
340
|
+
else {
|
|
341
|
+
await this.inspect(path);
|
|
342
|
+
actual.push(key);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
await walk(directory);
|
|
347
|
+
if (actual.sort().join('\n') !== Object.keys(m.files).sort().join('\n'))
|
|
348
|
+
throw new RuntimeError('digest_mismatch');
|
|
349
|
+
let total = 0;
|
|
350
|
+
for (const [name, expected] of Object.entries(m.files)) {
|
|
351
|
+
if (!expected ||
|
|
352
|
+
!/^[a-f0-9]{64}$/.test(expected.sha256) ||
|
|
353
|
+
!Number.isSafeInteger(expected.size) ||
|
|
354
|
+
expected.size < 0 ||
|
|
355
|
+
typeof expected.executable !== 'boolean')
|
|
356
|
+
throw new RuntimeError('digest_mismatch');
|
|
357
|
+
const path = join(directory, safePath(name));
|
|
358
|
+
const contents = await this.bytes(path);
|
|
359
|
+
if (hash(contents) !== expected.sha256 || contents.length !== expected.size)
|
|
360
|
+
throw new RuntimeError('digest_mismatch');
|
|
361
|
+
if (process.platform !== 'win32' &&
|
|
362
|
+
((await lstat(path)).mode & 0o777) !== (expected.executable ? 0o700 : 0o600))
|
|
363
|
+
throw new RuntimeError('permission');
|
|
364
|
+
total += contents.length;
|
|
365
|
+
}
|
|
366
|
+
if (total !== m.totalSize)
|
|
367
|
+
throw new RuntimeError('digest_mismatch');
|
|
368
|
+
if (m.signer) {
|
|
369
|
+
const signer = m.signer.platform === 'linux'
|
|
370
|
+
? { ...m.signer, signature: join(directory, safePath(m.signer.signature)) }
|
|
371
|
+
: m.signer;
|
|
372
|
+
if (m.signer.platform === 'linux' && !m.files[m.signer.signature])
|
|
373
|
+
throw new RuntimeError('unsigned');
|
|
374
|
+
try {
|
|
375
|
+
const entry = join(directory, m.entry);
|
|
376
|
+
if (signer.platform === 'win32') {
|
|
377
|
+
const key = JSON.stringify([entry, m.files[m.entry]?.sha256, signer.identity]);
|
|
378
|
+
let verification = RuntimeReader.windowsSignatures.get(key);
|
|
379
|
+
if (!verification) {
|
|
380
|
+
verification = verifySigner(entry, signer, this.run).catch((error) => {
|
|
381
|
+
RuntimeReader.windowsSignatures.delete(key);
|
|
382
|
+
throw error;
|
|
383
|
+
});
|
|
384
|
+
RuntimeReader.windowsSignatures.set(key, verification);
|
|
385
|
+
}
|
|
386
|
+
await verification;
|
|
387
|
+
}
|
|
388
|
+
else
|
|
389
|
+
await verifySigner(entry, signer, this.run);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
throw new RuntimeError('unsigned');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { directory, manifest: m, entry: join(directory, m.entry), reference: ref };
|
|
396
|
+
}
|
|
397
|
+
async verifyRuntime(artifact) {
|
|
398
|
+
return this.operation(async () => {
|
|
399
|
+
const p = await this.pointer(artifact);
|
|
400
|
+
if (!p)
|
|
401
|
+
throw new RuntimeError('manifest_missing');
|
|
402
|
+
// Reading the pointer is not permission to import its target. Only return a verified path.
|
|
403
|
+
return this.verifyOpen(artifact, p.current, join(dirname(this.pointerPath(artifact)), p.current.version));
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
export * from './runtimeSigners.js';
|
|
408
|
+
//# sourceMappingURL=runtimeReader.js.map
|