Files
openclaw/src/infra/exec-wrapper-trust-plan.ts
Peter Steinberger 21d480ed92 fix(infra): preserve blocked dispatch policy target
# Conflicts:
#	CHANGELOG.md
2026-03-23 19:04:04 -07:00

142 lines
4.0 KiB
TypeScript

import {
MAX_DISPATCH_WRAPPER_DEPTH,
resolveDispatchWrapperTrustPlan,
unwrapKnownDispatchWrapperInvocation,
} from "./dispatch-wrapper-resolution.js";
import {
extractShellWrapperInlineCommand,
isShellWrapperExecutable,
unwrapKnownShellMultiplexerInvocation,
} from "./shell-wrapper-resolution.js";
export type ExecWrapperTrustPlan = {
argv: string[];
policyArgv: string[];
wrapperChain: string[];
policyBlocked: boolean;
blockedWrapper?: string;
shellWrapperExecutable: boolean;
shellInlineCommand: string | null;
};
function blockedExecWrapperTrustPlan(params: {
argv: string[];
policyArgv?: string[];
wrapperChain: string[];
blockedWrapper: string;
}): ExecWrapperTrustPlan {
return {
argv: params.argv,
policyArgv: params.policyArgv ?? params.argv,
wrapperChain: params.wrapperChain,
policyBlocked: true,
blockedWrapper: params.blockedWrapper,
shellWrapperExecutable: false,
shellInlineCommand: null,
};
}
function finalizeExecWrapperTrustPlan(
argv: string[],
policyArgv: string[],
wrapperChain: string[],
policyBlocked: boolean,
blockedWrapper?: string,
): ExecWrapperTrustPlan {
const rawExecutable = argv[0]?.trim() ?? "";
const shellWrapperExecutable =
!policyBlocked && rawExecutable.length > 0 && isShellWrapperExecutable(rawExecutable);
return {
argv,
policyArgv,
wrapperChain,
policyBlocked,
blockedWrapper,
shellWrapperExecutable,
shellInlineCommand: shellWrapperExecutable ? extractShellWrapperInlineCommand(argv) : null,
};
}
export function resolveExecWrapperTrustPlan(
argv: string[],
maxDepth = MAX_DISPATCH_WRAPPER_DEPTH,
): ExecWrapperTrustPlan {
let current = argv;
let policyArgv = argv;
let sawShellMultiplexer = false;
const wrapperChain: string[] = [];
for (let depth = 0; depth < maxDepth; depth += 1) {
const dispatchPlan = resolveDispatchWrapperTrustPlan(current, maxDepth - wrapperChain.length);
if (dispatchPlan.policyBlocked) {
return blockedExecWrapperTrustPlan({
argv: dispatchPlan.argv,
policyArgv: dispatchPlan.argv,
wrapperChain,
blockedWrapper: dispatchPlan.blockedWrapper ?? current[0] ?? "unknown",
});
}
if (dispatchPlan.wrappers.length > 0) {
wrapperChain.push(...dispatchPlan.wrappers);
current = dispatchPlan.argv;
if (!sawShellMultiplexer) {
policyArgv = current;
}
if (wrapperChain.length >= maxDepth) {
break;
}
continue;
}
const shellMultiplexerUnwrap = unwrapKnownShellMultiplexerInvocation(current);
if (shellMultiplexerUnwrap.kind === "blocked") {
return blockedExecWrapperTrustPlan({
argv: current,
policyArgv,
wrapperChain,
blockedWrapper: shellMultiplexerUnwrap.wrapper,
});
}
if (shellMultiplexerUnwrap.kind === "unwrapped") {
wrapperChain.push(shellMultiplexerUnwrap.wrapper);
if (!sawShellMultiplexer) {
// Preserve the real executable target for trust checks.
policyArgv = current;
sawShellMultiplexer = true;
}
current = shellMultiplexerUnwrap.argv;
if (wrapperChain.length >= maxDepth) {
break;
}
continue;
}
break;
}
if (wrapperChain.length >= maxDepth) {
const dispatchOverflow = unwrapKnownDispatchWrapperInvocation(current);
if (dispatchOverflow.kind === "blocked" || dispatchOverflow.kind === "unwrapped") {
return blockedExecWrapperTrustPlan({
argv: current,
policyArgv,
wrapperChain,
blockedWrapper: dispatchOverflow.wrapper,
});
}
const shellMultiplexerOverflow = unwrapKnownShellMultiplexerInvocation(current);
if (
shellMultiplexerOverflow.kind === "blocked" ||
shellMultiplexerOverflow.kind === "unwrapped"
) {
return blockedExecWrapperTrustPlan({
argv: current,
policyArgv,
wrapperChain,
blockedWrapper: shellMultiplexerOverflow.wrapper,
});
}
}
return finalizeExecWrapperTrustPlan(current, policyArgv, wrapperChain, false);
}