mirror of https://github.com/openclaw/openclaw.git
158 lines
4.2 KiB
TypeScript
158 lines
4.2 KiB
TypeScript
import { resolveSafeBins } from "./exec-approvals-allowlist.js";
|
|
import {
|
|
normalizeSafeBinProfileFixtures,
|
|
resolveSafeBinProfiles,
|
|
type SafeBinProfile,
|
|
type SafeBinProfileFixture,
|
|
type SafeBinProfileFixtures,
|
|
} from "./exec-safe-bin-policy.js";
|
|
import {
|
|
getTrustedSafeBinDirs,
|
|
listWritableExplicitTrustedSafeBinDirs,
|
|
normalizeTrustedSafeBinDirs,
|
|
type WritableTrustedSafeBinDir,
|
|
} from "./exec-safe-bin-trust.js";
|
|
|
|
export type ExecSafeBinConfigScope = {
|
|
safeBins?: string[] | null;
|
|
safeBinProfiles?: SafeBinProfileFixtures | null;
|
|
safeBinTrustedDirs?: string[] | null;
|
|
};
|
|
|
|
const INTERPRETER_LIKE_SAFE_BINS = new Set([
|
|
"ash",
|
|
"bash",
|
|
"busybox",
|
|
"bun",
|
|
"cmd",
|
|
"cmd.exe",
|
|
"cscript",
|
|
"dash",
|
|
"deno",
|
|
"fish",
|
|
"ksh",
|
|
"lua",
|
|
"node",
|
|
"nodejs",
|
|
"perl",
|
|
"php",
|
|
"powershell",
|
|
"powershell.exe",
|
|
"pypy",
|
|
"pwsh",
|
|
"pwsh.exe",
|
|
"python",
|
|
"python2",
|
|
"python3",
|
|
"ruby",
|
|
"sh",
|
|
"toybox",
|
|
"wscript",
|
|
"zsh",
|
|
]);
|
|
|
|
const INTERPRETER_LIKE_PATTERNS = [
|
|
/^python\d+(?:\.\d+)?$/,
|
|
/^ruby\d+(?:\.\d+)?$/,
|
|
/^perl\d+(?:\.\d+)?$/,
|
|
/^php\d+(?:\.\d+)?$/,
|
|
/^node\d+(?:\.\d+)?$/,
|
|
];
|
|
|
|
function normalizeSafeBinName(raw: string): string {
|
|
const trimmed = raw.trim().toLowerCase();
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
const tail = trimmed.split(/[\\/]/).at(-1);
|
|
return tail ?? trimmed;
|
|
}
|
|
|
|
export function isInterpreterLikeSafeBin(raw: string): boolean {
|
|
const normalized = normalizeSafeBinName(raw);
|
|
if (!normalized) {
|
|
return false;
|
|
}
|
|
if (INTERPRETER_LIKE_SAFE_BINS.has(normalized)) {
|
|
return true;
|
|
}
|
|
return INTERPRETER_LIKE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
}
|
|
|
|
export function listInterpreterLikeSafeBins(entries: Iterable<string>): string[] {
|
|
return Array.from(entries)
|
|
.map((entry) => normalizeSafeBinName(entry))
|
|
.filter((entry) => entry.length > 0 && isInterpreterLikeSafeBin(entry))
|
|
.toSorted();
|
|
}
|
|
|
|
export function resolveMergedSafeBinProfileFixtures(params: {
|
|
global?: ExecSafeBinConfigScope | null;
|
|
local?: ExecSafeBinConfigScope | null;
|
|
}): Record<string, SafeBinProfileFixture> | undefined {
|
|
const global = normalizeSafeBinProfileFixtures(params.global?.safeBinProfiles);
|
|
const local = normalizeSafeBinProfileFixtures(params.local?.safeBinProfiles);
|
|
if (Object.keys(global).length === 0 && Object.keys(local).length === 0) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
...global,
|
|
...local,
|
|
};
|
|
}
|
|
|
|
export function resolveExecSafeBinRuntimePolicy(params: {
|
|
global?: ExecSafeBinConfigScope | null;
|
|
local?: ExecSafeBinConfigScope | null;
|
|
onWarning?: (message: string) => void;
|
|
}): {
|
|
safeBins: Set<string>;
|
|
safeBinProfiles: Readonly<Record<string, SafeBinProfile>>;
|
|
trustedSafeBinDirs: ReadonlySet<string>;
|
|
unprofiledSafeBins: string[];
|
|
unprofiledInterpreterSafeBins: string[];
|
|
writableTrustedSafeBinDirs: ReadonlyArray<WritableTrustedSafeBinDir>;
|
|
} {
|
|
const safeBins = resolveSafeBins(params.local?.safeBins ?? params.global?.safeBins);
|
|
const safeBinProfiles = resolveSafeBinProfiles(
|
|
resolveMergedSafeBinProfileFixtures({
|
|
global: params.global,
|
|
local: params.local,
|
|
}),
|
|
);
|
|
const unprofiledSafeBins = Array.from(safeBins)
|
|
.filter((entry) => !safeBinProfiles[entry])
|
|
.toSorted();
|
|
const explicitTrustedSafeBinDirs = [
|
|
...normalizeTrustedSafeBinDirs(params.global?.safeBinTrustedDirs),
|
|
...normalizeTrustedSafeBinDirs(params.local?.safeBinTrustedDirs),
|
|
];
|
|
const trustedSafeBinDirs = getTrustedSafeBinDirs({
|
|
extraDirs: explicitTrustedSafeBinDirs,
|
|
});
|
|
const writableTrustedSafeBinDirs = listWritableExplicitTrustedSafeBinDirs(
|
|
explicitTrustedSafeBinDirs,
|
|
);
|
|
if (params.onWarning) {
|
|
for (const hit of writableTrustedSafeBinDirs) {
|
|
const scope =
|
|
hit.worldWritable || hit.groupWritable
|
|
? hit.worldWritable
|
|
? "world-writable"
|
|
: "group-writable"
|
|
: "writable";
|
|
params.onWarning(
|
|
`exec: safeBinTrustedDirs includes ${scope} directory '${hit.dir}'; remove trust or tighten permissions (for example chmod 755).`,
|
|
);
|
|
}
|
|
}
|
|
return {
|
|
safeBins,
|
|
safeBinProfiles,
|
|
trustedSafeBinDirs,
|
|
unprofiledSafeBins,
|
|
unprofiledInterpreterSafeBins: listInterpreterLikeSafeBins(unprofiledSafeBins),
|
|
writableTrustedSafeBinDirs,
|
|
};
|
|
}
|