@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.
Files changed (177) hide show
  1. package/LICENSE +38 -0
  2. package/dist/auth/credentials.d.ts +38 -0
  3. package/dist/auth/credentials.d.ts.map +1 -0
  4. package/dist/auth/credentials.js +7464 -0
  5. package/dist/auth/credentials.js.map +1 -0
  6. package/dist/auth/device.d.ts +23 -0
  7. package/dist/auth/device.d.ts.map +1 -0
  8. package/dist/auth/device.js +108 -0
  9. package/dist/auth/device.js.map +1 -0
  10. package/dist/auth/index.d.ts +34 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +5048 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/installSession.d.ts +22 -0
  15. package/dist/auth/installSession.d.ts.map +1 -0
  16. package/dist/auth/installSession.js +4717 -0
  17. package/dist/auth/installSession.js.map +1 -0
  18. package/dist/auth/pkce.d.ts +35 -0
  19. package/dist/auth/pkce.d.ts.map +1 -0
  20. package/dist/auth/pkce.js +182 -0
  21. package/dist/auth/pkce.js.map +1 -0
  22. package/dist/auth/status.d.ts +30 -0
  23. package/dist/auth/status.d.ts.map +1 -0
  24. package/dist/auth/status.js +4799 -0
  25. package/dist/auth/status.js.map +1 -0
  26. package/dist/bin.d.ts +3 -0
  27. package/dist/bin.d.ts.map +1 -0
  28. package/dist/bin.js +28 -0
  29. package/dist/bin.js.map +1 -0
  30. package/dist/diagnostics.d.ts +42 -0
  31. package/dist/diagnostics.d.ts.map +1 -0
  32. package/dist/diagnostics.js +7405 -0
  33. package/dist/diagnostics.js.map +1 -0
  34. package/dist/digests.json +175 -0
  35. package/dist/identity/migrate.d.ts +62 -0
  36. package/dist/identity/migrate.d.ts.map +1 -0
  37. package/dist/identity/migrate.js +5416 -0
  38. package/dist/identity/migrate.js.map +1 -0
  39. package/dist/index.d.ts +9 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +11 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/install/adapters.d.ts +65 -0
  44. package/dist/install/adapters.d.ts.map +1 -0
  45. package/dist/install/adapters.js +91 -0
  46. package/dist/install/adapters.js.map +1 -0
  47. package/dist/install/hosts.d.ts +83 -0
  48. package/dist/install/hosts.d.ts.map +1 -0
  49. package/dist/install/hosts.js +5733 -0
  50. package/dist/install/hosts.js.map +1 -0
  51. package/dist/install/journal.d.ts +113 -0
  52. package/dist/install/journal.d.ts.map +1 -0
  53. package/dist/install/journal.js +6836 -0
  54. package/dist/install/journal.js.map +1 -0
  55. package/dist/install/journey.d.ts +11 -0
  56. package/dist/install/journey.d.ts.map +1 -0
  57. package/dist/install/journey.js +5594 -0
  58. package/dist/install/journey.js.map +1 -0
  59. package/dist/install/ownership.d.ts +55 -0
  60. package/dist/install/ownership.d.ts.map +1 -0
  61. package/dist/install/ownership.js +4942 -0
  62. package/dist/install/ownership.js.map +1 -0
  63. package/dist/install/transaction.d.ts +71 -0
  64. package/dist/install/transaction.d.ts.map +1 -0
  65. package/dist/install/transaction.js +5254 -0
  66. package/dist/install/transaction.js.map +1 -0
  67. package/dist/install/ui.d.ts +14 -0
  68. package/dist/install/ui.d.ts.map +1 -0
  69. package/dist/install/ui.js +4925 -0
  70. package/dist/install/ui.js.map +1 -0
  71. package/dist/installSession.d.ts +33 -0
  72. package/dist/installSession.d.ts.map +1 -0
  73. package/dist/installSession.js +4893 -0
  74. package/dist/installSession.js.map +1 -0
  75. package/dist/installation.d.ts +11 -0
  76. package/dist/installation.d.ts.map +1 -0
  77. package/dist/installation.js +6618 -0
  78. package/dist/installation.js.map +1 -0
  79. package/dist/launcher.d.ts +27 -0
  80. package/dist/launcher.d.ts.map +1 -0
  81. package/dist/launcher.js +6862 -0
  82. package/dist/launcher.js.map +1 -0
  83. package/dist/output.d.ts +22 -0
  84. package/dist/output.d.ts.map +1 -0
  85. package/dist/output.js +68 -0
  86. package/dist/output.js.map +1 -0
  87. package/dist/preflight.d.ts +43 -0
  88. package/dist/preflight.d.ts.map +1 -0
  89. package/dist/preflight.js +5265 -0
  90. package/dist/preflight.js.map +1 -0
  91. package/dist/project/eligibility.d.ts +19 -0
  92. package/dist/project/eligibility.d.ts.map +1 -0
  93. package/dist/project/eligibility.js +4835 -0
  94. package/dist/project/eligibility.js.map +1 -0
  95. package/dist/project/records.d.ts +14 -0
  96. package/dist/project/records.d.ts.map +1 -0
  97. package/dist/project/records.js +6598 -0
  98. package/dist/project/records.js.map +1 -0
  99. package/dist/project.d.ts +127 -0
  100. package/dist/project.d.ts.map +1 -0
  101. package/dist/project.js +7857 -0
  102. package/dist/project.js.map +1 -0
  103. package/dist/router.d.ts +63 -0
  104. package/dist/router.d.ts.map +1 -0
  105. package/dist/router.js +6168 -0
  106. package/dist/router.js.map +1 -0
  107. package/dist/runtime/bootstrap.d.ts +31 -0
  108. package/dist/runtime/bootstrap.d.ts.map +1 -0
  109. package/dist/runtime/bootstrap.js +386 -0
  110. package/dist/runtime/bootstrap.js.map +1 -0
  111. package/dist/runtime/releaseSource.d.ts +26 -0
  112. package/dist/runtime/releaseSource.d.ts.map +1 -0
  113. package/dist/runtime/releaseSource.js +4992 -0
  114. package/dist/runtime/releaseSource.js.map +1 -0
  115. package/dist/runtime/selfUpdate.d.ts +12 -0
  116. package/dist/runtime/selfUpdate.d.ts.map +1 -0
  117. package/dist/runtime/selfUpdate.js +4943 -0
  118. package/dist/runtime/selfUpdate.js.map +1 -0
  119. package/dist/runtime/signers.d.ts +2 -0
  120. package/dist/runtime/signers.d.ts.map +1 -0
  121. package/dist/runtime/signers.js +1697 -0
  122. package/dist/runtime/signers.js.map +1 -0
  123. package/dist/runtime/store.d.ts +30 -0
  124. package/dist/runtime/store.d.ts.map +1 -0
  125. package/dist/runtime/store.js +977 -0
  126. package/dist/runtime/store.js.map +1 -0
  127. package/dist/sbom.json +308 -0
  128. package/dist/scanner/control.d.ts +38 -0
  129. package/dist/scanner/control.d.ts.map +1 -0
  130. package/dist/scanner/control.js +6568 -0
  131. package/dist/scanner/control.js.map +1 -0
  132. package/dist/scanner/data.d.ts +9 -0
  133. package/dist/scanner/data.d.ts.map +1 -0
  134. package/dist/scanner/data.js +4715 -0
  135. package/dist/scanner/data.js.map +1 -0
  136. package/dist/scanner/discover.d.ts +35 -0
  137. package/dist/scanner/discover.d.ts.map +1 -0
  138. package/dist/scanner/discover.js +5266 -0
  139. package/dist/scanner/discover.js.map +1 -0
  140. package/dist/scanner/enable.d.ts +69 -0
  141. package/dist/scanner/enable.d.ts.map +1 -0
  142. package/dist/scanner/enable.js +6923 -0
  143. package/dist/scanner/enable.js.map +1 -0
  144. package/dist/scanner/picker.d.ts +58 -0
  145. package/dist/scanner/picker.d.ts.map +1 -0
  146. package/dist/scanner/picker.js +4993 -0
  147. package/dist/scanner/picker.js.map +1 -0
  148. package/dist/scanner/service.d.ts +49 -0
  149. package/dist/scanner/service.d.ts.map +1 -0
  150. package/dist/scanner/service.js +4888 -0
  151. package/dist/scanner/service.js.map +1 -0
  152. package/dist/scanner/update.d.ts +4 -0
  153. package/dist/scanner/update.d.ts.map +1 -0
  154. package/dist/scanner/update.js +6569 -0
  155. package/dist/scanner/update.js.map +1 -0
  156. package/dist/scanner-release.json +129 -0
  157. package/dist/screens/journey.d.ts +23 -0
  158. package/dist/screens/journey.d.ts.map +1 -0
  159. package/dist/screens/journey.js +110 -0
  160. package/dist/screens/journey.js.map +1 -0
  161. package/dist/screens.d.ts +18 -0
  162. package/dist/screens.d.ts.map +1 -0
  163. package/dist/screens.js +79 -0
  164. package/dist/screens.js.map +1 -0
  165. package/dist/status.d.ts +64 -0
  166. package/dist/status.d.ts.map +1 -0
  167. package/dist/status.js +5281 -0
  168. package/dist/status.js.map +1 -0
  169. package/dist/transport/server.d.ts +79 -0
  170. package/dist/transport/server.d.ts.map +1 -0
  171. package/dist/transport/server.js +136 -0
  172. package/dist/transport/server.js.map +1 -0
  173. package/dist/vendor/local-setup.js +6971 -0
  174. package/dist/vendor/shared/runtimeReader.js +408 -0
  175. package/dist/vendor/shared/runtimeSigners.js +512 -0
  176. package/package.json +258 -7
  177. package/README.md +0 -3
