@guilz-dev/belay 0.9.2 → 0.9.4

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 (118) hide show
  1. package/README.md +36 -2
  2. package/dist/adapters/codex/hooks.d.ts +1 -0
  3. package/dist/adapters/codex/hooks.js +3 -0
  4. package/dist/adapters/codex/runtime-entry.d.ts +3 -0
  5. package/dist/adapters/codex/runtime-entry.js +27 -4
  6. package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
  7. package/dist/adapters/cursor/cwd-resolution.js +58 -0
  8. package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
  9. package/dist/adapters/cursor/dispatcher-generation.js +5 -0
  10. package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
  11. package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
  12. package/dist/adapters/cursor/hook-router.d.ts +23 -0
  13. package/dist/adapters/cursor/hook-router.js +266 -0
  14. package/dist/adapters/cursor/hooks.d.ts +5 -0
  15. package/dist/adapters/cursor/hooks.js +115 -3
  16. package/dist/adapters/cursor/routing-layout.d.ts +5 -0
  17. package/dist/adapters/cursor/routing-layout.js +29 -0
  18. package/dist/adapters/cursor/runtime-entry.d.ts +18 -0
  19. package/dist/adapters/cursor/runtime-entry.js +135 -34
  20. package/dist/adapters/layouts/scope.d.ts +5 -0
  21. package/dist/adapters/layouts/scope.js +71 -0
  22. package/dist/adapters/shared/gate-runtime.js +27 -3
  23. package/dist/adapters/shared/repo-root.js +20 -1
  24. package/dist/bundle/claude-runtime.mjs +2660 -1951
  25. package/dist/bundle/codex-runtime.mjs +2680 -1953
  26. package/dist/bundle/cursor-dispatcher.mjs +443 -0
  27. package/dist/bundle/cursor-runtime.mjs +5638 -4697
  28. package/dist/cli.js +33 -3
  29. package/dist/commands/doctor.js +215 -16
  30. package/dist/commands/health-snapshot.d.ts +3 -0
  31. package/dist/commands/health-snapshot.js +61 -0
  32. package/dist/commands/recovery-checkpoints.d.ts +3 -3
  33. package/dist/commands/report.js +14 -0
  34. package/dist/commands/status.js +15 -0
  35. package/dist/commands/where.d.ts +4 -0
  36. package/dist/commands/where.js +52 -0
  37. package/dist/config-io.js +11 -2
  38. package/dist/core/approval-repo-lookup.d.ts +16 -0
  39. package/dist/core/approval-repo-lookup.js +48 -0
  40. package/dist/core/audit-io.d.ts +1 -1
  41. package/dist/core/audit-io.js +1 -1
  42. package/dist/core/audit-legacy-archive.js +43 -25
  43. package/dist/core/audit-query.d.ts +1 -0
  44. package/dist/core/audit-query.js +7 -0
  45. package/dist/core/audit-serialize.d.ts +3 -0
  46. package/dist/core/audit-serialize.js +39 -3
  47. package/dist/core/audit-summary.d.ts +9 -0
  48. package/dist/core/audit-summary.js +58 -1
  49. package/dist/core/audit-types.d.ts +4 -0
  50. package/dist/core/classify-tool.js +16 -8
  51. package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
  52. package/dist/core/effect-ir/argv-delegate.js +42 -0
  53. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
  54. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
  55. package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
  56. package/dist/core/effect-ir/shell-lower/augment.js +97 -0
  57. package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
  58. package/dist/core/effect-ir/shell-lower/context.js +1 -0
  59. package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
  60. package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
  61. package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
  62. package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
  63. package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
  64. package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
  65. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
  66. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
  67. package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
  68. package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
  69. package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
  70. package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
  71. package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
  72. package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
  73. package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
  74. package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
  75. package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
  76. package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
  77. package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
  78. package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
  79. package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
  80. package/dist/core/effect-ir/shell-lower/segment.js +82 -0
  81. package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
  82. package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
  83. package/dist/core/effect-ir/shell-lower.d.ts +2 -7
  84. package/dist/core/effect-ir/shell-lower.js +122 -1511
  85. package/dist/core/glob.js +32 -16
  86. package/dist/core/integrity.d.ts +2 -2
  87. package/dist/core/integrity.js +52 -12
  88. package/dist/core/replay-scrub.d.ts +3 -0
  89. package/dist/core/replay-scrub.js +30 -3
  90. package/dist/core/shell-substitution.js +2 -2
  91. package/dist/core/shell-tokenizer.d.ts +28 -0
  92. package/dist/core/shell-tokenizer.js +111 -29
  93. package/dist/core/shell-unparseable.js +3 -1
  94. package/dist/core/verdict/docker-compose-run.d.ts +18 -0
  95. package/dist/core/verdict/docker-compose-run.js +136 -0
  96. package/dist/core/verdict/launcher-resolve.js +28 -28
  97. package/dist/core/verdict/parser.d.ts +3 -0
  98. package/dist/core/verdict/parser.js +23 -40
  99. package/dist/core/verdict/recursive-invocation.d.ts +20 -0
  100. package/dist/core/verdict/recursive-invocation.js +224 -0
  101. package/dist/defaults.d.ts +7 -3
  102. package/dist/defaults.js +35 -21
  103. package/dist/installer/bootstrap.d.ts +1 -0
  104. package/dist/installer/bootstrap.js +2 -2
  105. package/dist/installer/runtime-artifacts.js +43 -13
  106. package/dist/installer/scope-config.d.ts +2 -2
  107. package/dist/installer.d.ts +10 -1
  108. package/dist/installer.js +83 -7
  109. package/dist/node-resolution.d.ts +1 -0
  110. package/dist/node-resolution.js +61 -0
  111. package/dist/templates.d.ts +7 -4
  112. package/dist/templates.js +42 -4
  113. package/dist/types.d.ts +35 -1
  114. package/dist/version.d.ts +1 -1
  115. package/dist/version.js +1 -1
  116. package/package.json +5 -2
  117. package/skills/belay/SKILL.md +5 -0
  118. package/skills/belay/belay-report.md +4 -1
@@ -1,20 +1,22 @@
1
- import { lstatSync, realpathSync } from 'node:fs';
2
1
  import path from 'node:path';
3
- import { inspectGitResourceIdentity } from '../git-resource-identity.js';
4
- import { canonicalPath, pathWithinRoot } from '../path-utils.js';
5
- import { isFdDuplication, isRedirectOperator, tokenizeShell } from '../shell-tokenizer.js';
2
+ import { lexShell } from '../shell-tokenizer.js';
3
+ import { decodeDockerComposeRun as decodeStructuredDockerComposeRun } from '../verdict/docker-compose-run.js';
6
4
  import { decodeEgressEffects } from '../verdict/egress-classify.js';
7
5
  import { decodeGitEffects } from '../verdict/git-classifier.js';
8
6
  import { resolveLauncherRecipe } from '../verdict/launcher-resolve.js';
9
- import { extractDockerComposeRunScript, extractRecursiveScript, isCommandInspection, isDynamicRecursiveEvaluation, parseSegment, redactCommand, segmentOpacity, splitStructuralShellSegments, structuralSubstitutionInners, } from '../verdict/parser.js';
7
+ import { decodeRecursiveInvocationTokens, parseSegment, redactCommand, segmentOpacity, splitStructuralShellSegments, structuralSubstitutionInners, } from '../verdict/parser.js';
8
+ import { innerRecipeFromArgvDelegate, peelArgvDelegateArgv } from './argv-delegate.js';
10
9
  import { joinEffectOpacity } from './normalize.js';
11
- import { classifyPackageAcquisitionSpec, innerRecipeFromPeel, peelPackageExecArgv, resolveLocalBin, } from './package-exec.js';
10
+ import { innerRecipeFromPeel, peelPackageExecArgv } from './package-exec.js';
12
11
  import { buildShellEffectPlan, } from './shell-build.js';
12
+ import { isGrammarUnknownOnly, shouldApplyArgvDelegate } from './shell-lower/argv-delegate-gate.js';
13
+ import { addRedirectEffects, effectChangingEnvironmentSignals } from './shell-lower/augment.js';
14
+ import { decodeProcessOrFilesystem } from './shell-lower/decode-process.js';
15
+ import { decodePackageExec, packageExecInnerIsMetadata, } from './shell-lower/decoders/decode-package-exec.js';
16
+ import { ensureRequirement, processRequirement, requirement, withInnerProvenance, withProvenances, } from './shell-lower/requirement.js';
17
+ import { joinNestedOpacity, requiresKnownCwd, resolveCdTransition, shellSegment, startsLocalPostgresService, } from './shell-lower/segment.js';
18
+ import { alignStructuredTokens, ENV_PREFIX_PATTERN, expandKnownVariables, extractEnvironment, isShellHead, pipeToShell, stripRedirects, stripStructuredRedirects, } from './shell-lower/tokens.js';
13
19
  const MAX_LOWER_DEPTH = 8;
