UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

706 lines (705 loc) 29.3 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js"; import { t as expandHomePrefix } from "./home-dir-BjcCg_IW.js"; import { a as validateSafeBinArgv, c as DEFAULT_SAFE_BINS, l as SAFE_BIN_PROFILES, n as isTrustedSafeBinPath } from "./exec-safe-bin-trust-Do7n3gvF.js"; import { c as resolveExecutableTrustPath, d as resolveExecutionTargetTrustPath, h as resolvePolicyTargetTrustPath, l as resolveExecutionTargetCandidatePath, m as resolvePolicyTargetResolution, p as resolvePolicyTargetCandidatePath, s as resolveCommandResolutionFromArgv, t as matchAllowlist, u as resolveExecutionTargetResolution, y as resolveExecWrapperTrustPlan } from "./exec-command-resolution-CqhcxBz-.js"; import { O as normalizeExecutableToken, _ as resolvePowerShellInlineCommandMatch, c as isShellWrapperExecutable, f as POSIX_INLINE_COMMAND_FLAGS, g as resolveInlineCommandMatch, m as isPowerShellInlineFileCommandFlag, n as POWERSHELL_WRAPPERS, r as extractBindableShellWrapperInlineCommand, x as unwrapDispatchWrappersForResolution, y as isDispatchWrapperExecutable } from "./shell-wrapper-resolution-CFL_Vekh.js"; import "./exec-wrapper-resolution-VZBsT_RG.js"; import { a as detectInlineEvalArgv, l as isInterpreterLikeAllowlistPattern } from "./risks-DRxJ1pW5.js"; import { c as splitCommandChain, l as splitCommandChainWithOperators, n as analyzeShellCommand, o as isWindowsPlatform } from "./exec-approvals-analysis-B4N2JXzl.js"; import path from "node:path"; //#region src/infra/exec-safe-builtins.ts const DEFAULT_SAFE_BUILTINS = new Set([ ":", "cd", "false", "pwd", "test", "true" ]); /** Returns true when a parsed POSIX shell segment is one of the closed safe builtin forms. */ function isSafeBuiltinSegment(params) { if (isWindowsPlatform(params.platform ?? process.platform)) return false; const head = params.segment.argv[0]?.trim().toLowerCase(); if (!head) return false; if (head === "[") return params.segment.argv.at(-1) === "]"; return DEFAULT_SAFE_BUILTINS.has(head); } //#endregion //#region src/infra/exec-approvals-allowlist.ts function hasShellLineContinuation(command) { return /\\(?:\r\n|\n|\r)/.test(command); } function normalizeSafeBins(entries) { if (!Array.isArray(entries)) return /* @__PURE__ */ new Set(); const normalized = entries.map((entry) => normalizeLowercaseStringOrEmpty(entry)).filter((entry) => entry.length > 0); return new Set(normalized); } function resolveSafeBins(entries) { if (entries === void 0) return normalizeSafeBins(DEFAULT_SAFE_BINS); return normalizeSafeBins(entries ?? []); } function isSafeBinUsage(params) { if (isWindowsPlatform(params.platform ?? process.platform)) return false; if (params.safeBins.size === 0) return false; const resolution = params.resolution; const execName = normalizeOptionalLowercaseString(resolution?.executableName); if (!execName) return false; if (!params.safeBins.has(execName)) return false; const trustPath = resolveExecutableTrustPath(resolution); if (!trustPath) return false; if (!(params.isTrustedSafeBinPathFn ?? isTrustedSafeBinPath)({ resolvedPath: trustPath, trustedDirs: params.trustedSafeBinDirs })) return false; const argv = params.argv.slice(1); const profile = (params.safeBinProfiles ?? SAFE_BIN_PROFILES)[execName]; if (!profile) return false; return validateSafeBinArgv(argv, profile, { binName: execName }); } function isPathScopedExecutableToken(token) { return token.includes("/") || token.includes("\\"); } function pickExecAllowlistContext(params) { return { allowlist: params.allowlist, safeBins: params.safeBins, safeBinProfiles: params.safeBinProfiles, cwd: params.cwd, env: params.env, platform: params.platform, trustedSafeBinDirs: params.trustedSafeBinDirs, skillBins: params.skillBins, autoAllowSkills: params.autoAllowSkills, allowShellBuiltins: params.allowShellBuiltins }; } function normalizeSkillBinName(value) { const trimmed = normalizeOptionalLowercaseString(value); return trimmed && trimmed.length > 0 ? trimmed : null; } function normalizeSkillBinResolvedPath(value) { const trimmed = normalizeOptionalString(value); if (!trimmed) return null; const resolved = path.resolve(trimmed); if (process.platform === "win32") return normalizeLowercaseStringOrEmpty(resolved.replace(/\\/g, "/")); return resolved; } function buildSkillBinTrustIndex(entries) { const trustByName = /* @__PURE__ */ new Map(); if (!entries || entries.length === 0) return trustByName; for (const entry of entries) { const name = normalizeSkillBinName(entry.name); const resolvedPath = normalizeSkillBinResolvedPath(entry.resolvedPath); if (!name || !resolvedPath) continue; const paths = trustByName.get(name) ?? /* @__PURE__ */ new Set(); paths.add(resolvedPath); trustByName.set(name, paths); } return trustByName; } function isSkillAutoAllowedSegment(params) { if (!params.allowSkills) return false; const resolution = params.segment.resolution; const execution = resolveExecutionTargetResolution(resolution); const trustPath = resolveExecutionTargetTrustPath(resolution); if (!execution?.resolvedPath || !trustPath) return false; const rawExecutable = execution.rawExecutable?.trim() ?? ""; if (!rawExecutable || isPathScopedExecutableToken(rawExecutable)) return false; const executableName = normalizeSkillBinName(execution.executableName); const resolvedPath = normalizeSkillBinResolvedPath(trustPath); if (!executableName || !resolvedPath) return false; return Boolean(params.skillBinTrust.get(executableName)?.has(resolvedPath)); } const MAX_SHELL_WRAPPER_INLINE_EVAL_DEPTH = 3; function matchExecutableAllowlistForSegment(params) { if (params.isPositionalCarrierInvocation) return null; const match = matchAllowlist(params.allowlist, params.candidateResolution, params.effectiveArgv, params.platform); const hasBoundArgPattern = typeof match?.argPattern === "string" && match.argPattern.trim().length > 0; const isBareWildcardMatch = match?.pattern?.trim() === "*" && !hasBoundArgPattern; if (params.allowlistTargetIsExecutionTarget && (params.inlineCommand !== null || params.isShellWrapperInvocation && params.effectiveArgv.length > 1) && !hasBoundArgPattern && !isBareWildcardMatch) return null; return match; } function executableResolutionsReferToSameTarget(left, right) { if (!left || !right) return false; return left.rawExecutable === right.rawExecutable && left.resolvedPath === right.resolvedPath && left.resolvedRealPath === right.resolvedRealPath && left.executableName === right.executableName; } function resolveShellWrapperScriptArgv(params) { const scriptBase = normalizeLowercaseStringOrEmpty(path.basename(params.shellScriptCandidatePath)); const cwdBase = params.cwd && params.cwd.trim() ? params.cwd.trim() : process.cwd(); const resolveArgPath = (a) => path.isAbsolute(a) ? a : path.resolve(cwdBase, a); let idx = params.effectiveArgv.findIndex((a) => resolveArgPath(a) === params.shellScriptCandidatePath); if (idx === -1) idx = params.effectiveArgv.findIndex((a) => normalizeLowercaseStringOrEmpty(path.basename(a)) === scriptBase); const scriptArgs = idx !== -1 ? params.effectiveArgv.slice(idx + 1) : []; return [params.shellScriptCandidatePath, ...scriptArgs]; } function resolvePowerShellFileScriptArgv(params) { const argv = resolveSegmentSourceArgv(params.segment); if (!Array.isArray(argv) || argv.length < 3) return null; const wrapperName = normalizeExecutableToken(argv[0] ?? ""); if (!POWERSHELL_WRAPPERS.has(wrapperName)) return null; const match = resolvePowerShellInlineCommandMatch(argv); if (match.valueTokenIndex === null || !match.command) return null; if (!isPowerShellInlineFileCommandFlag(argv[match.valueTokenIndex - 1] ?? "")) return null; const scriptToken = argv[match.valueTokenIndex]?.trim(); if (!scriptToken) return null; const expanded = scriptToken.startsWith("~") ? expandHomePrefix(scriptToken) : scriptToken; const base = params.cwd && params.cwd.trim().length > 0 ? params.cwd : process.cwd(); return [path.isAbsolute(expanded) ? expanded : path.resolve(base, expanded), ...argv.slice(match.valueTokenIndex + 1)]; } function resolveSegmentSourceArgv(segment) { const sourceArgv = segment.sourceArgv; if (!Array.isArray(sourceArgv) || sourceArgv.length === 0) return segment.argv; const segmentExecutable = normalizeExecutableToken(segment.argv[0] ?? ""); if (!segmentExecutable) return segment.argv; if (normalizeExecutableToken(sourceArgv[0] ?? "") === segmentExecutable) return sourceArgv; const unwrappedSourceArgv = unwrapDispatchWrappersForResolution(sourceArgv); return normalizeExecutableToken(unwrappedSourceArgv[0] ?? "") === segmentExecutable ? unwrappedSourceArgv : segment.argv; } function resolveSegmentAllowlistMatch(params) { const effectiveArgv = params.segment.resolution?.effectiveArgv && params.segment.resolution.effectiveArgv.length > 0 ? params.segment.resolution.effectiveArgv : params.segment.argv; const allowlistSegment = effectiveArgv === params.segment.argv ? params.segment : { ...params.segment, argv: effectiveArgv }; const executableResolution = resolvePolicyTargetResolution(params.segment.resolution); const executionResolution = resolveExecutionTargetResolution(params.segment.resolution); const candidatePath = resolvePolicyTargetCandidatePath(params.segment.resolution, params.context.cwd); const trustPath = resolvePolicyTargetTrustPath(params.segment.resolution, params.context.cwd); const candidateResolution = candidatePath && executableResolution ? { ...executableResolution, resolvedPath: candidatePath, resolvedRealPath: trustPath } : executableResolution; const inlineCommand = extractBindableShellWrapperInlineCommand(allowlistSegment.argv); const powerShellFileScriptArgv = resolvePowerShellFileScriptArgv({ segment: allowlistSegment, cwd: params.context.cwd }); const isShellWrapperInvocation = isShellWrapperSegment(allowlistSegment); const isPositionalCarrierInvocation = inlineCommand !== null && isDirectShellPositionalCarrierInvocation(inlineCommand); const executableMatch = matchExecutableAllowlistForSegment({ allowlist: params.context.allowlist, candidateResolution, effectiveArgv, platform: params.context.platform, inlineCommand, isShellWrapperInvocation, isPositionalCarrierInvocation, allowlistTargetIsExecutionTarget: executableResolutionsReferToSameTarget(executableResolution, executionResolution) }); const shellPositionalArgvCandidatePath = inlineCommand !== null ? resolveShellWrapperPositionalArgvCandidatePath({ segment: allowlistSegment, cwd: params.context.cwd, env: params.context.env, platform: params.context.platform }) : void 0; const shellPositionalArgvMatch = shellPositionalArgvCandidatePath ? matchAllowlist(params.context.allowlist, { rawExecutable: shellPositionalArgvCandidatePath, resolvedPath: shellPositionalArgvCandidatePath, resolvedRealPath: resolveCandidateTrustPath(shellPositionalArgvCandidatePath), executableName: path.basename(shellPositionalArgvCandidatePath) }, void 0, params.context.platform) : null; const shellScriptCandidatePath = powerShellFileScriptArgv?.[0] ?? (inlineCommand === null ? resolveShellWrapperScriptCandidatePath({ segment: allowlistSegment, cwd: params.context.cwd }) : void 0); const shellScriptArgv = shellScriptCandidatePath ? powerShellFileScriptArgv ?? resolveShellWrapperScriptArgv({ shellScriptCandidatePath, effectiveArgv, cwd: params.context.cwd }) : null; const shellScriptMatch = shellScriptCandidatePath && shellScriptArgv ? matchAllowlist(params.context.allowlist, { rawExecutable: shellScriptCandidatePath, resolvedPath: shellScriptCandidatePath, resolvedRealPath: resolveCandidateTrustPath(shellScriptCandidatePath), executableName: path.basename(shellScriptCandidatePath) }, shellScriptArgv, params.context.platform) : null; return { effectiveArgv, inlineCommand: powerShellFileScriptArgv ? null : inlineCommand, match: executableMatch ?? shellPositionalArgvMatch ?? shellScriptMatch }; } function resolveSegmentSatisfaction(params) { if (params.match) return "allowlist"; if (isSafeBinUsage({ argv: params.effectiveArgv, resolution: resolveExecutionTargetResolution(params.segment.resolution), safeBins: params.context.safeBins, safeBinProfiles: params.context.safeBinProfiles, platform: params.context.platform, trustedSafeBinDirs: params.context.trustedSafeBinDirs })) return "safeBins"; if (params.context.allowShellBuiltins === true && isSafeBuiltinSegment({ segment: params.segment, platform: params.context.platform })) return "safeBuiltins"; return isSkillAutoAllowedSegment({ segment: params.segment, allowSkills: params.allowSkills, skillBinTrust: params.skillBinTrust }) ? "skills" : null; } function resolveInlineCommandFallback(params) { if (params.by !== null || !params.inlineCommand) return null; if (!isWindowsPlatform(params.context.platform)) { if (hasShellLineContinuation(params.inlineCommand)) return null; const inlineChainParts = splitCommandChain(params.inlineCommand); if (!inlineChainParts || inlineChainParts.length <= 1) return null; return evaluateShellWrapperInlineCommands({ inlineCommands: inlineChainParts, context: params.context, inlineDepth: params.inlineDepth + 1 }); } return evaluateShellWrapperInlineCommand({ inlineCommand: params.inlineCommand, context: params.context, inlineDepth: params.inlineDepth + 1 }); } function evaluateShellWrapperInlineCommands(params) { if (params.inlineDepth >= MAX_SHELL_WRAPPER_INLINE_EVAL_DEPTH) return null; const matches = []; const segmentSatisfiedBy = []; for (const inlineCommand of params.inlineCommands) { const analysis = analyzeShellCommand({ command: inlineCommand, cwd: params.context.cwd, env: params.context.env, platform: params.context.platform }); if (!analysis.ok) return null; const result = evaluateSegments(analysis.segments, params.context, params.inlineDepth); if (!result.satisfied) return null; matches.push(...result.matches); segmentSatisfiedBy.push(...result.segmentSatisfiedBy); } return { matches, satisfiedBy: segmentSatisfiedBy.some((entry) => entry === "safeBins" || entry === "inlineChain") ? "inlineChain" : "allowlist" }; } function evaluateShellWrapperInlineCommand(params) { if (params.inlineDepth >= MAX_SHELL_WRAPPER_INLINE_EVAL_DEPTH) return null; if (hasShellLineContinuation(params.inlineCommand)) return null; const analysis = analyzeShellCommand({ command: params.inlineCommand, cwd: params.context.cwd, env: params.context.env, platform: params.context.platform }); if (!analysis.ok || analysis.segments.length === 0) return null; const matches = []; for (const group of resolveAnalysisSegmentGroups(analysis)) { const result = evaluateSegments(group, params.context, params.inlineDepth); if (!result.satisfied) return null; matches.push(...result.matches); } return { matches, satisfiedBy: "allowlist" }; } function evaluateSegments(segments, params, inlineDepth = 0) { const matches = []; const skillBinTrust = buildSkillBinTrustIndex(params.skillBins); const allowSkills = params.autoAllowSkills === true && skillBinTrust.size > 0; const segmentAllowlistEntries = []; const segmentSatisfiedBy = []; return { satisfied: segments.every((segment) => { if (segment.resolution?.policyBlocked === true) { segmentAllowlistEntries.push(null); segmentSatisfiedBy.push(null); return false; } const { effectiveArgv, inlineCommand, match } = resolveSegmentAllowlistMatch({ segment, context: params }); if (match) matches.push(match); segmentAllowlistEntries.push(match ?? null); const by = resolveSegmentSatisfaction({ match, segment, effectiveArgv, context: params, allowSkills, skillBinTrust }); const inlineResult = resolveInlineCommandFallback({ by, inlineCommand, context: params, inlineDepth }); if (inlineResult) { matches.push(...inlineResult.matches); segmentSatisfiedBy.push(inlineResult.satisfiedBy); return true; } segmentSatisfiedBy.push(by); return Boolean(by); }), matches, segmentAllowlistEntries, segmentSatisfiedBy }; } function resolveAnalysisSegmentGroups(analysis) { if (analysis.chains) return analysis.chains; return [analysis.segments]; } function evaluateExecAllowlist(params) { const allowlistMatches = []; const segmentAllowlistEntries = []; const segmentSatisfiedBy = []; if (!params.analysis.ok || params.analysis.segments.length === 0) return { allowlistSatisfied: false, allowlistMatches, segmentAllowlistEntries, segmentSatisfiedBy }; const allowlistContext = pickExecAllowlistContext(params); const hasChains = Boolean(params.analysis.chains); for (const group of resolveAnalysisSegmentGroups(params.analysis)) { const result = evaluateSegments(group, allowlistContext); if (!result.satisfied) { if (!hasChains) return { allowlistSatisfied: false, allowlistMatches: result.matches, segmentAllowlistEntries: result.segmentAllowlistEntries, segmentSatisfiedBy: result.segmentSatisfiedBy }; return { allowlistSatisfied: false, allowlistMatches: [], segmentAllowlistEntries: [], segmentSatisfiedBy: [] }; } allowlistMatches.push(...result.matches); segmentAllowlistEntries.push(...result.segmentAllowlistEntries); segmentSatisfiedBy.push(...result.segmentSatisfiedBy); } return { allowlistSatisfied: true, allowlistMatches, segmentAllowlistEntries, segmentSatisfiedBy }; } function hasSegmentExecutableMatch(segment, predicate) { const execution = resolveExecutionTargetResolution(segment.resolution); const candidates = [ execution?.executableName, execution?.rawExecutable, segment.argv[0] ]; for (const candidate of candidates) { if (typeof candidate !== "string") continue; const trimmed = candidate.trim(); if (!trimmed) continue; if (predicate(trimmed)) return true; } return false; } function isShellWrapperSegment(segment) { return hasSegmentExecutableMatch(segment, isShellWrapperExecutable); } const SHELL_WRAPPER_OPTIONS_WITH_VALUE = new Set([ "-c", "--command", "-o", "-O", "+O" ]); const SHELL_WRAPPER_DISQUALIFYING_SCRIPT_OPTIONS = [ "--rcfile", "--init-file", "--startup-file" ]; function hasDisqualifyingShellWrapperScriptOption(token) { return SHELL_WRAPPER_DISQUALIFYING_SCRIPT_OPTIONS.some((option) => token === option || token.startsWith(`${option}=`)); } const POWERSHELL_OPTIONS_WITH_VALUE_RE = /^-(?:executionpolicy|ep|windowstyle|w|workingdirectory|wd|inputformat|outputformat|settingsfile|configurationfile|version|v|psconsolefile|pscf|encodedcommand|en|enc|encodedarguments|ea)$/i; function resolveShellWrapperScriptCandidatePath(params) { if (!isShellWrapperSegment(params.segment)) return; const argv = params.segment.argv; if (!Array.isArray(argv) || argv.length < 2) return; const wrapperName = normalizeExecutableToken(argv[0] ?? ""); const isPowerShell = POWERSHELL_WRAPPERS.has(wrapperName); let idx = 1; while (idx < argv.length) { const token = argv[idx]?.trim() ?? ""; if (!token) { idx += 1; continue; } if (token === "--") { idx += 1; break; } if (token === "-c" || token === "--command") return; if (!isPowerShell && /^-[^-]*c[^-]*$/i.test(token)) return; if (token === "-s" || !isPowerShell && /^-[^-]*s[^-]*$/i.test(token)) return; if (hasDisqualifyingShellWrapperScriptOption(token)) return; if (SHELL_WRAPPER_OPTIONS_WITH_VALUE.has(token)) { idx += 2; continue; } if (isPowerShell && POWERSHELL_OPTIONS_WITH_VALUE_RE.test(token)) { idx += 2; continue; } if (token.startsWith("-") || token.startsWith("+")) { idx += 1; continue; } break; } const scriptToken = argv[idx]?.trim(); if (!scriptToken) return; if (path.isAbsolute(scriptToken)) return scriptToken; const expanded = scriptToken.startsWith("~") ? expandHomePrefix(scriptToken) : scriptToken; const base = params.cwd && params.cwd.trim().length > 0 ? params.cwd : process.cwd(); return path.resolve(base, expanded); } function resolveShellWrapperPositionalArgvCandidatePath(params) { if (!isShellWrapperSegment(params.segment)) return; const argv = params.segment.argv; if (!Array.isArray(argv) || argv.length < 4) return; const wrapper = normalizeExecutableToken(argv[0] ?? ""); if (![ "ash", "bash", "dash", "fish", "ksh", "sh", "zsh" ].includes(wrapper)) return; const inlineMatch = resolveInlineCommandMatch(argv, POSIX_INLINE_COMMAND_FLAGS, { allowCombinedC: true }); if (inlineMatch.valueTokenIndex === null || !inlineMatch.command) return; if (!isDirectShellPositionalCarrierInvocation(inlineMatch.command)) return; const carriedExecutable = argv.slice(inlineMatch.valueTokenIndex + 1).map((token) => token.trim()).find((token) => token.length > 0); if (!carriedExecutable) return; const carriedName = normalizeExecutableToken(carriedExecutable); if (isDispatchWrapperExecutable(carriedName) || isShellWrapperExecutable(carriedName)) return; return resolveExecutionTargetCandidatePath(resolveCommandResolutionFromArgv([carriedExecutable], params.cwd, params.env, params.platform ?? void 0), params.cwd); } function isDirectShellPositionalCarrierInvocation(command) { const trimmed = command.trim(); if (trimmed.length === 0) return false; const shellWhitespace = String.raw`[^\S\r\n]+`; const positionalZero = String.raw`(?:\$(?:0|\{0\})|"\$(?:0|\{0\})")`; const positionalArg = String.raw`(?:\$(?:[@*]|[1-9]|\{[@*1-9]\})|"\$(?:[@*]|[1-9]|\{[@*1-9]\})")`; return new RegExp(`^(?:exec${shellWhitespace}(?:--${shellWhitespace})?)?${positionalZero}(?:${shellWhitespace}${positionalArg})*$`, "u").test(trimmed); } function escapeRegExpLiteral(input) { return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function buildScriptArgPatternFromArgv(argv, scriptPath, cwd, platform) { if (!isWindowsPlatform(platform ?? process.platform)) return; const scriptBase = normalizeLowercaseStringOrEmpty(path.basename(scriptPath)); const base = cwd && cwd.trim() ? cwd.trim() : process.cwd(); const resolveArgPath = (arg) => path.isAbsolute(arg) ? arg : path.resolve(base, arg); let scriptIdx = argv.findIndex((arg) => resolveArgPath(arg) === scriptPath); if (scriptIdx === -1) scriptIdx = argv.findIndex((arg) => normalizeLowercaseStringOrEmpty(path.basename(arg)) === scriptBase); const normalized = (scriptIdx !== -1 ? argv.slice(scriptIdx + 1) : []).map((a) => a.replace(/\//g, "\\")); if (normalized.length === 0) return "^\0\0$"; return `^${normalized.map(escapeRegExpLiteral).join("\0")}\x00$`; } function buildArgPatternFromArgv(argv, platform) { if (!isWindowsPlatform(platform ?? process.platform)) return; const normalized = argv.slice(1).map((a) => a.replace(/\//g, "\\")); if (normalized.length === 0) return "^\0\0$"; return `^${escapeRegExpLiteral(normalized.join("\0"))}\x00$`; } function addAllowAlwaysPattern(out, pattern, argPattern) { if (!out.some((p) => p.pattern === pattern && (p.argPattern ?? void 0) === (argPattern ?? void 0))) out.push({ pattern, argPattern }); } function resolveCandidateTrustPath(candidatePath) { if (!candidatePath) return; return resolveExecutableTrustPath({ rawExecutable: candidatePath, resolvedPath: candidatePath, executableName: path.basename(candidatePath) }); } function collectAllowAlwaysPatterns(params) { if (params.depth >= 3) return; const trustPlan = resolveExecWrapperTrustPlan(params.segment.argv, void 0, params.platform ?? void 0); if (trustPlan.policyBlocked) return; const segment = trustPlan.argv === params.segment.argv ? params.segment : { raw: trustPlan.argv.join(" "), argv: trustPlan.argv, sourceArgv: params.segment.sourceArgv, resolution: resolveCommandResolutionFromArgv(trustPlan.argv, params.cwd, params.env, params.platform ?? void 0) }; const candidatePath = resolveExecutionTargetTrustPath(segment.resolution, params.cwd); if (!candidatePath) return; if (isInterpreterLikeAllowlistPattern(candidatePath)) { const effectiveArgv = segment.resolution?.effectiveArgv ?? segment.argv; if (params.strictInlineEval !== true || detectInlineEvalArgv(effectiveArgv) !== null) return; } if (!trustPlan.shellWrapperExecutable) { const argPattern = buildArgPatternFromArgv(segment.argv, params.platform); addAllowAlwaysPattern(params.out, candidatePath, argPattern); return; } const powerShellFileScriptArgv = resolvePowerShellFileScriptArgv({ segment, cwd: params.cwd }); const inlineCommand = powerShellFileScriptArgv ? null : trustPlan.shellInlineCommand; const positionalArgvPath = inlineCommand !== null ? resolveShellWrapperPositionalArgvCandidatePath({ segment, cwd: params.cwd, env: params.env, platform: params.platform }) : void 0; if (positionalArgvPath) { addAllowAlwaysPattern(params.out, resolveCandidateTrustPath(positionalArgvPath) ?? positionalArgvPath); return; } if (!inlineCommand) { const scriptPath = powerShellFileScriptArgv?.[0] ?? resolveShellWrapperScriptCandidatePath({ segment, cwd: params.cwd }); if (scriptPath) { const scriptTrustPath = resolveCandidateTrustPath(scriptPath) ?? scriptPath; const argPattern = buildScriptArgPatternFromArgv(powerShellFileScriptArgv ?? params.segment.argv, scriptPath, params.cwd, params.platform); addAllowAlwaysPattern(params.out, scriptTrustPath, argPattern); } return; } const nested = analyzeShellCommand({ command: inlineCommand, cwd: params.cwd, env: params.env, platform: params.platform }); if (!nested.ok) return; for (const nestedSegment of nested.segments) collectAllowAlwaysPatterns({ segment: nestedSegment, cwd: params.cwd, env: params.env, platform: params.platform, strictInlineEval: params.strictInlineEval, depth: params.depth + 1, out: params.out }); } /** * Derive persisted allowlist patterns for an "allow always" decision. * When a command is wrapped in a shell (for example `zsh -lc "<cmd>"`), * persist the inner executable(s) rather than the shell binary. */ function resolveAllowAlwaysPatternEntries(params) { const patterns = []; for (const segment of params.segments) collectAllowAlwaysPatterns({ segment, cwd: params.cwd, env: params.env, platform: params.platform, strictInlineEval: params.strictInlineEval, depth: 0, out: patterns }); return patterns; } function resolveAllowAlwaysPatterns(params) { return resolveAllowAlwaysPatternEntries(params).map((pattern) => pattern.pattern); } /** * Evaluates allowlist for shell commands (including &&, ||, ;) and returns analysis metadata. */ function evaluateShellAllowlist(params) { const allowlistContext = { ...pickExecAllowlistContext(params), allowShellBuiltins: true }; const analysisFailure = () => ({ analysisOk: false, allowlistSatisfied: false, allowlistMatches: [], segments: [], segmentAllowlistEntries: [], segmentSatisfiedBy: [] }); if (hasShellLineContinuation(params.command)) return analysisFailure(); const chainParts = isWindowsPlatform(params.platform) ? null : splitCommandChainWithOperators(params.command); if (!chainParts) { const analysis = analyzeShellCommand({ command: params.command, cwd: params.cwd, env: params.env, platform: params.platform }); if (!analysis.ok) return analysisFailure(); const evaluation = evaluateExecAllowlist({ analysis, ...allowlistContext }); return { analysisOk: true, allowlistSatisfied: evaluation.allowlistSatisfied, allowlistMatches: evaluation.allowlistMatches, segments: analysis.segments, segmentAllowlistEntries: evaluation.segmentAllowlistEntries, segmentSatisfiedBy: evaluation.segmentSatisfiedBy }; } const chainEvaluations = chainParts.map(({ part }) => { const analysis = analyzeShellCommand({ command: part, cwd: params.cwd, env: params.env, platform: params.platform }); if (!analysis.ok) return null; return { analysis, evaluation: evaluateExecAllowlist({ analysis, ...allowlistContext }) }; }); if (chainEvaluations.some((entry) => entry === null)) return analysisFailure(); const finalizedEvaluations = chainEvaluations; const allowlistMatches = []; const segments = []; const segmentAllowlistEntries = []; const segmentSatisfiedBy = []; for (const { analysis, evaluation } of finalizedEvaluations) { segments.push(...analysis.segments); allowlistMatches.push(...evaluation.allowlistMatches); segmentAllowlistEntries.push(...evaluation.segmentAllowlistEntries); segmentSatisfiedBy.push(...evaluation.segmentSatisfiedBy); if (!evaluation.allowlistSatisfied) return { analysisOk: true, allowlistSatisfied: false, allowlistMatches, segments, segmentAllowlistEntries, segmentSatisfiedBy }; } return { analysisOk: true, allowlistSatisfied: true, allowlistMatches, segments, segmentAllowlistEntries, segmentSatisfiedBy }; } //#endregion export { resolveAllowAlwaysPatternEntries as a, normalizeSafeBins as i, evaluateShellAllowlist as n, resolveAllowAlwaysPatterns as o, isSafeBinUsage as r, resolveSafeBins as s, evaluateExecAllowlist as t };