mustflow 2.114.11 → 2.115.0

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.
@@ -2,7 +2,8 @@ import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
2
  import { t } from '../lib/i18n.js';
3
3
  import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
4
  import { resolveMustflowRoot } from '../lib/project-root.js';
5
- import { findScriptPackScript, listScriptPackScripts, SCRIPT_PACKS, } from '../lib/script-pack-registry.js';
5
+ import { withScriptPackChildProcessGuard } from '../lib/script-pack-execution-guard.js';
6
+ import { findScriptPackScript, listScriptPackScripts, SCRIPT_PACKS, SCRIPT_PACK_SAFETY_CONTRACT, } from '../lib/script-pack-registry.js';
6
7
  import { createScriptPackSuggestionReport, isScriptPackSuggestionPhase, } from '../../core/script-pack-suggestions.js';
7
8
  const SCRIPT_PACK_LIST_OPTIONS = [{ name: '--json', kind: 'boolean' }];
8
9
  const SCRIPT_PACK_SUGGEST_OPTIONS = [
@@ -39,6 +40,7 @@ function createCatalogReport(mustflowRoot, packs) {
39
40
  risk_level: script.riskLevel,
40
41
  cost: script.cost,
41
42
  report_schema_file: script.reportSchemaFile,
43
+ safety_contract: SCRIPT_PACK_SAFETY_CONTRACT,
42
44
  })),
43
45
  })),
44
46
  issues: [],
@@ -253,8 +255,10 @@ async function runScriptPackScript(args, reporter, lang) {
253
255
  printUsageError(reporter, t(lang, 'scriptPack.error.unknownScript', { script: scriptRef }), 'mf script-pack list', getScriptPackHelp(lang), lang);
254
256
  return 1;
255
257
  }
256
- const runner = await script.loadRunner();
257
- return runner(scriptArgs, reporter, lang);
258
+ return withScriptPackChildProcessGuard(async () => {
259
+ const runner = await script.loadRunner();
260
+ return runner(scriptArgs, reporter, lang);
261
+ });
258
262
  }
