@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.
- package/README.md +36 -2
- package/dist/adapters/codex/hooks.d.ts +1 -0
- package/dist/adapters/codex/hooks.js +3 -0
- package/dist/adapters/codex/runtime-entry.d.ts +3 -0
- package/dist/adapters/codex/runtime-entry.js +27 -4
- package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
- package/dist/adapters/cursor/cwd-resolution.js +58 -0
- package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
- package/dist/adapters/cursor/dispatcher-generation.js +5 -0
- package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
- package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
- package/dist/adapters/cursor/hook-router.d.ts +23 -0
- package/dist/adapters/cursor/hook-router.js +266 -0
- package/dist/adapters/cursor/hooks.d.ts +5 -0
- package/dist/adapters/cursor/hooks.js +115 -3
- package/dist/adapters/cursor/routing-layout.d.ts +5 -0
- package/dist/adapters/cursor/routing-layout.js +29 -0
- package/dist/adapters/cursor/runtime-entry.d.ts +18 -0
- package/dist/adapters/cursor/runtime-entry.js +135 -34
- package/dist/adapters/layouts/scope.d.ts +5 -0
- package/dist/adapters/layouts/scope.js +71 -0
- package/dist/adapters/shared/gate-runtime.js +27 -3
- package/dist/adapters/shared/repo-root.js +20 -1
- package/dist/bundle/claude-runtime.mjs +2660 -1951
- package/dist/bundle/codex-runtime.mjs +2680 -1953
- package/dist/bundle/cursor-dispatcher.mjs +443 -0
- package/dist/bundle/cursor-runtime.mjs +5638 -4697
- package/dist/cli.js +33 -3
- package/dist/commands/doctor.js +215 -16
- package/dist/commands/health-snapshot.d.ts +3 -0
- package/dist/commands/health-snapshot.js +61 -0
- package/dist/commands/recovery-checkpoints.d.ts +3 -3
- package/dist/commands/report.js +14 -0
- package/dist/commands/status.js +15 -0
- package/dist/commands/where.d.ts +4 -0
- package/dist/commands/where.js +52 -0
- package/dist/config-io.js +11 -2
- package/dist/core/approval-repo-lookup.d.ts +16 -0
- package/dist/core/approval-repo-lookup.js +48 -0
- package/dist/core/audit-io.d.ts +1 -1
- package/dist/core/audit-io.js +1 -1
- package/dist/core/audit-legacy-archive.js +43 -25
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +7 -0
- package/dist/core/audit-serialize.d.ts +3 -0
- package/dist/core/audit-serialize.js +39 -3
- package/dist/core/audit-summary.d.ts +9 -0
- package/dist/core/audit-summary.js +58 -1
- package/dist/core/audit-types.d.ts +4 -0
- package/dist/core/classify-tool.js +16 -8
- package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
- package/dist/core/effect-ir/argv-delegate.js +42 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
- package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/augment.js +97 -0
- package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
- package/dist/core/effect-ir/shell-lower/context.js +1 -0
- package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
- package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
- package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
- package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/segment.js +82 -0
- package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
- package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
- package/dist/core/effect-ir/shell-lower.d.ts +2 -7
- package/dist/core/effect-ir/shell-lower.js +122 -1511
- package/dist/core/glob.js +32 -16
- package/dist/core/integrity.d.ts +2 -2
- package/dist/core/integrity.js +52 -12
- package/dist/core/replay-scrub.d.ts +3 -0
- package/dist/core/replay-scrub.js +30 -3
- package/dist/core/shell-substitution.js +2 -2
- package/dist/core/shell-tokenizer.d.ts +28 -0
- package/dist/core/shell-tokenizer.js +111 -29
- package/dist/core/shell-unparseable.js +3 -1
- package/dist/core/verdict/docker-compose-run.d.ts +18 -0
- package/dist/core/verdict/docker-compose-run.js +136 -0
- package/dist/core/verdict/launcher-resolve.js +28 -28
- package/dist/core/verdict/parser.d.ts +3 -0
- package/dist/core/verdict/parser.js +23 -40
- package/dist/core/verdict/recursive-invocation.d.ts +20 -0
- package/dist/core/verdict/recursive-invocation.js +224 -0
- package/dist/defaults.d.ts +7 -3
- package/dist/defaults.js +35 -21
- package/dist/installer/bootstrap.d.ts +1 -0
- package/dist/installer/bootstrap.js +2 -2
- package/dist/installer/runtime-artifacts.js +43 -13
- package/dist/installer/scope-config.d.ts +2 -2
- package/dist/installer.d.ts +10 -1
- package/dist/installer.js +83 -7
- package/dist/node-resolution.d.ts +1 -0
- package/dist/node-resolution.js +61 -0
- package/dist/templates.d.ts +7 -4
- package/dist/templates.js +42 -4
- package/dist/types.d.ts +35 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
- package/skills/belay/SKILL.md +5 -0
- 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 {
|
|
4
|
-
import {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
195
|
-
if (
|
|
196
|
-
|
|
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
|
-
|
|
175
|
+
'dynamic_shell_evaluation',
|
|
200
176
|
]));
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
-
}
|