@@ -0,0 +1,512 @@
1
+ import { lstatSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve, sep, win32 } from 'node:path';
4
+ import { createHash, createPublicKey, randomUUID, timingSafeEqual, verify as verifySignature, } from 'node:crypto';
5
+ import { execFile, execFileSync } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+ export const execute = (file, args, input) => {
8
+ const result = promisify(execFile)(file, args);
9
+ if (input === undefined)
10
+ return result;
11
+ return new Promise((resolve, reject) => {
12
+ void result.then(resolve, reject);
13
+ result.child.stdin?.on('error', reject);
14
+ result.child.stdin?.end(input);
15
+ });
16
+ };
17
+ const quote = (text) => `'${text.replaceAll("'", "''")}'`;
18
+ function decodeBase64(text, length) {
19
+ const bytes = Buffer.from(text, 'base64');
20
+ if (bytes.length !== length || bytes.toString('base64') !== text)
21
+ throw new Error('unsigned');
22
+ return bytes;
23
+ }
24
+ async function verifyMinisign(path, signaturePath, identity) {
25
+ const [message, signatureText] = await Promise.all([
26
+ readFile(path),
27
+ readFile(signaturePath, 'utf8'),
28
+ ]);
29
+ const publicPacket = decodeBase64(identity, 42);
30
+ const lines = signatureText.trimEnd().split(/\r?\n/);
31
+ const [untrustedComment, encodedSignature, trustedLine, encodedGlobalSignature] = lines;
32
+ if (publicPacket.subarray(0, 2).toString() !== 'Ed' ||
33
+ lines.length !== 4 ||
34
+ !untrustedComment?.startsWith('untrusted comment:') ||
35
+ !encodedSignature ||
36
+ !trustedLine?.startsWith('trusted comment: ') ||
37
+ !encodedGlobalSignature)
38
+ throw new Error('unsigned');
39
+ const signaturePacket = decodeBase64(encodedSignature, 74);
40
+ const globalSignature = decodeBase64(encodedGlobalSignature, 64);
41
+ const algorithm = signaturePacket.subarray(0, 2).toString();
42
+ if ((algorithm !== 'ED' && algorithm !== 'Ed') ||
43
+ !timingSafeEqual(publicPacket.subarray(2, 10), signaturePacket.subarray(2, 10)))
44
+ throw new Error('unsigned');
45
+ const publicKey = createPublicKey({
46
+ key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), publicPacket.subarray(10)]),
47
+ format: 'der',
48
+ type: 'spki',
49
+ });
50
+ const fileSignature = signaturePacket.subarray(10);
51
+ const signedMessage = algorithm === 'ED' ? createHash('blake2b512').update(message).digest() : message;
52
+ const trustedComment = trustedLine.slice('trusted comment: '.length);
53
+ if (!verifySignature(null, signedMessage, publicKey, fileSignature) ||
54
+ !verifySignature(null, Buffer.concat([fileSignature, Buffer.from(trustedComment)]), publicKey, globalSignature))
55
+ throw new Error('unsigned');
56
+ }
57
+ /** Release tooling supplies the real identity and artifacts. These checks do not sign anything. */
58
+ export async function verifySigner(path, signer, run = execute) {
59
+ if (signer.platform === 'darwin' && /^[A-Z0-9]{10}$/.test(signer.identity)) {
60
+ // codesign is the whole darwin check. spctl --assess --type execute only
61
+ // assesses app bundles and rejects every bare Mach-O executable, Apple's
62
+ // own shipped ones included, so running it would refuse every signed
63
+ // scanner. -R reads its argument as a requirement file path unless it
64
+ // starts with "=", which makes the rest of that argument the requirement
65
+ // text itself.
66
+ await run('/usr/bin/codesign', [
67
+ '--verify',
68
+ '--strict',
69
+ '-R',
70
+ `=anchor apple generic and certificate leaf[subject.OU] = "${signer.identity}"`,
71
+ path,
72
+ ]);
73
+ }
74
+ else if (signer.platform === 'win32' && /^[A-Fa-f0-9]{40}$/.test(signer.identity)) {
75
+ await run('powershell.exe', [
76
+ '-NoProfile',
77
+ '-NonInteractive',
78
+ '-Command',
79
+ `$ErrorActionPreference = 'Stop'; $s = Get-AuthenticodeSignature -LiteralPath ${quote(path)}; if ($s.Status -ne 'Valid' -or $s.SignerCertificate.Thumbprint -ne ${quote(signer.identity)}) { exit 1 }`,
80
+ ]);
81
+ }
82
+ else if (signer.platform === 'linux') {
83
+ try {
84
+ await verifyMinisign(path, signer.signature, signer.identity);
85
+ }
86
+ catch {
87
+ throw new Error('unsigned');
88
+ }
89
+ }
90
+ else
91
+ throw new Error('unsigned');
92
+ }
93
+ /** Windows stat mode/uid are not ACL evidence. */
94
+ export async function verifyWindowsPermission(path, run = hookExecute, state) {
95
+ const verdicts = await auditWindowsPermissions(path, [path], run, false, state);
96
+ if (verdicts.get(path) === 'acl_unavailable')
97
+ throw new Error('acl_unavailable');
98
+ if (verdicts.get(path) !== 'ok')
99
+ throw new Error('acl_permissions');
100
+ }
101
+ const identityArgs = ['/user', '/fo', 'csv', '/nh'];
102
+ function windowsCommand(file) {
103
+ const root = process.env.SystemRoot;
104
+ if (!root || !win32.isAbsolute(root))
105
+ throw new Error('acl_system_root_unavailable');
106
+ return win32.join(root, 'System32', file);
107
+ }
108
+ let currentIdentity;
109
+ const receipts = new Map();
110
+ const ttl = 10 * 60_000;
111
+ const fresh = (time) => time !== undefined && Date.now() >= time && Date.now() - time < ttl;
112
+ function readIdentity(stdout) {
113
+ const match = /^"([^"\r\n]+)","(S-1-(?:\d+-)+\d+)"$/.exec(stdout.trim());
114
+ if (!match?.[1] || !match[2] || stdout.includes('\uFFFD'))
115
+ throw new Error('acl_identity_unavailable');
116
+ return { name: match[1], sid: match[2] };
117
+ }
118
+ export function windowsCurrentAccountSync() {
119
+ return (currentIdentity ??= readIdentity(hookExecuteSync(windowsCommand('whoami.exe'), identityArgs)));
120
+ }
121
+ let identityPending;
122
+ async function windowsIdentity(run) {
123
+ if (currentIdentity)
124
+ return currentIdentity;
125
+ identityPending ??= run(windowsCommand('whoami.exe'), identityArgs)
126
+ .then((result) => (currentIdentity = readIdentity(stdout(result))))
127
+ .finally(() => {
128
+ identityPending = undefined;
129
+ });
130
+ return identityPending;
131
+ }
132
+ /** Frequent config polling checks fresh ACEs; stat changes trigger a full owner audit. */
133
+ export async function verifyWindowsAcl(path, run = hookExecute, state) {
134
+ const user = await windowsIdentity(run);
135
+ if (!privateEntries((await drive(saveAcl(path, false, [path], state), run)).get(path), user, false))
136
+ throw new Error('acl_permissions');
137
+ }
138
+ const aclArgs = (path, sid) => [
139
+ path,
140
+ '/inheritance:r',
141
+ '/grant:r',
142
+ `*${sid}:(OI)(CI)F`,
143
+ ];
144
+ // Keep injected runners at the logical-command boundary; native execution owns encoding.
145
+ function hookInvocation(file, args) {
146
+ if (win32.basename(file) === 'whoami.exe') {
147
+ if ([file, ...args].some((arg) => /[%!"\r\n]/.test(arg)))
148
+ throw new Error('acl_path_unavailable');
149
+ const command = args
150
+ .map((arg) => (/^(?:[a-z0-9]+|\/[a-z]+)$/i.test(arg) ? arg : `"${arg}"`))
151
+ .join(' ');
152
+ return [
153
+ windowsCommand('cmd.exe'),
154
+ ['/d', '/v:off', '/s', '/c', `"chcp 65001>nul & "${file}" ${command}"`],
155
+ ];
156
+ }
157
+ if (win32.basename(file) === 'cmd.exe')
158
+ return [file, ['/u', ...args]];
159
+ return [file, args];
160
+ }
161
+ const hookOptions = (file, args) => ({
162
+ encoding: args.includes('/u') ? 'utf16le' : 'utf8',
163
+ timeout: win32.basename(file) === 'powershell.exe' ? 30_000 : 5_000,
164
+ windowsHide: true,
165
+ maxBuffer: 16 * 1024 * 1024,
166
+ windowsVerbatimArguments: win32.basename(file) === 'cmd.exe',
167
+ });
168
+ function hookExecuteSync(file, args, input) {
169
+ [file, args] = hookInvocation(file, args);
170
+ return execFileSync(file, args, { ...hookOptions(file, args), input });
171
+ }
172
+ const hookExecute = (file, args, input) => {
173
+ const treeListing = (win32.basename(file) === 'icacls.exe' && args.includes('/t')) ||
174
+ (win32.basename(file) === 'cmd.exe' && args.some((arg) => arg.startsWith('dir /q /a /s ')));
175
+ [file, args] = hookInvocation(file, args);
176
+ const options = hookOptions(file, args);
177
+ if (treeListing)
178
+ options.timeout = 30_000;
179
+ return new Promise((resolve, reject) => {
180
+ const child = execFile(file, args, options, (error, stdout) => error ? reject(error) : resolve({ stdout }));
181
+ if (input !== undefined) {
182
+ child.stdin?.on('error', reject);
183
+ child.stdin?.end(input);
184
+ }
185
+ });
186
+ };
187
+ const stdout = (value) => value.stdout;
188
+ /** icacls /save writes alternating relative paths and SDDL in UTF-16LE. */
189
+ export function aclRecords(output, root, paths, onLookup) {
190
+ const records = new Map();
191
+ const wanted = new Set(paths);
192
+ const lines = output
193
+ .replace(/^\uFEFF/, '')
194
+ .trimEnd()
195
+ .split(/\r?\n/);
196
+ if (lines.length % 2)
197
+ throw new Error('acl_unavailable');
198
+ for (let index = 0; index < lines.length; index += 2) {
199
+ onLookup?.();
200
+ const relativePath = lines[index];
201
+ const descriptor = lines[index + 1];
202
+ if (relativePath === undefined || descriptor === undefined)
203
+ throw new Error('acl_unavailable');
204
+ const path = resolve(dirname(root), relativePath.replaceAll('\\', sep));
205
+ if (!wanted.has(path))
206
+ continue;
207
+ if (records.has(path))
208
+ throw new Error('acl_unavailable');
209
+ const dacl = /^D:(?:P|AI|AR)*((?:\([^()]*\))*)$/.exec(descriptor);
210
+ records.set(path, dacl?.[1]?.match(/\([^()]*\)/g) ?? []);
211
+ }
212
+ return records;
213
+ }
214
+ function privateEntries(entries, user, directory) {
215
+ if (!entries?.length)
216
+ throw new Error('acl_unavailable');
217
+ return entries.every((line) => {
218
+ const ace = /^\(([AD]);((?:OI|CI|NP|IO|ID|SA|FA)*);[A-Za-z0-9]+;;;((?:S-1-(?:\d+-)+\d+)|[A-Z]{2})\)$/.exec(line);
219
+ if (!ace)
220
+ throw new Error('acl_unavailable');
221
+ return (!directory || !ace[2]?.includes('ID')) && (ace[1] === 'D' || ace[3] === user.sid);
222
+ });
223
+ }
224
+ const auditDirectories = new Set();
225
+ function* prepareAclDirectory(state) {
226
+ const directory = join(state, 'audit-tmp');
227
+ if (auditDirectories.has(directory)) {
228
+ const stat = lstatSync(directory);
229
+ if (!stat.isDirectory() || stat.isSymbolicLink())
230
+ throw new Error('acl_permissions');
231
+ return directory;
232
+ }
233
+ let created = false;
234
+ try {
235
+ mkdirSync(directory, { mode: 0o700 });
236
+ created = true;
237
+ }
238
+ catch (error) {
239
+ if (error.code !== 'EEXIST')
240
+ throw error;
241
+ }
242
+ const stat = lstatSync(directory);
243
+ if (!stat.isDirectory() || stat.isSymbolicLink())
244
+ throw new Error('acl_permissions');
245
+ if (!auditDirectories.has(directory)) {
246
+ currentIdentity ??= readIdentity(yield [windowsCommand('whoami.exe'), identityArgs]);
247
+ yield [windowsCommand('icacls.exe'), aclArgs(directory, currentIdentity.sid)];
248
+ if (!created) {
249
+ const temp = join(directory, `.acl-${randomUUID()}.tmp`);
250
+ writeFileSync(temp, '', { flag: 'wx', mode: 0o600 });
251
+ try {
252
+ yield [windowsCommand('icacls.exe'), [directory, '/save', temp, '/l']];
253
+ if (!privateEntries(aclRecords(readFileSync(temp, 'utf16le'), directory, [directory]).get(directory), currentIdentity, true))
254
+ throw new Error('acl_permissions');
255
+ }
256
+ finally {
257
+ unlinkSync(temp);
258
+ }
259
+ }
260
+ auditDirectories.add(directory);
261
+ }
262
+ return directory;
263
+ }
264
+ const directoryPending = new Map();
265
+ export async function prepareWindowsAclDirectory(state, run = hookExecute) {
266
+ let pending = directoryPending.get(state);
267
+ if (!pending) {
268
+ pending = drive(prepareAclDirectory(state), run)
269
+ .then(() => { })
270
+ .finally(() => directoryPending.delete(state));
271
+ directoryPending.set(state, pending);
272
+ }
273
+ await pending;
274
+ }
275
+ /** Inherits the already-private parent ACL; never parses the OEM console output. */
276
+ function* saveAcl(path, recursive, paths = [path], state = lstatSync(path).isDirectory() ? path : dirname(path)) {
277
+ const directory = yield* prepareAclDirectory(state);
278
+ const temp = join(directory, `.acl-${randomUUID()}.tmp`);
279
+ writeFileSync(temp, '', { flag: 'wx', mode: 0o600 });
280
+ try {
281
+ yield [windowsCommand('icacls.exe'), [path, '/save', temp, ...(recursive ? ['/t'] : []), '/l']];
282
+ // The export may contain its own temporary file. Only requested paths are parsed.
283
+ return aclRecords(readFileSync(temp, 'utf16le'), path, paths.filter((item) => item !== directory && !item.startsWith(directory + sep)));
284
+ }
285
+ finally {
286
+ unlinkSync(temp);
287
+ }
288
+ }
289
+ async function drive(commands, run) {
290
+ try {
291
+ let step = commands.next();
292
+ while (!step.done)
293
+ step = commands.next(stdout(await run(...step.value)));
294
+ return step.value;
295
+ }
296
+ finally {
297
+ commands.return(undefined);
298
+ }
299
+ }
300
+ // Native dir /q: metadata width varies with the clock format; owner is 23 characters.
301
+ // A full owner column is silently truncated: no ellipsis or separating space.
302
+ const ownerWidth = 23;
303
+ function dirEntry(line) {
304
+ // Size grouping is culture-dependent (fr-FR uses U+202F).
305
+ const metadata = /^\S+\s+\S+(?:\s+\S+)?\s+(?:<DIR>|\d[\d,.\s]*)\s+/.exec(line)?.[0];
306
+ if (!metadata)
307
+ return undefined;
308
+ const ownerColumn = metadata.length;
309
+ const owner = line
310
+ .slice(ownerColumn, ownerColumn + ownerWidth)
311
+ .trim()
312
+ .toLowerCase();
313
+ const name = line.slice(ownerColumn + ownerWidth);
314
+ return owner && name ? { owner, name } : undefined;
315
+ }
316
+ const unknownOwner = (owner, user) => user.name.length >= ownerWidth || owner.length >= ownerWidth || owner.includes('...');
317
+ function ownerLookup(paths) {
318
+ if (paths.some((path) => /[\t\r\n]/.test(path)))
319
+ throw new Error('acl_path_unavailable');
320
+ const script = `$ErrorActionPreference = 'Stop'
321
+ [Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
322
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
323
+ while ($null -ne ($path = [Console]::ReadLine())) {
324
+ $owner = (Get-Acl -LiteralPath $path).GetOwner([System.Security.Principal.SecurityIdentifier]).Value
325
+ [Console]::WriteLine($path + "\`t" + $owner)
326
+ }`;
327
+ return [
328
+ windowsCommand('WindowsPowerShell\\v1.0\\powershell.exe'),
329
+ ['-NoProfile', '-NonInteractive', '-Command', script],
330
+ paths.join('\n') + '\n',
331
+ ];
332
+ }
333
+ function ownerSids(output) {
334
+ const owners = new Map();
335
+ for (const line of output.split(/\r?\n/)) {
336
+ const [path, sid] = line.split('\t');
337
+ if (path && sid && /^S-1-(?:\d+-)+\d+$/.test(sid))
338
+ owners.set(path, sid.toLowerCase());
339
+ }
340
+ return owners;
341
+ }
342
+ /** One command sequence for both async hooks and synchronous cache writers.
343
+ * The session receipt deliberately trusts previously audited paths for ten minutes. */
344
+ function* hookPermission(path, created, session, directory = true) {
345
+ const stat = lstatSync(path);
346
+ if (stat.isSymbolicLink() || (directory ? !stat.isDirectory() : !stat.isFile()))
347
+ throw new Error('cache unavailable');
348
+ const marker = join(session, '.windows-acl.json');
349
+ let receipt = receipts.get(session);
350
+ if (!receipt) {
351
+ try {
352
+ if (lstatSync(session).isSymbolicLink() ||
353
+ !lstatSync(marker).isFile() ||
354
+ lstatSync(marker).isSymbolicLink())
355
+ throw new Error('cache unavailable');
356
+ receipt = JSON.parse(readFileSync(marker, 'utf8'));
357
+ if (!receipt ||
358
+ !receipt.paths ||
359
+ !receipt.identity ||
360
+ !/^S-1-(?:\d+-)+\d+$/.test(receipt.identity.sid) ||
361
+ typeof receipt.identity.name !== 'string')
362
+ receipt = undefined;
363
+ }
364
+ catch {
365
+ /* No usable session receipt. */
366
+ }
367
+ }
368
+ if (receipt && currentIdentity && receipt.identity.sid !== currentIdentity.sid)
369
+ receipt = undefined;
370
+ if (!created && receipt && Object.hasOwn(receipt.paths, path) && fresh(receipt.paths[path])) {
371
+ currentIdentity ??= receipt.identity;
372
+ receipts.set(session, receipt);
373
+ return;
374
+ }
375
+ currentIdentity ??= readIdentity(yield [windowsCommand('whoami.exe'), identityArgs]);
376
+ const user = currentIdentity;
377
+ if (receipt && receipt.identity.sid !== user.sid)
378
+ receipt = undefined;
379
+ // cmd is needed only for its built-in dir. Reject expansion/quote characters;
380
+ // the quoted parent remains one literal argument, including spaces and &.
381
+ const parent = dirname(path);
382
+ if (/[%!"\r\n]/.test(parent))
383
+ throw new Error('acl_path_unavailable');
384
+ const listing = yield [
385
+ windowsCommand('cmd.exe'),
386
+ ['/d', '/v:off', '/s', '/c', `dir /q ${directory ? '/a:d' : '/a'} "${parent}"`],
387
+ ];
388
+ const owners = listing.split(/\r?\n/).flatMap((line) => {
389
+ const entry = dirEntry(line);
390
+ return entry?.name.toLowerCase() === basename(path).toLowerCase() ? [entry.owner] : [];
391
+ });
392
+ let owner = owners[0];
393
+ if (owners.length === 1 && owner && unknownOwner(owner, user))
394
+ owner = ownerSids(yield ownerLookup([path])).get(path);
395
+ if (owners.length !== 1 ||
396
+ ![user.name.toLowerCase(), user.sid.toLowerCase()].includes(owner ?? ''))
397
+ throw new Error('acl_owner');
398
+ const icacls = windowsCommand('icacls.exe');
399
+ if (created)
400
+ yield [icacls, aclArgs(path, user.sid)];
401
+ let valid = privateEntries((yield* saveAcl(path, false)).get(path), user, directory);
402
+ if (!valid && !created && directory) {
403
+ yield [icacls, aclArgs(path, user.sid)];
404
+ valid = privateEntries((yield* saveAcl(path, false)).get(path), user, directory);
405
+ }
406
+ if (!valid)
407
+ throw new Error('acl_permissions');
408
+ receipt ??= { identity: user, paths: {} };
409
+ receipt.paths[path] = Date.now();
410
+ receipts.set(session, receipt);
411
+ // Root is audited before the session exists; its receipt is flushed by the
412
+ // subsequent session-directory audit. Atomic rename avoids partial JSON.
413
+ try {
414
+ const temp = `${marker}.${randomUUID()}`;
415
+ writeFileSync(temp, JSON.stringify(receipt), { flag: 'wx', mode: 0o600 });
416
+ renameSync(temp, marker);
417
+ }
418
+ catch {
419
+ /* A missing receipt costs another audit next process. */
420
+ }
421
+ }
422
+ export async function windowsCurrentUserDirectoryAcl(path, run = hookExecute) {
423
+ currentIdentity ??= readIdentity(stdout(await run(windowsCommand('whoami.exe'), identityArgs)));
424
+ await run(windowsCommand('icacls.exe'), aclArgs(path, currentIdentity.sid));
425
+ }
426
+ export async function protectWindowsDirectory(path, created, run = hookExecute, session = path) {
427
+ await drive(hookPermission(path, created, session), run);
428
+ }
429
+ function protectSync(path, created, session, directory = true) {
430
+ const audit = hookPermission(path, created, session, directory);
431
+ try {
432
+ let step = audit.next();
433
+ while (!step.done)
434
+ step = audit.next(hookExecuteSync(...step.value));
435
+ }
436
+ finally {
437
+ audit.return();
438
+ }
439
+ }
440
+ /** File ACEs may inherit from their already-private parent. */
441
+ export function verifyWindowsPermissionSync(path) {
442
+ protectSync(path, false, dirname(path), false);
443
+ }
444
+ export function ensureWindowsPrivateDirectorySync(path, session = path) {
445
+ if (process.platform !== 'win32')
446
+ return;
447
+ const created = mkdirSync(path, { recursive: true, mode: 0o700 });
448
+ protectSync(path, created !== undefined, session);
449
+ }
450
+ /** Recursive native listings are attributed by absolute path; missing entries fall back. */
451
+ export async function auditWindowsPermissions(state, paths, run = hookExecute, recursive = true, tempState = lstatSync(state).isDirectory() ? state : dirname(state)) {
452
+ const user = await windowsIdentity(run);
453
+ const root = resolve(state);
454
+ for (const path of paths)
455
+ if (resolve(path) !== root && !resolve(path).startsWith(root + sep))
456
+ throw new Error('acl_path_unavailable');
457
+ if (/[%!"\r\n]/.test(root))
458
+ throw new Error('acl_path_unavailable');
459
+ const owners = new Map();
460
+ // The shallow parent listing supplies the root's owner, including drive-root state paths.
461
+ for (const [directory, descend] of [
462
+ [dirname(root), false],
463
+ ...(recursive ? [[root, true]] : []),
464
+ ]) {
465
+ if (/[%!"\r\n]/.test(directory))
466
+ throw new Error('acl_path_unavailable');
467
+ const output = stdout(await run(windowsCommand('cmd.exe'), [
468
+ '/d',
469
+ '/v:off',
470
+ '/s',
471
+ '/c',
472
+ `dir /q /a ${descend ? '/s ' : ''}"${directory}"`,
473
+ ]));
474
+ let current = directory;
475
+ for (const line of output.split(/\r?\n/)) {
476
+ const entry = dirEntry(line);
477
+ if (entry) {
478
+ if (entry.name !== '..')
479
+ owners.set(join(current, entry.name), unknownOwner(entry.owner, user) ? '...' : entry.owner);
480
+ }
481
+ else {
482
+ // Directory headers are localized; their absolute path is not.
483
+ const header = /(?:^|\s)((?:[A-Za-z]:\\|\\\\|\/).*)$/.exec(line.trim());
484
+ if (header?.[1])
485
+ current = header[1];
486
+ }
487
+ }
488
+ }
489
+ const verdicts = new Map();
490
+ const ownerPaths = [...owners.keys()].filter((path) => path === root || path.startsWith(root + sep));
491
+ const truncated = new Set(ownerPaths.filter((path) => owners.get(path)?.includes('...')));
492
+ if (truncated.size) {
493
+ const result = await run(...ownerLookup([...truncated])).catch(() => ({ stdout: '' }));
494
+ for (const [path, sid] of ownerSids(stdout(result)))
495
+ if (truncated.has(path))
496
+ owners.set(path, sid);
497
+ }
498
+ for (const [path, entries] of await drive(saveAcl(root, recursive, ownerPaths, tempState), run)) {
499
+ const owner = owners.get(path) ?? '...';
500
+ const owned = owner === user.name.toLowerCase() || owner === user.sid.toLowerCase();
501
+ try {
502
+ if (!entries.length || owner.includes('...'))
503
+ throw new Error('acl_unavailable');
504
+ verdicts.set(path, !owned ? 'owner' : privateEntries(entries, user, false) ? 'ok' : 'ace');
505
+ }
506
+ catch {
507
+ verdicts.set(path, 'acl_unavailable');
508
+ }
509
+ }
510
+ return verdicts;
511
+ }
512
+ //# sourceMappingURL=runtimeSigners.js.map