259
263
  export async function runScriptPack(args, reporter, lang = 'en') {
260
264
  const [action, ...rest] = args;
@@ -0,0 +1,528 @@
1
+ import { createRequire, syncBuiltinESMExports } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ const childProcess = require('node:child_process');
4
+ const fs = require('node:fs');
5
+ const workerThreads = require('node:worker_threads');
6
+ const MAX_RECORDED_DENIALS = 20;
7
+ const READ_ONLY_GIT_SUBCOMMANDS = new Set(['check-ignore', 'config', 'diff', 'ls-files', 'rev-parse', 'show', 'status']);
8
+ const GIT_WRITE_SUBCOMMANDS = new Set([
9
+ 'add',
10
+ 'am',
11
+ 'apply',
12
+ 'bisect',
13
+ 'branch',
14
+ 'checkout',
15
+ 'cherry-pick',
16
+ 'clean',
17
+ 'clone',
18
+ 'commit',
19
+ 'fetch',
20
+ 'gc',
21
+ 'merge',
22
+ 'mv',
23
+ 'pull',
24
+ 'push',
25
+ 'rebase',
26
+ 'remote',
27
+ 'reset',
28
+ 'restore',
29
+ 'revert',
30
+ 'rm',
31
+ 'submodule',
32
+ 'switch',
33
+ 'tag',
34
+ 'worktree',
35
+ ]);
36
+ const PACKAGE_MANAGER_COMMANDS = new Set(['bun', 'corepack', 'npm', 'npx', 'pnpm', 'pnpx', 'yarn', 'yarnpkg']);
37
+ const SHELL_COMMANDS = new Set(['bash', 'cmd', 'cmd.exe', 'fish', 'powershell', 'powershell.exe', 'pwsh', 'pwsh.exe', 'sh', 'sh.exe', 'zsh']);
38
+ const PUBLISH_COMMANDS = new Set(['curl', 'gh', 'hub', 'wget']);
39
+ const PACKAGE_MANAGER_DENIED_ARGS = new Set([
40
+ 'add',
41
+ 'audit',
42
+ 'build',
43
+ 'ci',
44
+ 'exec',
45
+ 'install',
46
+ 'i',
47
+ 'lint',
48
+ 'publish',
49
+ 'release',
50
+ 'run',
51
+ 'test',
52
+ 'upgrade',
53
+ 'update',
54
+ ]);
55
+ const SHELL_WRAPPER_ARGS = new Set(['-c', '/c', 'eval']);
56
+ const FORBIDDEN_GIT_GLOBAL_OPTIONS = new Set([
57
+ '-c',
58
+ '--config-env',
59
+ '--exec-path',
60
+ '--git-dir',
61
+ '--literal-pathspecs',
62
+ '--namespace',
63
+ '--work-tree',
64
+ ]);
65
+ const FORBIDDEN_GIT_READ_OPTIONS = new Set([
66
+ '--ext-diff',
67
+ '--external-diff',
68
+ '--textconv',
69
+ ]);
70
+ const FORBIDDEN_ENV_PREFIXES = ['GIT_', 'NPM_', 'NODE_OPTIONS'];
71
+ const FORBIDDEN_ENV_NAMES = new Set([
72
+ 'BUN_AUTH_TOKEN',
73
+ 'COREPACK_HOME',
74
+ 'GITHUB_TOKEN',
75
+ 'NODE_AUTH_TOKEN',
76
+ 'PNPM_HOME',
77
+ 'YARN_ENABLE_SCRIPTS',
78
+ ]);
79
+ const FS_WRITE_FUNCTIONS = [
80
+ 'appendFile',
81
+ 'appendFileSync',
82
+ 'chmod',
83
+ 'chmodSync',
84
+ 'chown',
85
+ 'chownSync',
86
+ 'copyFile',
87
+ 'copyFileSync',
88
+ 'cp',
89
+ 'cpSync',
90
+ 'createWriteStream',
91
+ 'lchmod',
92
+ 'lchmodSync',
93
+ 'lchown',
94
+ 'lchownSync',
95
+ 'link',
96
+ 'linkSync',
97
+ 'mkdir',
98
+ 'mkdirSync',
99
+ 'open',
100
+ 'openSync',
101
+ 'rename',
102
+ 'renameSync',
103
+ 'rm',
104
+ 'rmSync',
105
+ 'symlink',
106
+ 'symlinkSync',
107
+ 'truncate',
108
+ 'truncateSync',
109
+ 'unlink',
110
+ 'unlinkSync',
111
+ 'writeFile',
112
+ 'writeFileSync',
113
+ ];
114
+ const FS_PROMISE_WRITE_FUNCTIONS = [
115
+ 'appendFile',
116
+ 'chmod',
117
+ 'chown',
118
+ 'copyFile',
119
+ 'cp',
120
+ 'lchmod',
121
+ 'lchown',
122
+ 'link',
123
+ 'mkdir',
124
+ 'open',
125
+ 'rename',
126
+ 'rm',
127
+ 'symlink',
128
+ 'truncate',
129
+ 'unlink',
130
+ 'writeFile',
131
+ ];
132
+ const FS_WRITE_OPEN_FLAGS = /[+wax]/iu;
133
+ const FS_WRITE_OPEN_FLAG_BITS = fs.constants.O_WRONLY |
134
+ fs.constants.O_RDWR |
135
+ fs.constants.O_CREAT |
136
+ fs.constants.O_TRUNC |
137
+ fs.constants.O_APPEND;
138
+ let activeGuardContext = null;
139
+ export class ScriptPackExecutionDeniedError extends Error {
140
+ command;
141
+ args;
142
+ reason;
143
+ denial;
144
+ constructor(command, args, reason, denial = createGuardDenial(command, args, reason)) {
145
+ super(`script-pack child process denied: ${reason}: ${formatCommand(command, args)}`);
146
+ this.name = 'ScriptPackExecutionDeniedError';
147
+ this.command = command;
148
+ this.args = args;
149
+ this.reason = reason;
150
+ this.denial = denial;
151
+ }
152
+ }
153
+ function basenameCommand(command) {
154
+ const normalized = command.replace(/\\/gu, '/').split('/').pop() ?? command;
155
+ return normalized.toLowerCase();
156
+ }
157
+ function normalizeArg(value) {
158
+ return String(value).trim().toLowerCase();
159
+ }
160
+ function normalizeArgs(args) {
161
+ return Array.isArray(args) ? args.map((arg) => String(arg)) : [];
162
+ }
163
+ function formatCommand(command, args) {
164
+ return [command, ...args].join(' ');
165
+ }
166
+ function redactGuardToken(value) {
167
+ const trimmed = value.trim();
168
+ if (trimmed.length > 160) {
169
+ return `${trimmed.slice(0, 80)}...[truncated]`;
170
+ }
171
+ if (/^(?:https?:\/\/|ssh:\/\/)/iu.test(trimmed)) {
172
+ return '[redacted-url]';
173
+ }
174
+ if (/(?:token|secret|password|passwd|authorization|credential|sshcommand|proxycommand)/iu.test(trimmed)) {
175
+ const separatorIndex = trimmed.search(/[=:]/u);
176
+ if (separatorIndex > 0) {
177
+ return `${trimmed.slice(0, separatorIndex + 1)}[redacted]`;
178
+ }
179
+ return '[redacted-sensitive-arg]';
180
+ }
181
+ return trimmed;
182
+ }
183
+ function createGuardDenial(command, args, reason) {
184
+ return {
185
+ surface: basenameCommand(command),
186
+ reason,
187
+ command: redactGuardToken(command),
188
+ args: args.map(redactGuardToken),
189
+ };
190
+ }
191
+ function recordGuardDenial(denial) {
192
+ if (!activeGuardContext || activeGuardContext.denials.length >= MAX_RECORDED_DENIALS) {
193
+ return;
194
+ }
195
+ activeGuardContext.denials.push(denial);
196
+ }
197
+ function createDeniedError(command, args, reason) {
198
+ const denial = createGuardDenial(command, args, reason);
199
+ recordGuardDenial(denial);
200
+ return new ScriptPackExecutionDeniedError(command, args, reason, denial);
201
+ }
202
+ export function getCurrentScriptPackGuardDenials() {
203
+ return activeGuardContext?.denials.map((denial) => ({
204
+ surface: denial.surface,
205
+ reason: denial.reason,
206
+ command: denial.command,
207
+ args: [...denial.args],
208
+ })) ?? [];
209
+ }
210
+ function isObjectRecord(value) {
211
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
212
+ }
213
+ function normalizeEnvName(value) {
214
+ return value.trim().toUpperCase();
215
+ }
216
+ function assertChildProcessOptionsAllowed(command, args, options) {
217
+ if (!isObjectRecord(options)) {
218
+ return;
219
+ }
220
+ if (options.shell === true || typeof options.shell === 'string') {
221
+ deny(command, args, 'shell option is not allowed for script-pack child processes');
222
+ }
223
+ if (isObjectRecord(options.env)) {
224
+ for (const key of Object.keys(options.env)) {
225
+ const normalized = normalizeEnvName(key);
226
+ if (FORBIDDEN_ENV_NAMES.has(normalized) || FORBIDDEN_ENV_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
227
+ deny(command, args, `environment override ${key} is not allowed`);
228
+ }
229
+ }
230
+ }
231
+ }
232
+ function assertNoForbiddenGitReadOptions(command, args) {
233
+ for (const arg of args) {
234
+ const normalized = normalizeArg(arg);
235
+ if (FORBIDDEN_GIT_READ_OPTIONS.has(normalized)) {
236
+ deny(command, args, 'git external diff or text conversion hooks are not allowed');
237
+ }
238
+ }
239
+ }
240
+ function firstNonOptionGitArg(command, args) {
241
+ for (let index = 0; index < args.length; index += 1) {
242
+ const arg = args[index] ?? '';
243
+ const normalized = normalizeArg(arg);
244
+ if (arg === '-C') {
245
+ index += 1;
246
+ continue;
247
+ }
248
+ if (FORBIDDEN_GIT_GLOBAL_OPTIONS.has(normalized)) {
249
+ deny(command, args, `git global option ${arg} is not allowed`);
250
+ }
251
+ if (arg.startsWith('-')) {
252
+ deny(command, args, `git global option ${arg} is not allowed before the subcommand`);
253
+ }
254
+ if (normalized.length === 0) {
255
+ continue;
256
+ }
257
+ return normalizeArg(arg);
258
+ }
259
+ return null;
260
+ }
261
+ function deny(command, args, reason) {
262
+ throw createDeniedError(command, args, reason);
263
+ }
264
+ function denyCapability(surface, reason) {
265
+ throw createDeniedError(surface, [], reason);
266
+ }
267
+ function isWriteOpenFlags(value) {
268
+ if (value === undefined || value === null) {
269
+ return false;
270
+ }
271
+ if (typeof value === 'number') {
272
+ return (value & FS_WRITE_OPEN_FLAG_BITS) !== 0;
273
+ }
274
+ return FS_WRITE_OPEN_FLAGS.test(String(value));
275
+ }
276
+ export function assertScriptPackChildProcessAllowed(command, args) {
277
+ const executable = basenameCommand(command);
278
+ const normalizedArgs = args.map(normalizeArg);
279
+ if (SHELL_COMMANDS.has(executable)) {
280
+ if (normalizedArgs.some((arg) => SHELL_WRAPPER_ARGS.has(arg))) {
281
+ deny(command, args, 'shell wrapper execution is not allowed');
282
+ }
283
+ deny(command, args, 'shell execution is not allowed');
284
+ }
285
+ if (PACKAGE_MANAGER_COMMANDS.has(executable)) {
286
+ if (normalizedArgs.some((arg) => PACKAGE_MANAGER_DENIED_ARGS.has(arg))) {
287
+ deny(command, args, 'package manager, build, lint, or test execution is not allowed');
288
+ }
289
+ deny(command, args, 'package manager execution is not allowed');
290
+ }
291
+ if (PUBLISH_COMMANDS.has(executable)) {
292
+ deny(command, args, 'network publishing or remote automation commands are not allowed');
293
+ }
294
+ if (executable !== 'git' && executable !== 'git.exe') {
295
+ deny(command, args, 'only bounded read-only git probes are allowed');
296
+ }
297
+ const subcommand = firstNonOptionGitArg(command, args);
298
+ if (!subcommand) {
299
+ deny(command, args, 'git subcommand is missing');
300
+ }
301
+ if (GIT_WRITE_SUBCOMMANDS.has(subcommand)) {
302
+ deny(command, args, 'git write, network, or ref mutation is not allowed');
303
+ }
304
+ if (!READ_ONLY_GIT_SUBCOMMANDS.has(subcommand)) {
305
+ deny(command, args, 'git subcommand is not in the read-only allowlist');
306
+ }
307
+ assertNoForbiddenGitReadOptions(command, args);
308
+ if (subcommand === 'config') {
309
+ const nonOptionArgs = normalizedArgs.filter((arg) => !arg.startsWith('-'));
310
+ const allowed = normalizedArgs.includes('--get') &&
311
+ nonOptionArgs.length === 2 &&
312
+ nonOptionArgs[0] === 'config' &&
313
+ nonOptionArgs[1] === 'core.excludesfile' &&
314
+ !normalizedArgs.some((arg) => arg === '--global' || arg === '--system' || arg === '--local');
315
+ if (!allowed) {
316
+ deny(command, args, 'git config is limited to repository-neutral read-only --get core.excludesFile probes');
317
+ }
318
+ }
319
+ }
320
+ function deniedSpawnSyncResult(error) {
321
+ return {
322
+ pid: 0,
323
+ output: [null, '', `${error.message}\n`],
324
+ stdout: '',
325
+ stderr: `${error.message}\n`,
326
+ status: 1,
327
+ signal: null,
328
+ error,
329
+ };
330
+ }
331
+ function createGuardedFunctions(original) {
332
+ return {
333
+ exec(command, optionsOrCallback, maybeCallback) {
334
+ const commandText = String(command);
335
+ assertChildProcessOptionsAllowed(commandText, [], typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback);
336
+ deny(commandText, [], 'shell string execution is not allowed');
337
+ return original.exec(command, optionsOrCallback, maybeCallback);
338
+ },
339
+ execFile(file, argsOrOptionsOrCallback, optionsOrCallback, maybeCallback) {
340
+ const args = normalizeArgs(argsOrOptionsOrCallback);
341
+ const options = Array.isArray(argsOrOptionsOrCallback) ? optionsOrCallback : argsOrOptionsOrCallback;
342
+ assertChildProcessOptionsAllowed(String(file), args, typeof options === 'function' ? undefined : options);
343
+ assertScriptPackChildProcessAllowed(String(file), args);
344
+ return original.execFile(file, argsOrOptionsOrCallback, optionsOrCallback, maybeCallback);
345
+ },
346
+ execFileSync(file, argsOrOptions, options) {
347
+ const args = normalizeArgs(argsOrOptions);
348
+ const childOptions = Array.isArray(argsOrOptions) ? options : argsOrOptions;
349
+ assertChildProcessOptionsAllowed(String(file), args, childOptions);
350
+ assertScriptPackChildProcessAllowed(String(file), args);
351
+ return original.execFileSync(file, argsOrOptions, options);
352
+ },
353
+ execSync(command, options) {
354
+ const commandText = String(command);
355
+ assertChildProcessOptionsAllowed(commandText, [], options);
356
+ deny(commandText, [], 'shell string execution is not allowed');
357
+ return original.execSync(command, options);
358
+ },
359
+ spawn(command, argsOrOptions, options) {
360
+ const args = normalizeArgs(argsOrOptions);
361
+ const childOptions = Array.isArray(argsOrOptions) ? options : argsOrOptions;
362
+ assertChildProcessOptionsAllowed(String(command), args, childOptions);
363
+ assertScriptPackChildProcessAllowed(String(command), args);
364
+ return original.spawn(command, argsOrOptions, options);
365
+ },
366
+ spawnSync(command, argsOrOptions, options) {
367
+ const args = normalizeArgs(argsOrOptions);
368
+ const childOptions = Array.isArray(argsOrOptions) ? options : argsOrOptions;
369
+ try {
370
+ assertChildProcessOptionsAllowed(String(command), args, childOptions);
371
+ assertScriptPackChildProcessAllowed(String(command), args);
372
+ }
373
+ catch (error) {
374
+ if (error instanceof ScriptPackExecutionDeniedError) {
375
+ return deniedSpawnSyncResult(error);
376
+ }
377
+ throw error;
378
+ }
379
+ return original.spawnSync(command, argsOrOptions, options);
380
+ },
381
+ };
382
+ }
383
+ function patchFunction(target, key, replacement) {
384
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
385
+ const original = target[key];
386
+ Object.defineProperty(target, key, {
387
+ configurable: true,
388
+ enumerable: descriptor?.enumerable ?? true,
389
+ writable: true,
390
+ value: replacement,
391
+ });
392
+ return {
393
+ restore: () => {
394
+ if (descriptor) {
395
+ Object.defineProperty(target, key, descriptor);
396
+ return;
397
+ }
398
+ if (original === undefined) {
399
+ delete target[key];
400
+ return;
401
+ }
402
+ target[key] = original;
403
+ },
404
+ };
405
+ }
406
+ function createDeniedCapabilityFunction(surface, reason) {
407
+ return () => denyCapability(surface, reason);
408
+ }
409
+ function createDeniedAsyncCapabilityFunction(surface, reason) {
410
+ return () => Promise.reject(createDeniedError(surface, [], reason));
411
+ }
412
+ function createDeniedOpenFunction(surface, original) {
413
+ return (...args) => {
414
+ const flags = args[1];
415
+ if (isWriteOpenFlags(flags)) {
416
+ denyCapability(surface, 'filesystem write handles are not allowed during script-pack runs');
417
+ }
418
+ return original(...args);
419
+ };
420
+ }
421
+ function patchFilesystemGuards() {
422
+ const patches = [];
423
+ const fsRecord = fs;
424
+ for (const key of FS_WRITE_FUNCTIONS) {
425
+ const original = fsRecord[key];
426
+ if (typeof original !== 'function') {
427
+ continue;
428
+ }
429
+ patches.push(patchFunction(fsRecord, key, key === 'open' || key === 'openSync'
430
+ ? createDeniedOpenFunction(`fs.${key}`, original)
431
+ : createDeniedCapabilityFunction(`fs.${key}`, 'filesystem writes are not allowed during script-pack runs')));
432
+ }
433
+ const promisesRecord = fs.promises;
434
+ for (const key of FS_PROMISE_WRITE_FUNCTIONS) {
435
+ const original = promisesRecord[key];
436
+ if (typeof original !== 'function') {
437
+ continue;
438
+ }
439
+ patches.push(patchFunction(promisesRecord, key, key === 'open'
440
+ ? createDeniedOpenFunction(`fs.promises.${key}`, original)
441
+ : createDeniedAsyncCapabilityFunction(`fs.promises.${key}`, 'filesystem writes are not allowed during script-pack runs')));
442
+ }
443
+ return patches;
444
+ }
445
+ function patchNetworkGuards() {
446
+ const globalRecord = globalThis;
447
+ const patches = [];
448
+ if (typeof globalRecord.fetch === 'function') {
449
+ patches.push(patchFunction(globalRecord, 'fetch', createDeniedAsyncCapabilityFunction('fetch', 'network access is not allowed during script-pack runs')));
450
+ }
451
+ return patches;
452
+ }
453
+ function patchWorkerGuards() {
454
+ const workerRecord = workerThreads;
455
+ if (typeof workerRecord.Worker !== 'function') {
456
+ return [];
457
+ }
458
+ function deniedWorkerConstructor() {
459
+ return denyCapability('worker_threads.Worker', 'worker threads are not allowed during script-pack runs');
460
+ }
461
+ return [
462
+ patchFunction(workerRecord, 'Worker', deniedWorkerConstructor),
463
+ ];
464
+ }
465
+ function patchBunGuards() {
466
+ const globalRecord = globalThis;
467
+ const bunRecord = isObjectRecord(globalRecord.Bun) ? globalRecord.Bun : null;
468
+ if (!bunRecord) {
469
+ return [];
470
+ }
471
+ const patches = [];
472
+ for (const key of ['spawn', 'spawnSync', 'write', '$']) {
473
+ if (typeof bunRecord[key] === 'function') {
474
+ const reason = key === 'write'
475
+ ? 'Bun filesystem writes are not allowed during script-pack runs'
476
+ : 'Bun process or shell execution is not allowed during script-pack runs';
477
+ patches.push(patchFunction(bunRecord, key, createDeniedCapabilityFunction(`Bun.${key}`, reason)));
478
+ }
479
+ }
480
+ return patches;
481
+ }
482
+ function patchProcessStateGuards() {
483
+ const processRecord = process;
484
+ const patches = [];
485
+ for (const [key, reason] of [
486
+ ['chdir', 'process working-directory changes are not allowed during script-pack runs'],
487
+ ['umask', 'process umask changes are not allowed during script-pack runs'],
488
+ ]) {
489
+ if (typeof processRecord[key] === 'function') {
490
+ patches.push(patchFunction(processRecord, key, createDeniedCapabilityFunction(`process.${key}`, reason)));
491
+ }
492
+ }
493
+ return patches;
494
+ }
495
+ export async function withScriptPackChildProcessGuard(callback) {
496
+ const original = {
497
+ exec: childProcess.exec,
498
+ execFile: childProcess.execFile,
499
+ execFileSync: childProcess.execFileSync,
500
+ execSync: childProcess.execSync,
501
+ spawn: childProcess.spawn,
502
+ spawnSync: childProcess.spawnSync,
503
+ };
504
+ const guarded = createGuardedFunctions(original);
505
+ const patches = [
506
+ ...patchFilesystemGuards(),
507
+ ...patchNetworkGuards(),
508
+ ...patchWorkerGuards(),
509
+ ...patchBunGuards(),
510
+ ...patchProcessStateGuards(),
511
+ ];
512
+ const parentGuardContext = activeGuardContext;
513
+ const guardContext = { denials: [] };
514
+ Object.assign(childProcess, guarded);
515
+ syncBuiltinESMExports();
516
+ activeGuardContext = guardContext;
517
+ try {
518
+ return await callback();
519
+ }
520
+ finally {
521
+ activeGuardContext = parentGuardContext;
522
+ for (const patch of [...patches].reverse()) {
523
+ patch.restore();
524
+ }
525
+ Object.assign(childProcess, original);
526
+ syncBuiltinESMExports();
527
+ }
528
+ }
@@ -1,6 +1,16 @@
1
1
  function scriptRef(packId, scriptId) {
2
2
  return `${packId}/${scriptId}`;
3
3
  }
4
+ export const SCRIPT_PACK_SAFETY_CONTRACT = {
5
+ authority_class: 'advisory_evidence',
6
+ execution_mode: 'suggestion_only',
7
+ command_authority: 'requires_configured_intent',
8
+ run_hint_authority: 'not_authority',
9
+ input_scope: 'explicit_or_changed_paths',
10
+ output_scope: 'bounded_report',
11
+ cannot_satisfy_intents: ['build', 'lint', 'test', 'typecheck', 'docs_validate', 'release'],
12
+ forbidden_actions: ['raw_shell', 'git_write', 'package_install', 'network_publish', 'secret_read'],
13
+ };
4
14
  export const SCRIPT_PACKS = [
5
15
  {
6
16
  id: 'code',
@@ -50,6 +50,10 @@ function renderSkillRouteAuditSummary(report, lang) {
50
50
  `${t(lang, 'skillRouteAudit.label.indexRoutes')}: ${report.counts.index_routes}`,
51
51
  `${t(lang, 'skillRouteAudit.label.templateSkills')}: ${report.counts.template_skills}`,
52
52
  `${t(lang, 'skillRouteAudit.label.findings')}: ${report.findings.length}`,
53
+ `local_registry: ${report.resolution.local_registry.status}`,
54
+ `packaged_template_registry: ${report.resolution.packaged_template_registry.status}`,
55
+ `shared_workspace_registry: ${report.resolution.shared_workspace_registry.status}`,
56
+ `active_install_profile: ${report.resolution.active_install_profile.status}${report.resolution.active_install_profile.profile ? ` (${report.resolution.active_install_profile.profile})` : ''}`,
53
57
  ];
54
58
  if (report.findings.length > 0) {
55
59
  lines.push(t(lang, 'skillRouteAudit.label.findings'));
@@ -12,6 +12,16 @@ const CODE_NAVIGATION_SCRIPT_REFS = new Set([
12
12
  ]);
13
13
  const CONFIG_CHAIN_SURFACES = new Set(['config', 'package', 'source', 'test']);
14
14
  const CONFIG_FILE_PATTERN = /(?:^|\/)(?:\.gitignore|\.env\.(?:example|sample|template|defaults)|\.dev\.vars\.example|\.nvmrc|\.node-version|\.python-version|\.tool-versions|mise\.toml|\.mise\.toml|pyproject\.toml|go\.mod|rust-toolchain(?:\.toml)?|Makefile|Taskfile\.ya?ml|justfile|Justfile|tsconfig(?:\..*)?\.json|eslint\.config\.[cm]?[jt]s|\.eslintrc(?:\.json)?|\.prettierrc(?:\.json)?|prettier\.config\.[cm]?[jt]s|vite\.config\.[cm]?[jt]s|vitest\.config\.[cm]?[jt]s|tailwind\.config\.[cm]?[jt]s|jest\.config\.[cm]?[jt]s|playwright\.config\.[cm]?[jt]s|astro\.config\.mjs|svelte\.config\.js|wrangler\.(?:toml|jsonc?)|vercel\.json|netlify\.toml|Dockerfile|docker-compose\.ya?ml|compose\.ya?ml)$/u;
15
+ export const SCRIPT_PACK_SUGGESTION_SAFETY_CONTRACT = {
16
+ authority_class: 'advisory_evidence',
17
+ execution_mode: 'suggestion_only',
18
+ command_authority: 'requires_configured_intent',
19
+ run_hint_authority: 'not_authority',
20
+ input_scope: 'explicit_or_changed_paths',
21
+ output_scope: 'bounded_report',
22
+ cannot_satisfy_intents: ['build', 'lint', 'test', 'typecheck', 'docs_validate', 'release'],
23
+ forbidden_actions: ['raw_shell', 'git_write', 'package_install', 'network_publish', 'secret_read'],
24
+ };
15
25
  export function isScriptPackSuggestionPhase(value) {
16
26
  return ['before_change', 'during_change', 'after_change', 'review'].includes(value);
17
27
  }
@@ -450,6 +460,7 @@ export function createScriptPackSuggestionReport(mustflowRoot, options) {
450
460
  risk_level: script.riskLevel,
451
461
  cost: script.cost,
452
462
  report_schema_file: script.reportSchemaFile,
463
+ safety_contract: SCRIPT_PACK_SUGGESTION_SAFETY_CONTRACT,
453
464
  run_hint: createRunHint(script, analyzedPaths),
454
465
  };
455
466
  })