14
- const ENV_PREFIX_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
15
- const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '0:0:0:0:0:0:0:1']);
16
- const SECRET_PATH_PATTERN = /(?:^|[/\\])(?:\.env(?:\..*)?|credentials?|secrets?|id_rsa)(?:$|[/\\])/i;
17
- const CONTROL_PLANE_PATH_PATTERN = /^(?:\/etc(?:\/|$)|\/var\/run(?:\/|$)|.*(?:^|[/\\])\.(?:git|ssh|cursor|claude)(?:[/\\]|$))/;
18
20
  /**
19
21
  * Parse and lower a general shell command into the canonical Task 2 model.
20
22
  * This function only produces effects; it does not evaluate policy or return a
@@ -91,43 +93,10 @@ function lowerTopLevelSegments(command, context) {
91
93
  }
92
94
  return lowered;
93
95
  }
94
- function startsLocalPostgresService(command) {
95
- const tokens = tokenizeShell(command);
96
- return (path.basename(tokens[0] ?? '') === 'docker' &&
97
- tokens[1] === 'compose' &&
98
- ['up', 'start', 'restart'].includes(tokens[2] ?? '') &&
99
- tokens.includes('postgres'));
100
- }
101
- function resolveCdTransition(command, currentCwd) {
102
- const tokens = tokenizeShell(command);
103
- if (path.basename(tokens[0] ?? '') !== 'cd') {
104
- return null;
105
- }
106
- const target = tokens[1] ?? '~';
107
- if (!target || target === '-' || target.includes('$') || target.includes('`')) {
108
- return { cwd: currentCwd, known: false };
109
- }
110
- return { cwd: resolvePathOperand(target, currentCwd), known: true };
111
- }
112
- function requiresKnownCwd(requirementValue) {
113
- if (requirementValue.action === 'fs.write' ||
114
- requirementValue.action === 'git.ref.write' ||
115
- requirementValue.action === 'control_plane.write') {
116
- return true;
117
- }
118
- return (requirementValue.action === 'process.exec' &&
119
- requirementValue.resource.kind === 'executable' &&
120
- requirementValue.resource.operation === 'spawn');
121
- }
122
- function joinNestedOpacity(outer, nested) {
123
- const nestedOpacity = nested.completeness === 'partial' && nested.opacity === 'transparent'
124
- ? 'recursive'
125
- : nested.opacity;
126
- return joinEffectOpacity(outer, nestedOpacity);
127
- }
128
96
  function lowerSegment(command, context) {
129
97
  const commandRedacted = redactCommand(command);
130
- const rawTokens = tokenizeShell(command);
98
+ const lexed = lexShell(command);
99
+ const rawTokens = lexed.tokens.map((token) => token.value);
131
100
  const environment = extractEnvironment(rawTokens, context.env);
132
101
  const env = environment.env;
133
102
  const parsed = parseSegment(command);
@@ -137,10 +106,18 @@ function lowerSegment(command, context) {
137
106
  rawTokens.every((token) => ENV_PREFIX_PATTERN.test(token))
138
107
  ? rawTokens
139
108
  : parsedTokens).map((token) => expandKnownVariables(token, env));
109
+ const decoderTokens = alignStructuredTokens(stripStructuredRedirects(lexed.tokens), stripRedirects(parsedTokens));
140
110
  const head = path.basename(tokens[0] ?? parsed.head);
141
111
  let opacity = segmentOpacity(command);
142
112
  const signals = new Set();
143
113
  const requirements = [];
114
+ if (!lexed.complete) {
115
+ requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, commandRedacted, [
116
+ 'shell.grammar_incomplete',
117
+ ]));
118
+ signals.add('shell.grammar_incomplete');
119
+ opacity = joinEffectOpacity(opacity, 'unparseable');
120
+ }
144
121
  addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
145
122
  addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
146
123
  if (environment.malformed) {
@@ -156,7 +133,7 @@ function lowerSegment(command, context) {
156
133
  signals.add('shell.xargs_stdin_dynamic');
157
134
  opacity = joinEffectOpacity(opacity, 'opaque');
158
135
  }
159
- if (context.depth >= MAX_LOWER_DEPTH) {
136
+ if (context.depth > MAX_LOWER_DEPTH) {
160
137
  requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, commandRedacted, [
161
138
  'shell.lower_depth_exceeded',
162
139
  ]));
@@ -191,50 +168,69 @@ function lowerSegment(command, context) {
191
168
  }
192
169
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
193
170
  }
194
- const recursiveScript = extractRecursiveScript(tokens);
195
- if (recursiveScript && opacity !== 'opaque' && opacity !== 'unparseable') {
196
- const dynamicEvaluation = isDynamicRecursiveEvaluation(tokens);
197
- requirements.push(processRequirement(head || 'sh', 'spawn', commandRedacted, [
171
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
172
+ if (recursive.kind === 'static' && opacity !== 'opaque' && opacity !== 'unparseable') {
173
+ requirements.push(processRequirement(recursive.interpreter, 'spawn', commandRedacted, [
198
174
  'shell.recursive_wrapper',
199
- ...(dynamicEvaluation ? ['dynamic_shell_evaluation'] : []),
175
+ 'dynamic_shell_evaluation',
200
176
  ]));
201
- const nested = lowerTopLevelSegments(recursiveScript, {
202
- ...context,
203
- command: recursiveScript,
204
- env,
205
- depth: context.depth + 1,
206
- });
207
- for (const nestedSegment of nested) {
208
- requirements.push(...nestedSegment.requirements.map((entry) => withInnerProvenance(entry, recursiveScript, head, commandRedacted)));
209
- for (const signal of nestedSegment.signals) {
210
- signals.add(signal);
177
+ if (recursive.script !== '') {
178
+ const nested = lowerTopLevelSegments(recursive.script, {
179
+ ...context,
180
+ command: recursive.script,
181
+ env,
182
+ depth: context.depth + 1,
183
+ });
184
+ for (const nestedSegment of nested) {
185
+ requirements.push(...nestedSegment.requirements.map((entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)));
186
+ for (const signal of nestedSegment.signals)
187
+ signals.add(signal);
211
188
  }
212
189
  }
213
190
  signals.add('shell.recursive_wrapper');
214
- if (dynamicEvaluation) {
215
- signals.add('dynamic_shell_evaluation');
216
- }
191
+ signals.add('dynamic_shell_evaluation');
217
192
  return shellSegment(commandRedacted, head, requirements, 'recursive', signals);
218
193
  }
219
- const dockerComposeScript = extractDockerComposeRunScript(tokens);
220
- if (dockerComposeScript && opacity !== 'opaque' && opacity !== 'unparseable') {
194
+ if (recursive.kind === 'dynamic' || recursive.kind === 'indeterminate') {
195
+ const recursiveSignals = [
196
+ recursive.signal,
197
+ ...(recursive.kind === 'dynamic' ? ['dynamic_shell_evaluation'] : []),
198
+ ];
199
+ requirements.push(processRequirement(recursive.interpreter, 'spawn', commandRedacted, recursiveSignals), requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, commandRedacted, [
200
+ ...recursiveSignals,
201
+ ]));
202
+ for (const signal of recursiveSignals)
203
+ signals.add(signal);
204
+ return shellSegment(commandRedacted, head, requirements, joinEffectOpacity(opacity, 'opaque'), signals);
205
+ }
206
+ const compose = decodeStructuredDockerComposeRun(decoderTokens);
207
+ if (compose.kind === 'recursive' && opacity !== 'opaque' && opacity !== 'unparseable') {
221
208
  requirements.push(processRequirement(head, 'spawn', commandRedacted, ['process.docker_compose_run']));
222
- const nested = lowerTopLevelSegments(dockerComposeScript, {
223
- ...context,
224
- command: dockerComposeScript,
225
- env,
226
- depth: context.depth + 1,
227
- });
228
- for (const nestedSegment of nested) {
229
- requirements.push(...nestedSegment.requirements.map((entry) => withInnerProvenance(entry, dockerComposeScript, head, commandRedacted)));
230
- for (const signal of nestedSegment.signals) {
231
- signals.add(signal);
209
+ if (compose.script !== '') {
210
+ const nested = lowerTopLevelSegments(compose.script, {
211
+ ...context,
212
+ command: compose.script,
213
+ env,
214
+ depth: context.depth + 1,
215
+ });
216
+ for (const nestedSegment of nested) {
217
+ requirements.push(...nestedSegment.requirements.map((entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)));
218
+ for (const signal of nestedSegment.signals)
219
+ signals.add(signal);
220
+ opacity = joinNestedOpacity(opacity, nestedSegment);
232
221
  }
233
- opacity = joinNestedOpacity(opacity, nestedSegment);
234
222
  }
235
223
  signals.add('process.docker_compose_run');
236
224
  return shellSegment(commandRedacted, head, requirements, 'recursive', signals);
237
225
  }
226
+ if (compose.kind === 'dynamic' || compose.kind === 'indeterminate') {
227
+ requirements.push(processRequirement(head, 'spawn', commandRedacted, ['process.docker_compose_run']), requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, commandRedacted, [
228
+ compose.signal,
229
+ ]));
230
+ signals.add('process.docker_compose_run');
231
+ signals.add(compose.signal);
232
+ return shellSegment(commandRedacted, head, requirements, joinEffectOpacity(opacity, 'opaque'), signals);
233
+ }
238
234
  const launcher = resolveLauncherRecipe({
239
235
  tokens,
240
236
  cwd: context.cwd,
@@ -292,14 +288,61 @@ function lowerSegment(command, context) {
292
288
  }
293
289
  }
294
290
  else {
295
- requirements.push(...decodeProcessOrFilesystem({
291
+ const processRequirements = decodeProcessOrFilesystem({
296
292
  tokens,
297
293
  head,
298
294
  env,
299
295
  cwd: context.cwd,
300
296
  repoRoot: context.repoRoot,
301
297
  segment: commandRedacted,
302
- }));
298
+ });
299
+ if (isGrammarUnknownOnly(processRequirements, head)) {
300
+ const argvDelegate = peelArgvDelegateArgv(tokens);
301
+ if (argvDelegate &&
302
+ !argvDelegate.opaque &&
303
+ shouldApplyArgvDelegate(head, argvDelegate.innerTokens, context.depth)) {
304
+ const innerRecipe = innerRecipeFromArgvDelegate(argvDelegate);
305
+ if (innerRecipe) {
306
+ requirements.push(processRequirement(head, 'inspect', commandRedacted, [
307
+ 'process.argv_delegate',
308
+ ...argvDelegate.signals,
309
+ ]));
310
+ for (const signal of argvDelegate.signals) {
311
+ signals.add(signal);
312
+ }
313
+ const nested = lowerTopLevelSegments(innerRecipe, {
314
+ ...context,
315
+ command: innerRecipe,
316
+ env,
317
+ depth: context.depth + 1,
318
+ });
319
+ for (const nestedSegment of nested) {
320
+ requirements.push(...nestedSegment.requirements.map((entry) => withInnerProvenance(entry, innerRecipe, head, commandRedacted)));
321
+ for (const signal of nestedSegment.signals) {
322
+ signals.add(signal);
323
+ }
324
+ opacity = joinNestedOpacity(opacity, nestedSegment);
325
+ }
326
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
327
+ }
328
+ }
329
+ if (argvDelegate?.opaque) {
330
+ requirements.push(processRequirement(head, 'spawn', commandRedacted, [
331
+ 'process.argv_delegate',
332
+ argvDelegate.reason,
333
+ ...argvDelegate.signals,
334
+ ]), requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, commandRedacted, [
335
+ argvDelegate.reason,
336
+ 'process.argv_delegate_opaque',
337
+ ]));
338
+ for (const signal of argvDelegate.signals) {
339
+ signals.add(signal);
340
+ }
341
+ signals.add('process.argv_delegate_opaque');
342
+ return shellSegment(commandRedacted, head, requirements, joinEffectOpacity(opacity, 'opaque'), signals);
343
+ }
344
+ }
345
+ requirements.push(...processRequirements);
303
346
  }
304
347
  }
305
348
  const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
@@ -325,885 +368,6 @@ function lowerSegment(command, context) {
325
368
  }
326
369
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
327
370
  }
328
- function decodePackageExec(peel, cwd, repoRoot, segment) {
329
- if (peel.opaque) {
330
- return [
331
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
332
- peel.reason,
333
- ...peel.signals,
334
- ]),
335
- ];
336
- }
337
- if (peel.reason === 'npx_wrapper_readonly') {
338
- return [];
339
- }
340
- const binHead = peel.innerTokens[0] ?? '';
341
- const metadataOnly = packageExecInnerIsMetadata(peel);
342
- const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
343
- if (local) {
344
- return metadataOnly
345
- ? [
346
- requirement('process.exec', 'process.exec', { kind: 'executable', command: local.path, operation: 'inspect' }, segment, ['package_exec.delegate', 'package_exec.local_bin_resolved']),
347
- ]
348
- : [];
349
- }
350
- const networkResources = peel.acquisitionSpecs
351
- .map((spec) => classifyPackageAcquisitionSpec(spec))
352
- .filter((source) => source.kind !== 'local')
353
- .map((source) => source.kind === 'registry'
354
- ? {
355
- kind: 'network',
356
- host: 'registry.npmjs.org',
357
- protocol: 'registry',
358
- mode: 'read',
359
- payload: 'none',
360
- }
361
- : {
362
- kind: 'network',
363
- host: source.host,
364
- ...(source.port ? { port: source.port } : {}),
365
- protocol: source.protocol,
366
- mode: 'read',
367
- payload: 'none',
368
- });
369
- if (networkResources.length === 0) {
370
- networkResources.push({
371
- kind: 'network',
372
- host: 'registry.npmjs.org',
373
- protocol: 'registry',
374
- mode: 'read',
375
- payload: 'none',
376
- });
377
- }
378
- return [
379
- ...networkResources.map((resource) => requirement('network.acquire', 'network.connect', resource, segment, [
380
- 'package_acquire_possible',
381
- ])),
382
- requirement('fs.write', 'fs.write', { kind: 'package-cache', manager: peel.launcher === 'pnpm' ? 'pnpm' : 'npm' }, segment, ['package_cache_write']),
383
- ...(metadataOnly
384
- ? [
385
- requirement('process.exec', 'process.exec', { kind: 'executable', command: binHead || peel.launcher, operation: 'inspect' }, segment, ['package_exec.delegate']),
386
- ]
387
- : []),
388
- ];
389
- }
390
- function packageExecInnerIsMetadata(peel) {
391
- const args = peel.innerTokens.slice(1);
392
- return (args.length > 0 && args.every((arg) => ['--help', '--version', '-V', '-h', '-v'].includes(arg)));
393
- }
394
- const METADATA_ONLY_FLAGS = new Set(['--version', '-v', '-V', '--help', '-h']);
395
- function isMetadataOnlyArgv(argv) {
396
- return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
397
- }
398
- function executableBaseName(head) {
399
- return path.basename(head);
400
- }
401
- const RAILS_READ_ONLY_SUBCOMMANDS = new Set(['routes', 'middleware', 'stats', 'about', 'version']);
402
- function railsReadOnlySubcommand(args) {
403
- const subcommand = args[0];
404
- if (!subcommand || subcommand.startsWith('-')) {
405
- return false;
406
- }
407
- return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
408
- }
409
- function isRubyTestScript(scriptPath) {
410
- const base = path.basename(scriptPath);
411
- return base.endsWith('_test.rb') || base.endsWith('_spec.rb');
412
- }
413
- function parseRubyTestInvocation(args) {
414
- const includePaths = [];
415
- for (let index = 0; index < args.length; index += 1) {
416
- const arg = args[index] ?? '';
417
- if (arg === '-e' || arg === '-r') {
418
- return null;
419
- }
420
- if (arg === '-I') {
421
- const includePath = args[index + 1];
422
- if (!includePath) {
423
- return null;
424
- }
425
- includePaths.push(includePath);
426
- index += 1;
427
- continue;
428
- }
429
- if (arg.startsWith('-I') && arg.length > 2) {
430
- includePaths.push(arg.slice(2));
431
- continue;
432
- }
433
- if (arg.startsWith('-')) {
434
- if (arg === '-n') {
435
- if (!args[index + 1]) {
436
- return null;
437
- }
438
- index += 1;
439
- continue;
440
- }
441
- if (arg.startsWith('-n')) {
442
- continue;
443
- }
444
- return null;
445
- }
446
- if (isRubyTestScript(arg)) {
447
- return { includePaths, scriptPath: arg };
448
- }
449
- return null;
450
- }
451
- return null;
452
- }
453
- function isRubocopMutating(args) {
454
- return args.some((arg) => arg === '-A' ||
455
- arg === '-a' ||
456
- arg === '--auto-correct' ||
457
- arg === '--autocorrect' ||
458
- arg.startsWith('--auto-correct-all') ||
459
- arg.startsWith('--autocorrect-all'));
460
- }
461
- function decodeBundleExecInner(innerHead, innerArgs, segment) {
462
- const innerBase = executableBaseName(innerHead);
463
- if (innerBase === 'rubocop') {
464
- const mutating = isRubocopMutating(innerArgs);
465
- return [
466
- processRequirement(innerHead, mutating ? 'spawn' : 'inspect', segment, mutating ? ['process.linter.mutating'] : ['process.inspect.linter']),
467
- ];
468
- }
469
- if (innerBase === 'rspec') {
470
- const targetArgs = innerArgs.filter((arg) => !arg.startsWith('-'));
471
- if (targetArgs.length === 0) {
472
- return null;
473
- }
474
- return [processRequirement(innerHead, 'spawn', segment, ['process.test_runner.rspec'])];
475
- }
476
- return null;
477
- }
478
- function decodeRuby(args, cwd, repoRoot, segment) {
479
- const parsed = parseRubyTestInvocation(args);
480
- if (!parsed) {
481
- return unsupportedProcess('ruby', segment, 'process.ruby_grammar_incomplete');
482
- }
483
- const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
484
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
485
- return unsupportedProcess('ruby', segment, 'process.ruby_outside_repo');
486
- }
487
- for (const includePath of parsed.includePaths) {
488
- const resolvedInclude = resolvePathOperand(includePath, cwd);
489
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
490
- return unsupportedProcess('ruby', segment, 'process.ruby_outside_repo');
491
- }
492
- }
493
- const lowered = [
494
- processRequirement('ruby', 'spawn', segment, ['process.test_runner.minitest']),
495
- requirement('fs.read', 'fs.read', { kind: 'path', path: scriptPath }, segment, [
496
- 'ruby.minitest_script_read',
497
- ]),
498
- ];
499
- for (const includePath of parsed.includePaths) {
500
- lowered.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(includePath, cwd) }, segment, ['ruby.minitest_load_path_read']));
501
- }
502
- return lowered;
503
- }
504
- function decodeRuntimeMetadataProcess(head, args, segment) {
505
- if (head === 'bundle') {
506
- if (args.length === 1 && isMetadataOnlyArgv(args)) {
507
- return [processRequirement(head, 'inspect', segment, ['process.inspect.runtime_metadata'])];
508
- }
509
- if (args[0] === 'exec' && args.length >= 2) {
510
- const innerHead = args[1] ?? '';
511
- const innerArgs = args.slice(2);
512
- const innerBase = executableBaseName(innerHead);
513
- if ((innerBase === 'rails' || innerBase === 'rake') && railsReadOnlySubcommand(innerArgs)) {
514
- return [
515
- processRequirement(innerHead, 'inspect', segment, ['process.inspect.rails_read_only']),
516
- ];
517
- }
518
- if (isMetadataOnlyArgv(innerArgs)) {
519
- return [
520
- processRequirement(innerHead, 'inspect', segment, [
521
- 'process.inspect.bundle_exec_metadata',
522
- ]),
523
- ];
524
- }
525
- const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
526
- if (bundleExecInner) {
527
- return bundleExecInner;
528
- }
529
- }
530
- return null;
531
- }
532
- if ((head === 'ruby' || head === 'yarn') && args.length >= 1 && isMetadataOnlyArgv(args)) {
533
- return [processRequirement(head, 'inspect', segment, ['process.inspect.runtime_metadata'])];
534
- }
535
- if (head === 'make' && args.includes('-n')) {
536
- return [processRequirement(head, 'inspect', segment, ['process.inspect.make_dry_run'])];
537
- }
538
- const base = executableBaseName(head);
539
- if ((base === 'rails' || base === 'rake') && railsReadOnlySubcommand(args)) {
540
- return [processRequirement(head, 'inspect', segment, ['process.inspect.rails_read_only'])];
541
- }
542
- return null;
543
- }
544
- function decodeSetBuiltin(args) {
545
- let index = 0;
546
- while (index < args.length) {
547
- const arg = args[index] ?? '';
548
- if (arg === '--') {
549
- index += 1;
550
- continue;
551
- }
552
- if (arg === '-o' || arg === '+o') {
553
- if (!args[index + 1]) {
554
- return false;
555
- }
556
- index += 2;
557
- continue;
558
- }
559
- if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
560
- index += 1;
561
- continue;
562
- }
563
- return false;
564
- }
565
- return true;
566
- }
567
- function decodeShellControlBuiltin(head, args) {
568
- if (head === 'set') {
569
- return decodeSetBuiltin(args) ? [] : null;
570
- }
571
- if (head === 'wait') {
572
- if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
573
- return [];
574
- }
575
- return null;
576
- }
577
- if (head === 'exit') {
578
- if (args.length === 0 || (args.length === 1 && /^-?\d+$/.test(args[0] ?? ''))) {
579
- return [];
580
- }
581
- return null;
582
- }
583
- return null;
584
- }
585
- function decodeDockerComposeRun(head, args, segment) {
586
- let composeArgs = null;
587
- let command = head;
588
- if (head === 'docker-compose') {
589
- composeArgs = args;
590
- }
591
- else if (head === 'docker' && args[0] === 'compose') {
592
- composeArgs = args.slice(1);
593
- command = 'docker';
594
- }
595
- if (!composeArgs) {
596
- return null;
597
- }
598
- if (composeArgs.includes('run')) {
599
- return [processRequirement(command, 'spawn', segment, ['process.docker_compose_run'])];
600
- }
601
- return unsupportedProcess(command, segment, 'process.docker_compose_grammar_incomplete');
602
- }
603
- function decodeProcessOrFilesystem(params) {
604
- const { tokens, head, env, cwd, repoRoot, segment } = params;
605
- const args = tokens.slice(1);
606
- if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN.test(token))) {
607
- return [];
608
- }
609
- const shellControl = decodeShellControlBuiltin(head, args);
610
- if (shellControl) {
611
- return shellControl;
612
- }
613
- if (isCommandInspection(tokens)) {
614
- return [processRequirement(head, 'inspect', segment, ['process.inspect.command_lookup'])];
615
- }
616
- if (head === 'lsof') {
617
- return validLsof(args)
618
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.lsof'])]
619
- : unsupportedProcess(head, segment, 'process.lsof_grammar_incomplete');
620
- }
621
- if (head === 'ps') {
622
- return validPs(args)
623
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.ps'])]
624
- : unsupportedProcess(head, segment, 'process.ps_grammar_incomplete');
625
- }
626
- if (head === 'test' || head === '[') {
627
- return validFilesystemTest(head, args)
628
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.filesystem_test'])]
629
- : unsupportedProcess(head, segment, 'process.filesystem_test_grammar_incomplete');
630
- }
631
- if (head === 'docker') {
632
- if (args[0] === 'info') {
633
- return validDockerInfo(args.slice(1))
634
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.docker_info'])]
635
- : unsupportedProcess(head, segment, 'process.docker_info_grammar_incomplete');
636
- }
637
- if (args[0] === 'compose' &&
638
- ['up', 'down', 'start', 'stop', 'restart', 'create', 'rm'].includes(args[1] ?? '')) {
639
- return [processRequirement(head, 'spawn', segment, ['service.local_mutation'])];
640
- }
641
- if (args[0] === 'push' ||
642
- (args[0] === 'build' && args.includes('--push')) ||
643
- (args[0] === 'buildx' &&
644
- args[1] === 'build' &&
645
- (args.includes('--push') ||
646
- args.some((arg) => arg.startsWith('--output=type=registry') || arg.startsWith('-o=type=registry'))))) {
647
- return [
648
- processRequirement(head, 'spawn', segment, ['tier0_external', 'docker.remote_mutation']),
649
- requirement('network.connect', 'network.connect', {
650
- kind: 'network',
651
- host: 'registry',
652
- protocol: 'container-registry',
653
- mode: 'mutate',
654
- payload: 'present',
655
- }, segment, ['tier0_external', 'docker.remote_mutation']),
656
- ];
657
- }
658
- if (args[0] === 'run' ||
659
- args[0] === 'create' ||
660
- args[0] === 'pull' ||
661
- args[0] === 'build' ||
662
- (args[0] === 'buildx' && args[1] === 'build')) {
663
- return [
664
- processRequirement(head, 'spawn', segment, ['process.docker_spawn']),
665
- requirement('network.acquire', 'network.connect', {
666
- kind: 'network',
667
- host: 'unknown',
668
- protocol: 'container-registry',
669
- mode: 'ambiguous',
670
- payload: 'none',
671
- }, segment, ['docker.image_acquisition_possible']),
672
- ];
673
- }
674
- return [processRequirement(head, 'spawn', segment, ['process.docker_spawn'])];
675
- }
676
- if (isShellHead(head)) {
677
- const syntax = bashSyntaxTarget(args);
678
- if (syntax) {
679
- return [
680
- processRequirement(head, 'inspect', segment, ['process.inspect.shell_syntax']),
681
- requirement('fs.read', 'fs.read', { kind: 'path', path: path.resolve(cwd, syntax) }, segment, ['shell.syntax_source_read']),
682
- ];
683
- }
684
- return unsupportedProcess(head, segment, 'process.shell_grammar_incomplete');
685
- }
686
- if (head === 'prisma') {
687
- return decodePrisma(args, env, repoRoot, segment);
688
- }
689
- const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
690
- if (runtimeMetadata) {
691
- return runtimeMetadata;
692
- }
693
- if (head === 'ruby') {
694
- return decodeRuby(args, cwd, repoRoot, segment);
695
- }
696
- if (head === 'rubocop' || head === 'rspec') {
697
- const decoded = decodeBundleExecInner(head, args, segment);
698
- if (decoded) {
699
- return decoded;
700
- }
701
- }
702
- const dockerCompose = decodeDockerComposeRun(head, args, segment);
703
- if (dockerCompose) {
704
- return dockerCompose;
705
- }
706
- if ((head === 'npm' || head === 'pnpm') && args.length === 1 && isMetadataOnlyArgv(args)) {
707
- return [processRequirement(head, 'inspect', segment, ['process.inspect.package_manager'])];
708
- }
709
- if (head === 'belay') {
710
- return decodeBelay(args, repoRoot, segment);
711
- }
712
- if (head === 'tsc') {
713
- return decodeTsc(args, cwd, segment);
714
- }
715
- if (head === 'go') {
716
- return decodeGo(args, segment);
717
- }
718
- if (head === 'rsync') {
719
- return decodeRsync(args, cwd, segment);
720
- }
721
- if (head === 'sed') {
722
- return decodeSed(args, cwd, segment);
723
- }
724
- if (head === 'base64' && args.length === 1 && ['-d', '-D', '--decode'].includes(args[0] ?? '')) {
725
- return [processRequirement(head, 'inspect', segment, ['process.inspect.base64_stdin'])];
726
- }
727
- if (head === 'node') {
728
- return decodeNode(args, cwd, segment);
729
- }
730
- if (head === 'vite' || head === 'vite-node') {
731
- return [processRequirement(head, 'spawn', segment, ['process.local_dev_spawn'])];
732
- }
733
- if (((head === 'npm' || head === 'pnpm') && args[0] === 'publish') ||
734
- (head === 'terraform' && args[0] === 'apply')) {
735
- return [
736
- processRequirement(head, 'spawn', segment, ['tier0_external']),
737
- requirement('network.connect', 'network.connect', {
738
- kind: 'network',
739
- host: 'control-plane',
740
- protocol: head,
741
- mode: 'mutate',
742
- payload: 'present',
743
- }, segment, ['tier0_external']),
744
- ];
745
- }
746
- if (head === 'cd') {
747
- return [];
748
- }
749
- if (head === 'cp' || head === 'mv') {
750
- return decodeCopyMove(head, args, cwd, segment);
751
- }
752
- if (head === 'rm') {
753
- return decodeRm(args, cwd, repoRoot, segment);
754
- }
755
- if (head === 'vitest' || head === 'eslint' || head === 'biome' || head === 'belay') {
756
- return [processRequirement(head, 'spawn', segment, ['process.known_local_spawn'])];
757
- }
758
- if (head === 'pwd' || head === 'which' || head === 'whoami') {
759
- return [processRequirement(head, 'inspect', segment, ['process.pure_inspection'])];
760
- }
761
- const readOperands = filesystemReadOperands(head, args);
762
- if (readOperands !== null) {
763
- const lowered = [processRequirement(head, 'inspect', segment, ['process.filesystem_inspect'])];
764
- for (const operand of readOperands) {
765
- const resolved = resolvePathOperand(operand, cwd);
766
- lowered.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
767
- 'filesystem.read',
768
- ]));
769
- addSecretRead(lowered, resolved, segment);
770
- }
771
- return lowered;
772
- }
773
- const writeOperands = filesystemWriteOperands(head, args);
774
- if (writeOperands !== null) {
775
- const lowered = [processRequirement(head, 'spawn', segment, ['process.filesystem_mutation'])];
776
- for (const operand of writeOperands) {
777
- addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ['filesystem.write']);
778
- }
779
- return lowered;
780
- }
781
- if (head === 'printf' || head === 'echo' || head === 'true' || head === 'false' || head === ':') {
782
- return [];
783
- }
784
- if (!head) {
785
- return [
786
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
787
- 'shell.segment_empty',
788
- ]),
789
- ];
790
- }
791
- return unsupportedProcess(head, segment, 'process.grammar_unknown');
792
- }
793
- function decodeBelay(args, repoRoot, segment) {
794
- const [section, operation, key] = args;
795
- const judgeCommand = section === 'judge' &&
796
- operation !== undefined &&
797
- ['consent', 'list', 'status', 'test', 'use'].includes(operation);
798
- const configRead = section === 'config' &&
799
- (operation === undefined ||
800
- operation === 'list' ||
801
- (operation === 'get' && key?.startsWith('judge.')));
802
- const configJudgeMutation = section === 'config' &&
803
- ((['set', 'unset'].includes(operation ?? '') && key?.startsWith('judge.')) ||
804
- (operation === 'credential' && key === 'mode'));
805
- if (judgeCommand || configRead || configJudgeMutation) {
806
- return [
807
- processRequirement('belay', 'inspect', segment, [
808
- 'belay_control_plane_command',
809
- configJudgeMutation ? 'belay.config_judge_mutation' : 'belay.config_read',
810
- ]),
811
- ];
812
- }
813
- if (section === 'config' && ['set', 'unset', 'credential'].includes(operation ?? '')) {
814
- return [
815
- requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: path.join(repoRoot, '.belay-control-plane') }, segment, ['belay.config_non_judge_mutation']),
816
- ];
817
- }
818
- return [processRequirement('belay', 'spawn', segment, ['process.known_local_spawn'])];
819
- }
820
- function decodeTsc(args, cwd, segment) {
821
- const metadataOnly = args.length > 0 && args.every((arg) => ['--help', '--version', '-h', '-v'].includes(arg));
822
- const requirements = [];
823
- let hasOutput = false;
824
- for (let index = 0; index < args.length; index += 1) {
825
- const arg = args[index] ?? '';
826
- const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
827
- const separate = (arg === '--outDir' || arg === '--outFile') && args[index + 1] ? args[index + 1] : null;
828
- const output = inline ?? separate;
829
- if (!output) {
830
- continue;
831
- }
832
- hasOutput = true;
833
- addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ['tsc.output']);
834
- if (separate) {
835
- index += 1;
836
- }
837
- }
838
- const typecheckOnly = args.includes('--noEmit') && !hasOutput;
839
- const inspect = metadataOnly || typecheckOnly;
840
- requirements.unshift(processRequirement('tsc', inspect ? 'inspect' : 'spawn', segment, [
841
- metadataOnly
842
- ? 'process.inspect.tsc_metadata'
843
- : typecheckOnly
844
- ? 'process.inspect.tsc_typecheck'
845
- : 'process.known_local_spawn',
846
- ]));
847
- return requirements;
848
- }
849
- function decodeCopyMove(head, args, cwd, segment) {
850
- const requirements = [processRequirement(head, 'spawn', segment, ['process.filesystem_mutation'])];
851
- const operands = [];
852
- let targetDirectory = null;
853
- let recursive = false;
854
- let incomplete = false;
855
- let optionsEnded = false;
856
- const neutralFlags = new Set([
857
- '-f',
858
- '-i',
859
- '-n',
860
- '-p',
861
- '-v',
862
- '--force',
863
- '--interactive',
864
- '--no-clobber',
865
- ]);
866
- for (let index = 0; index < args.length; index += 1) {
867
- const arg = args[index] ?? '';
868
- if (!optionsEnded && arg === '--') {
869
- optionsEnded = true;
870
- continue;
871
- }
872
- if (!optionsEnded && (arg === '-r' || arg === '-R' || arg === '--recursive')) {
873
- recursive = true;
874
- continue;
875
- }
876
- if (!optionsEnded && (arg === '-t' || arg === '--target-directory')) {
877
- const value = args[index + 1];
878
- if (!value || value.startsWith('-')) {
879
- incomplete = true;
880
- }
881
- else {
882
- targetDirectory = value;
883
- index += 1;
884
- }
885
- continue;
886
- }
887
- if (!optionsEnded && arg.startsWith('--target-directory=')) {
888
- targetDirectory = arg.slice('--target-directory='.length) || null;
889
- incomplete ||= !targetDirectory;
890
- continue;
891
- }
892
- if (!optionsEnded && arg.startsWith('-')) {
893
- if (!neutralFlags.has(arg)) {
894
- incomplete = true;
895
- }
896
- continue;
897
- }
898
- operands.push(arg);
899
- }
900
- const sources = targetDirectory ? operands : operands.slice(0, -1);
901
- const destination = targetDirectory ?? (operands.length >= 2 ? (operands.at(-1) ?? null) : null);
902
- if (sources.length === 0 || !destination) {
903
- incomplete = true;
904
- }
905
- for (const source of sources) {
906
- const resolved = resolvePathOperand(source, cwd);
907
- requirements.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
908
- `${head}.source_read`,
909
- ]));
910
- addSecretRead(requirements, resolved, segment);
911
- if (head === 'mv') {
912
- addWriteEffects(requirements, resolved, segment, ['mv.source_remove']);
913
- }
914
- }
915
- if (destination) {
916
- addWriteEffects(requirements, resolvePathOperand(destination, cwd), segment, [
917
- `${head}.destination_write`,
918
- ]);
919
- }
920
- if (recursive || incomplete) {
921
- requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
922
- recursive ? `${head}.recursive_source` : `${head}.grammar_incomplete`,
923
- ]));
924
- }
925
- return requirements;
926
- }
927
- function decodeRm(args, cwd, repoRoot, segment) {
928
- const requirements = [processRequirement('rm', 'spawn', segment, ['process.filesystem_mutation'])];
929
- const recursive = args.some((arg) => arg === '-r' || arg === '-R' || arg === '--recursive' || /^-[^-]*[rR]/.test(arg));
930
- const operands = args.filter((arg) => arg && !arg.startsWith('-'));
931
- if (operands.length === 0) {
932
- return [
933
- ...requirements,
934
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
935
- 'rm.operand_missing',
936
- ]),
937
- ];
938
- }
939
- const identity = inspectGitResourceIdentity(repoRoot);
940
- for (const operand of operands) {
941
- const resolved = resolvePathOperand(operand, cwd);
942
- addWriteEffects(requirements, resolved, segment, ['filesystem.write', 'rm.remove']);
943
- const finalOperandIsSymlink = isSymbolicLink(resolved);
944
- const canonicalResolved = canonicalRmOperand(resolved, finalOperandIsSymlink);
945
- const targetIdentity = recursive && !finalOperandIsSymlink
946
- ? inspectGitResourceIdentity(resolved)
947
- : { status: 'absent' };
948
- const sameIdentityTargetRoot = identity.status === 'resolved' &&
949
- targetIdentity.status === 'resolved' &&
950
- identity.identity.commonDir === targetIdentity.identity.commonDir &&
951
- pathContains(canonicalResolved, targetIdentity.identity.repositoryRoot);
952
- if (recursive &&
953
- identity.status === 'resolved' &&
954
- (sameIdentityTargetRoot ||
955
- [
956
- repoRoot,
957
- identity.identity.repositoryRoot,
958
- identity.identity.gitDir,
959
- identity.identity.commonDir,
960
- identity.identity.gitEntryPath,
961
- ].some((metadataPath) => pathContains(canonicalResolved, metadataPath)))) {
962
- const gitEntryPath = targetIdentity.status === 'resolved'
963
- ? targetIdentity.identity.gitEntryPath
964
- : identity.identity.gitEntryPath;
965
- requirements.push(requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: gitEntryPath }, segment, ['rm.recursive_git_boundary', 'tier1_catastrophic']));
966
- }
967
- }
968
- return requirements;
969
- }
970
- function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
971
- try {
972
- if (finalOperandIsSymlink) {
973
- return path.join(realpathSync.native(path.dirname(targetPath)), path.basename(targetPath));
974
- }
975
- return realpathSync.native(targetPath);
976
- }
977
- catch {
978
- return path.resolve(targetPath);
979
- }
980
- }
981
- function isSymbolicLink(targetPath) {
982
- try {
983
- return lstatSync(targetPath).isSymbolicLink();
984
- }
985
- catch {
986
- return false;
987
- }
988
- }
989
- function pathContains(ancestor, candidate) {
990
- const relative = path.relative(path.resolve(ancestor), path.resolve(candidate));
991
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
992
- }
993
- function decodeGo(args, segment) {
994
- if (['test', 'list', 'vet'].includes(args[0] ?? '')) {
995
- return [processRequirement('go', 'spawn', segment, ['process.go_local_spawn'])];
996
- }
997
- if (args[0] === 'install' || (args[0] === 'mod' && args[1] === 'download')) {
998
- return [
999
- processRequirement('go', 'spawn', segment, ['process.go_acquire']),
1000
- requirement('network.acquire', 'network.connect', {
1001
- kind: 'network',
1002
- host: 'unknown',
1003
- protocol: 'go-module',
1004
- mode: 'ambiguous',
1005
- payload: 'none',
1006
- }, segment, ['go.network_acquisition_possible']),
1007
- ];
1008
- }
1009
- return unsupportedProcess('go', segment, 'process.go_grammar_incomplete');
1010
- }
1011
- function decodeRsync(args, cwd, segment) {
1012
- const destructive = args.some((arg) => arg === '--delete' || arg === '--del' || arg.startsWith('--delete-'));
1013
- if (destructive) {
1014
- return [
1015
- processRequirement('rsync', 'spawn', segment, ['rsync_destructive']),
1016
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1017
- 'rsync_destructive',
1018
- ]),
1019
- ];
1020
- }
1021
- const allowedFlags = new Set([
1022
- '-a',
1023
- '-r',
1024
- '-v',
1025
- '-z',
1026
- '--archive',
1027
- '--delay-updates',
1028
- '--recursive',
1029
- '--verbose',
1030
- ]);
1031
- if (args.some((arg) => arg.startsWith('-') && !allowedFlags.has(arg))) {
1032
- return unsupportedProcess('rsync', segment, 'process.rsync_grammar_incomplete');
1033
- }
1034
- const operands = args.filter((arg) => !arg.startsWith('-'));
1035
- if (operands.length < 2) {
1036
- return unsupportedProcess('rsync', segment, 'process.rsync_operands_incomplete');
1037
- }
1038
- if (operands.some(isRemoteRsyncOperand)) {
1039
- return [
1040
- processRequirement('rsync', 'spawn', segment, ['rsync.remote']),
1041
- requirement('network.connect', 'network.connect', {
1042
- kind: 'network',
1043
- host: 'remote',
1044
- protocol: 'rsync',
1045
- mode: 'mutate',
1046
- payload: 'present',
1047
- }, segment, ['rsync.remote']),
1048
- ];
1049
- }
1050
- const destination = operands.at(-1) ?? '';
1051
- return [
1052
- processRequirement('rsync', 'spawn', segment, ['process.rsync_local']),
1053
- ...operands
1054
- .slice(0, -1)
1055
- .map((operand) => requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(operand, cwd) }, segment, ['rsync.local_source'])),
1056
- requirement('fs.write', 'fs.write', { kind: 'path', path: resolvePathOperand(destination, cwd) }, segment, ['rsync.local_destination']),
1057
- ];
1058
- }
1059
- function isRemoteRsyncOperand(operand) {
1060
- return (/^rsync:\/\//i.test(operand) ||
1061
- /^[^/:\s]+::/.test(operand) ||
1062
- /^(?:[^@/:\s]+@)?[^/:\s]+:/.test(operand));
1063
- }
1064
- function decodeSed(args, cwd, segment) {
1065
- let inline = false;
1066
- let scriptConsumed = false;
1067
- const files = [];
1068
- for (let index = 0; index < args.length; index += 1) {
1069
- const arg = args[index] ?? '';
1070
- if (arg === '-n' || arg === '--quiet' || arg === '--silent') {
1071
- continue;
1072
- }
1073
- if (arg === '-i' || arg === '--in-place' || arg.startsWith('-i')) {
1074
- inline = true;
1075
- continue;
1076
- }
1077
- if (arg === '-e' || arg === '--expression') {
1078
- if (!args[index + 1]) {
1079
- return unsupportedProcess('sed', segment, 'process.sed_expression_missing');
1080
- }
1081
- scriptConsumed = true;
1082
- index += 1;
1083
- continue;
1084
- }
1085
- if (arg.startsWith('--expression=')) {
1086
- scriptConsumed = true;
1087
- continue;
1088
- }
1089
- if (arg.startsWith('-')) {
1090
- return unsupportedProcess('sed', segment, 'process.sed_grammar_incomplete');
1091
- }
1092
- if (!scriptConsumed) {
1093
- scriptConsumed = true;
1094
- continue;
1095
- }
1096
- files.push(arg);
1097
- }
1098
- const operation = inline ? 'spawn' : 'inspect';
1099
- const lowered = [processRequirement('sed', operation, segment, ['process.sed'])];
1100
- for (const file of files) {
1101
- lowered.push(requirement(inline ? 'fs.write' : 'fs.read', inline ? 'fs.write' : 'fs.read', { kind: 'path', path: resolvePathOperand(file, cwd) }, segment, [inline ? 'sed.in_place_write' : 'sed.file_read']));
1102
- }
1103
- return lowered;
1104
- }
1105
- function decodeNode(args, cwd, segment) {
1106
- if (args.length > 0 && args.every((arg) => ['--help', '--version', '-h', '-v'].includes(arg))) {
1107
- return [processRequirement('node', 'inspect', segment, ['process.inspect.node_metadata'])];
1108
- }
1109
- if ((args[0] === '--check' || args[0] === '-c') && args.length === 2 && args[1]) {
1110
- return [
1111
- processRequirement('node', 'inspect', segment, ['process.inspect.node_syntax']),
1112
- requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(args[1], cwd) }, segment, ['node.syntax_source_read']),
1113
- ];
1114
- }
1115
- return unsupportedProcess('node', segment, 'process.node_grammar_incomplete');
1116
- }
1117
- function decodePrisma(args, env, repoRoot, segment) {
1118
- const processEffect = processRequirement('prisma', 'spawn', segment, ['process.prisma']);
1119
- if (args[0] === 'generate') {
1120
- return [
1121
- processEffect,
1122
- requirement('fs.write', 'fs.write', { kind: 'path', path: repoRoot }, segment, [
1123
- 'prisma.generate.repo_local',
1124
- ]),
1125
- ];
1126
- }
1127
- const databaseMutation = args[0] === 'migrate' || (args[0] === 'db' && args[1] === 'seed') || args[0] === 'seed';
1128
- if (!databaseMutation) {
1129
- return [
1130
- processEffect,
1131
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1132
- 'prisma.grammar_incomplete',
1133
- ]),
1134
- ];
1135
- }
1136
- const endpoint = databaseEndpoint(env.DATABASE_URL);
1137
- if (!endpoint) {
1138
- return [
1139
- processEffect,
1140
- requirement('network.connect', 'network.connect', {
1141
- kind: 'network',
1142
- host: 'unknown',
1143
- protocol: 'database',
1144
- mode: 'ambiguous',
1145
- payload: 'present',
1146
- }, segment, ['prisma.database_endpoint_unknown']),
1147
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1148
- 'prisma.database_endpoint_unknown',
1149
- ]),
1150
- ];
1151
- }
1152
- const network = requirement('network.connect', 'network.connect', {
1153
- kind: 'network',
1154
- ...endpoint,
1155
- mode: 'mutate',
1156
- payload: 'present',
1157
- }, segment, [LOOPBACK_HOSTS.has(endpoint.host) ? 'prisma.database_local' : 'prisma.database_remote']);
1158
- if (LOOPBACK_HOSTS.has(endpoint.host)) {
1159
- return [processEffect, network];
1160
- }
1161
- return [
1162
- processEffect,
1163
- network,
1164
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1165
- 'prisma.remote_database_mutation',
1166
- ]),
1167
- ];
1168
- }
1169
- function addRedirectEffects(requirements, tokens, env, context, segment) {
1170
- for (let index = 0; index < tokens.length; index += 1) {
1171
- const operator = tokens[index] ?? '';
1172
- if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
1173
- continue;
1174
- }
1175
- const rawTarget = tokens[index + 1];
1176
- if (!rawTarget) {
1177
- requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1178
- 'shell.redirect_target_missing',
1179
- ]));
1180
- continue;
1181
- }
1182
- if (operator.includes('<<')) {
1183
- requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1184
- 'shell.heredoc_opaque',
1185
- ]));
1186
- index += 1;
1187
- continue;
1188
- }
1189
- const target = expandKnownVariables(rawTarget, env);
1190
- const resolved = resolvePathOperand(target, context.cwd);
1191
- if (resolved === '/dev/null') {
1192
- index += 1;
1193
- continue;
1194
- }
1195
- if (operator.includes('<')) {
1196
- requirements.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
1197
- 'shell.input_redirect',
1198
- ]));
1199
- addSecretRead(requirements, resolved, segment);
1200
- }
1201
- if (operator.includes('>')) {
1202
- addWriteEffects(requirements, resolved, segment, ['shell.output_redirect']);
1203
- }
1204
- index += 1;
1205
- }
1206
- }
1207
371
  function addSubstitutionEffects(requirements, command, context, segment, signals) {
1208
372
  const inners = structuralSubstitutionInners(command);
1209
373
  for (const inner of inners) {
@@ -1235,556 +399,3 @@ function addSubstitutionEffects(requirements, command, context, segment, signals
1235
399
  }
1236
400
  }
1237
401
  }
1238
- function extractEnvironment(tokens, inherited) {
1239
- const env = { ...inherited };
1240
- const changedNames = new Set();
1241
- if (tokens[0] === 'env') {
1242
- let index = 1;
1243
- let malformed = false;
1244
- while (index < tokens.length) {
1245
- const option = tokens[index] ?? '';
1246
- if (option === '-u' || option === '--unset') {
1247
- const name = tokens[index + 1] ?? '';
1248
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
1249
- malformed = true;
1250
- index += 1;
1251
- break;
1252
- }
1253
- delete env[name];
1254
- changedNames.add(name);
1255
- index += 2;
1256
- continue;
1257
- }
1258
- if (option.startsWith('--unset=')) {
1259
- const name = option.slice('--unset='.length);
1260
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
1261
- malformed = true;
1262
- index += 1;
1263
- break;
1264
- }
1265
- delete env[name];
1266
- changedNames.add(name);
1267
- index += 1;
1268
- continue;
1269
- }
1270
- if (option === '-i' || option === '--ignore-environment') {
1271
- for (const name of Object.keys(env)) {
1272
- delete env[name];
1273
- }
1274
- changedNames.add('*');
1275
- index += 1;
1276
- continue;
1277
- }
1278
- if (option === '--') {
1279
- index += 1;
1280
- break;
1281
- }
1282
- if (option.startsWith('-')) {
1283
- malformed = true;
1284
- index += 1;
1285
- break;
1286
- }
1287
- break;
1288
- }
1289
- while (index < tokens.length) {
1290
- const token = tokens[index] ?? '';
1291
- const match = ENV_PREFIX_PATTERN.exec(token);
1292
- if (!match) {
1293
- break;
1294
- }
1295
- const [, name, value] = match;
1296
- if (name) {
1297
- env[name] = expandKnownVariables(value ?? '', env);
1298
- changedNames.add(name);
1299
- }
1300
- index += 1;
1301
- }
1302
- const commandTokens = tokens.slice(index);
1303
- return {
1304
- env,
1305
- commandTokens,
1306
- malformed: malformed || commandTokens.length === 0,
1307
- changedNames,
1308
- };
1309
- }
1310
- for (const token of tokens) {
1311
- const match = ENV_PREFIX_PATTERN.exec(token);
1312
- if (!match) {
1313
- break;
1314
- }
1315
- const [, name, value] = match;
1316
- if (name) {
1317
- env[name] = expandKnownVariables(value ?? '', env);
1318
- changedNames.add(name);
1319
- }
1320
- }
1321
- return { env, malformed: false, changedNames };
1322
- }
1323
- function expandKnownVariables(token, env) {
1324
- return token.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (raw, a, b) => {
1325
- const value = env[a ?? b];
1326
- return value === undefined ? raw : value;
1327
- });
1328
- }
1329
- function stripRedirects(tokens) {
1330
- const stripped = [];
1331
- for (let index = 0; index < tokens.length; index += 1) {
1332
- const token = tokens[index] ?? '';
1333
- if (isFdDuplication(token)) {
1334
- continue;
1335
- }
1336
- if (!isRedirectOperator(token)) {
1337
- stripped.push(token);
1338
- continue;
1339
- }
1340
- if (token.includes('>') || token.includes('<')) {
1341
- const inline = token.replace(/^\d*(?:>>?|<<?|<>|>\|)/, '');
1342
- if (!inline) {
1343
- index += 1;
1344
- }
1345
- }
1346
- }
1347
- return stripped;
1348
- }
1349
- function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
1350
- const normalizedRequirements = requirements.flatMap((entry) => {
1351
- const dynamicSignal = dynamicResourceSignal(entry.resource);
1352
- if (dynamicSignal) {
1353
- return [
1354
- { ...entry, resource: { kind: 'unknown' } },
1355
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, entry.provenance.segment ?? commandRedacted, [...entry.evidence.signals, dynamicSignal]),
1356
- ];
1357
- }
1358
- return [entry];
1359
- });
1360
- const partial = normalizedRequirements.some((entry) => entry.tag === 'indeterminate' || entry.action === 'indeterminate');
1361
- return {
1362
- commandRedacted,
1363
- segmentHead,
1364
- requirements: normalizedRequirements,
1365
- completeness: partial ? 'partial' : 'complete',
1366
- opacity,
1367
- signals: [...signals].sort(),
1368
- };
1369
- }
1370
- const DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
1371
- const SHELL_GLOB_PATTERN = /[*?[]/;
1372
- function effectChangingEnvironmentSignals(head, env, changedNames) {
1373
- if (head === 'curl' &&
1374
- (env.CURL_HOME ||
1375
- changedNames.has('CURL_HOME') ||
1376
- changedNames.has('HOME') ||
1377
- changedNames.has('XDG_CONFIG_HOME'))) {
1378
- return ['egress.curl.environment_config_override'];
1379
- }
1380
- if (head === 'wget' && (env.WGETRC || changedNames.has('WGETRC') || changedNames.has('HOME'))) {
1381
- return ['egress.wget.environment_config_override'];
1382
- }
1383
- if (head !== 'git') {
1384
- return [];
1385
- }
1386
- const hazardousNames = new Set([
1387
- 'GIT_ASKPASS',
1388
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
1389
- 'GIT_CEILING_DIRECTORIES',
1390
- 'GIT_COMMON_DIR',
1391
- 'GIT_CONFIG',
1392
- 'GIT_CONFIG_GLOBAL',
1393
- 'GIT_CONFIG_SYSTEM',
1394
- 'GIT_DIR',
1395
- 'GIT_DISCOVERY_ACROSS_FILESYSTEM',
1396
- 'GIT_EDITOR',
1397
- 'GIT_EXEC_PATH',
1398
- 'GIT_EXTERNAL_DIFF',
1399
- 'GIT_INDEX_FILE',
1400
- 'GIT_NAMESPACE',
1401
- 'GIT_OBJECT_DIRECTORY',
1402
- 'GIT_PAGER',
1403
- 'GIT_PROXY_COMMAND',
1404
- 'GIT_QUARANTINE_PATH',
1405
- 'GIT_SEQUENCE_EDITOR',
1406
- 'GIT_SHALLOW_FILE',
1407
- 'GIT_SSH',
1408
- 'GIT_SSH_COMMAND',
1409
- 'GIT_TEMPLATE_DIR',
1410
- 'GIT_WORK_TREE',
1411
- 'SSH_ASKPASS',
1412
- ]);
1413
- const overridden = changedNames.has('HOME') ||
1414
- changedNames.has('XDG_CONFIG_HOME') ||
1415
- Object.entries(env).some(([name, value]) => {
1416
- if (!value) {
1417
- return false;
1418
- }
1419
- if (hazardousNames.has(name) ||
1420
- /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) ||
1421
- /^GIT_TRACE/.test(name)) {
1422
- return true;
1423
- }
1424
- return name === 'GIT_CONFIG_COUNT' && value !== '0';
1425
- });
1426
- return overridden ? ['git.environment_execution_or_config_override'] : [];
1427
- }
1428
- function dynamicResourceSignal(resource) {
1429
- const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
1430
- const pathLike = (value, allowSemanticWildcard = false) => Boolean(value && (expanded(value) || (!allowSemanticWildcard && SHELL_GLOB_PATTERN.test(value))));
1431
- switch (resource.kind) {
1432
- case 'path':
1433
- return pathLike(resource.path) ? 'shell.path_unresolved' : null;
1434
- case 'network':
1435
- return expanded(resource.host) || expanded(resource.protocol)
1436
- ? 'shell.network_resource_unresolved'
1437
- : null;
1438
- case 'git-ref':
1439
- return pathLike(resource.repoPath) || pathLike(resource.ref, resource.ref.endsWith('/*'))
1440
- ? 'shell.git_resource_unresolved'
1441
- : null;
1442
- case 'executable':
1443
- return expanded(resource.command) ? 'shell.executable_unresolved' : null;
1444
- default:
1445
- return null;
1446
- }
1447
- }
1448
- function processRequirement(command, operation, segment, signals) {
1449
- return requirement('process.exec', 'process.exec', { kind: 'executable', command, operation }, segment, signals);
1450
- }
1451
- function unsupportedProcess(command, segment, signal) {
1452
- return [
1453
- processRequirement(command, 'spawn', segment, [signal]),
1454
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [signal]),
1455
- ];
1456
- }
1457
- function addWriteEffects(requirements, resolved, segment, signals) {
1458
- requirements.push(requirement('fs.write', 'fs.write', { kind: 'path', path: resolved }, segment, signals));
1459
- if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
1460
- requirements.push(requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: resolved }, segment, [...signals, 'protected_path']));
1461
- }
1462
- }
1463
- function addSecretRead(requirements, resolved, segment) {
1464
- if (SECRET_PATH_PATTERN.test(resolved)) {
1465
- requirements.push(requirement('secret.read', 'secret.read', { kind: 'path', path: resolved }, segment, [
1466
- 'secret_path_read',
1467
- ]));
1468
- }
1469
- }
1470
- function filesystemReadOperands(head, args) {
1471
- switch (head) {
1472
- case 'ls': {
1473
- const operands = args.filter((arg) => arg && !arg.startsWith('-'));
1474
- return operands.length > 0 ? operands : ['.'];
1475
- }
1476
- case 'find':
1477
- return findReadOperands(args);
1478
- case 'cat':
1479
- case 'head':
1480
- case 'tail':
1481
- case 'less':
1482
- case 'stat':
1483
- case 'file':
1484
- case 'du':
1485
- case 'wc':
1486
- return args.filter((arg) => arg && !arg.startsWith('-'));
1487
- case 'grep':
1488
- case 'rg':
1489
- return args.slice(1).filter((arg) => arg && !arg.startsWith('-'));
1490
- case 'jq':
1491
- return args.slice(1).filter((arg) => arg && !arg.startsWith('-'));
1492
- default:
1493
- return null;
1494
- }
1495
- }
1496
- function findReadOperands(args) {
1497
- const mutatingPrimaries = new Set([
1498
- '-delete',
1499
- '-exec',
1500
- '-execdir',
1501
- '-fls',
1502
- '-fprint',
1503
- '-fprintf',
1504
- '-ok',
1505
- '-okdir',
1506
- ]);
1507
- if (args.some((arg) => mutatingPrimaries.has(arg) ||
1508
- [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`)))) {
1509
- return null;
1510
- }
1511
- const paths = [];
1512
- for (const arg of args) {
1513
- if (arg.startsWith('-') || arg === '!' || arg === '(') {
1514
- break;
1515
- }
1516
- paths.push(arg);
1517
- }
1518
- return paths.length > 0 ? paths : ['.'];
1519
- }
1520
- function filesystemWriteOperands(head, args) {
1521
- const positional = args.filter((arg) => arg && !arg.startsWith('-'));
1522
- switch (head) {
1523
- case 'touch':
1524
- case 'mkdir':
1525
- case 'rm':
1526
- case 'truncate':
1527
- case 'chmod':
1528
- return positional;
1529
- case 'cp':
1530
- case 'mv':
1531
- return positional.length > 0 ? [positional[positional.length - 1] ?? ''] : [];
1532
- case 'tee':
1533
- return positional;
1534
- default:
1535
- return null;
1536
- }
1537
- }
1538
- function validLsof(args) {
1539
- const flagsWithoutValues = new Set(['-a', '-b', '-l', '-n', '-P', '-R', '-t', '-V']);
1540
- const flagsWithValues = new Set(['-c', '-d', '-F', '-g', '-p', '-T', '-u', '+d', '+D']);
1541
- for (let index = 0; index < args.length; index += 1) {
1542
- const arg = args[index] ?? '';
1543
- if (flagsWithoutValues.has(arg) || arg === '-i') {
1544
- continue;
1545
- }
1546
- if (flagsWithValues.has(arg)) {
1547
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1548
- return false;
1549
- }
1550
- index += 1;
1551
- continue;
1552
- }
1553
- if ((/^-[ablnPRtV]+$/.test(arg) &&
1554
- [...arg.slice(1)].every((flag) => 'ablnPRtV'.includes(flag))) ||
1555
- /^-i(?:TCP|UDP)?(?::\d+(?:-\d+)?)?$/.test(arg) ||
1556
- /^-s(?:TCP|UDP):[A-Za-z]+$/.test(arg) ||
1557
- /^-(?:c|d|F|g|p|T|u).+$/.test(arg) ||
1558
- /^\+(?:d|D).+$/.test(arg)) {
1559
- continue;
1560
- }
1561
- return false;
1562
- }
1563
- return true;
1564
- }
1565
- function validPs(args) {
1566
- const bareForms = new Set(['aux', 'ax', 'x', 'u']);
1567
- const flagsWithoutValues = new Set(['-A', '-a', '-d', '-e', '-f', '-j', '-l', '-T', '-x']);
1568
- const flagsWithValues = new Set([
1569
- '--format',
1570
- '--group',
1571
- '--pid',
1572
- '--ppid',
1573
- '--sort',
1574
- '--tty',
1575
- '--user',
1576
- ]);
1577
- for (let index = 0; index < args.length; index += 1) {
1578
- const arg = args[index] ?? '';
1579
- if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
1580
- continue;
1581
- }
1582
- if (flagsWithValues.has(arg)) {
1583
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1584
- return false;
1585
- }
1586
- index += 1;
1587
- continue;
1588
- }
1589
- if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
1590
- continue;
1591
- }
1592
- if (/^-[^-]/.test(arg)) {
1593
- const noValue = new Set(['A', 'a', 'd', 'e', 'f', 'j', 'l', 'T', 'x']);
1594
- const withValue = new Set(['G', 'g', 'N', 'o', 'p', 't', 'U', 'u']);
1595
- const characters = arg.slice(1);
1596
- let valid = true;
1597
- for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
1598
- const option = characters[optionIndex] ?? '';
1599
- if (noValue.has(option)) {
1600
- continue;
1601
- }
1602
- if (withValue.has(option)) {
1603
- if (optionIndex === characters.length - 1) {
1604
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1605
- return false;
1606
- }
1607
- index += 1;
1608
- }
1609
- break;
1610
- }
1611
- valid = false;
1612
- break;
1613
- }
1614
- if (valid) {
1615
- continue;
1616
- }
1617
- }
1618
- return false;
1619
- }
1620
- return true;
1621
- }
1622
- function validFilesystemTest(head, args) {
1623
- const operands = head === '[' && args.at(-1) === ']' ? args.slice(0, -1) : args;
1624
- if (head === '[' && args.at(-1) !== ']') {
1625
- return false;
1626
- }
1627
- return (operands.length === 2 &&
1628
- new Set(['-b', '-c', '-d', '-e', '-f', '-h', '-L', '-r', '-s', '-w', '-x']).has(operands[0] ?? '') &&
1629
- Boolean(operands[1]));
1630
- }
1631
- function validDockerInfo(args) {
1632
- for (let index = 0; index < args.length; index += 1) {
1633
- const arg = args[index] ?? '';
1634
- if (arg === '--help') {
1635
- continue;
1636
- }
1637
- if (arg === '--format' || arg === '-f') {
1638
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1639
- return false;
1640
- }
1641
- index += 1;
1642
- continue;
1643
- }
1644
- if (arg.startsWith('--format=')) {
1645
- if (arg.slice('--format='.length).length === 0) {
1646
- return false;
1647
- }
1648
- continue;
1649
- }
1650
- return false;
1651
- }
1652
- return true;
1653
- }
1654
- function bashSyntaxTarget(args) {
1655
- let syntaxOnly = false;
1656
- let script = null;
1657
- const shortFlags = new Set('abefhkmnptuvxBCEHPT'.split(''));
1658
- const longFlags = new Set([
1659
- '--debugger',
1660
- '--dump-po-strings',
1661
- '--dump-strings',
1662
- '--help',
1663
- '--login',
1664
- '--noediting',
1665
- '--noprofile',
1666
- '--norc',
1667
- '--posix',
1668
- '--restricted',
1669
- '--verbose',
1670
- '--version',
1671
- ]);
1672
- for (let index = 0; index < args.length; index += 1) {
1673
- const arg = args[index] ?? '';
1674
- if (arg === '-c' || arg === '-lc') {
1675
- return null;
1676
- }
1677
- if (arg === '-O' || arg === '+O' || arg === '--rcfile' || arg === '--init-file') {
1678
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1679
- return null;
1680
- }
1681
- index += 1;
1682
- continue;
1683
- }
1684
- if ((arg.startsWith('-O') && arg.length > 2) ||
1685
- (arg.startsWith('+O') && arg.length > 2) ||
1686
- (arg.startsWith('--rcfile=') && arg.length > '--rcfile='.length) ||
1687
- (arg.startsWith('--init-file=') && arg.length > '--init-file='.length)) {
1688
- continue;
1689
- }
1690
- if (longFlags.has(arg)) {
1691
- continue;
1692
- }
1693
- if (/^[+-][A-Za-z]+$/.test(arg)) {
1694
- const options = arg.slice(1);
1695
- if ([...options].some((option) => !shortFlags.has(option))) {
1696
- return null;
1697
- }
1698
- syntaxOnly ||= options.includes('n');
1699
- continue;
1700
- }
1701
- if (arg === '--') {
1702
- script = args[index + 1] ?? null;
1703
- break;
1704
- }
1705
- if (arg.startsWith('-') || arg.startsWith('+')) {
1706
- return null;
1707
- }
1708
- script = arg;
1709
- break;
1710
- }
1711
- return syntaxOnly ? script : null;
1712
- }
1713
- function isOptionToken(value) {
1714
- return value.startsWith('-') || value.startsWith('+');
1715
- }
1716
- function databaseEndpoint(raw) {
1717
- if (!raw || raw.includes('$')) {
1718
- return null;
1719
- }
1720
- try {
1721
- const url = new URL(raw);
1722
- if (!url.hostname || !url.protocol) {
1723
- return null;
1724
- }
1725
- return {
1726
- host: url.hostname.toLowerCase(),
1727
- protocol: url.protocol.slice(0, -1),
1728
- ...(url.port ? { port: Number(url.port) } : {}),
1729
- };
1730
- }
1731
- catch {
1732
- return null;
1733
- }
1734
- }
1735
- function resolvePathOperand(operand, cwd) {
1736
- if (operand === '~') {
1737
- return process.env.HOME ?? operand;
1738
- }
1739
- if (operand.startsWith('~/')) {
1740
- return path.join(process.env.HOME ?? '~', operand.slice(2));
1741
- }
1742
- return path.resolve(cwd, operand);
1743
- }
1744
- function isShellHead(head) {
1745
- return head === 'bash' || head === 'sh' || head === 'zsh' || head === 'dash' || head === 'fish';
1746
- }
1747
- function pipeToShell(command) {
1748
- return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
1749
- }
1750
- function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
1751
- const provenance = {
1752
- ...requirementValue.provenance,
1753
- segment: outerSegment,
1754
- innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
1755
- ...(launcher === 'npm' || launcher === 'pnpm' ? { launcher, phase: 'delegate' } : {}),
1756
- };
1757
- const launcherProvenance = {
1758
- segment: outerSegment,
1759
- innerCommand,
1760
- ...(launcher === 'npm' || launcher === 'pnpm' ? { launcher, phase: 'delegate' } : {}),
1761
- };
1762
- return withProvenances(requirementValue, provenance, launcherProvenance);
1763
- }
1764
- function withProvenances(requirementValue, provenance, contributing) {
1765
- const provenances = [
1766
- ...(requirementValue.provenances ?? [requirementValue.provenance]),
1767
- contributing,
1768
- provenance,
1769
- ].filter((candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index);
1770
- return { ...requirementValue, provenance, provenances };
1771
- }
1772
- function ensureRequirement(requirements, candidate) {
1773
- if (!requirements.some((entry) => entry.action === candidate.action &&
1774
- JSON.stringify(entry.resource) === JSON.stringify(candidate.resource))) {
1775
- requirements.push(candidate);
1776
- }
1777
- }
1778
- function requirement(tag, action, resource, segment, signals) {
1779
- return {
1780
- tag,
1781
- action,
1782
- resource,
1783
- evidence: {
1784
- level: tag === 'indeterminate' ? 'indeterminate' : 'certain',
1785
- signals: [...new Set(signals)].sort(),
1786
- basis: ['shell_semantic_lowering'],
1787
- },
1788
- provenance: { segment },
1789
- };
1790
- }