Files
openclaw/src/infra/exec-safe-bin-semantics.ts
2026-03-22 17:28:04 +00:00

60 lines
1.9 KiB
TypeScript

export type SafeBinSemanticValidationParams = {
binName?: string;
positional: readonly string[];
};
type SafeBinSemanticRule = {
validate?: (params: SafeBinSemanticValidationParams) => boolean;
configWarning?: string;
};
const JQ_ENV_FILTER_PATTERN = /(^|[^.$A-Za-z0-9_])env([^A-Za-z0-9_]|$)/;
const SAFE_BIN_SEMANTIC_RULES: Readonly<Record<string, SafeBinSemanticRule>> = {
jq: {
validate: ({ positional }) => !positional.some((token) => JQ_ENV_FILTER_PATTERN.test(token)),
configWarning:
"jq supports broad jq programs and builtins (for example `env`), so prefer explicit allowlist entries or approval-gated runs instead of safeBins.",
},
};
export function normalizeSafeBinName(raw: string): string {
const trimmed = raw.trim().toLowerCase();
if (!trimmed) {
return "";
}
const tail = trimmed.split(/[\\/]/).at(-1);
const normalized = tail ?? trimmed;
return normalized.replace(/\.(?:exe|cmd|bat|com)$/i, "");
}
export function getSafeBinSemanticRule(binName?: string): SafeBinSemanticRule | undefined {
const normalized = typeof binName === "string" ? normalizeSafeBinName(binName) : "";
return normalized ? SAFE_BIN_SEMANTIC_RULES[normalized] : undefined;
}
export function validateSafeBinSemantics(params: SafeBinSemanticValidationParams): boolean {
return getSafeBinSemanticRule(params.binName)?.validate?.(params) ?? true;
}
export function listRiskyConfiguredSafeBins(entries: Iterable<string>): Array<{
bin: string;
warning: string;
}> {
const hits = new Map<string, string>();
for (const entry of entries) {
const normalized = normalizeSafeBinName(entry);
if (!normalized || hits.has(normalized)) {
continue;
}
const warning = getSafeBinSemanticRule(normalized)?.configWarning;
if (!warning) {
continue;
}
hits.set(normalized, warning);
}
return Array.from(hits.entries())
.map(([bin, warning]) => ({ bin, warning }))
.toSorted((a, b) => a.bin.localeCompare(b.bin));
}