import { resolveSafeBins } from "./exec-approvals-allowlist.js" ;
import {
normalizeSafeBinProfileFixtures,
resolveSafeBinProfiles,
type SafeBinProfile,
type SafeBinProfileFixture,
type SafeBinProfileFixtures,
} from "./exec-safe-bin-policy.js" ;
import { normalizeSafeBinName } from "./exec-safe-bin-semantics.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" ,
"awk" ,
"bash" ,
"busybox" ,
"bun" ,
"cmd" ,
"cmd.exe" ,
"cscript" ,
"dash" ,
"deno" ,
"fish" ,
"gawk" ,
"gsed" ,
"ksh" ,
"lua" ,
"mawk" ,
"nawk" ,
"node" ,
"nodejs" ,
"perl" ,
"php" ,
"powershell" ,
"powershell.exe" ,
"pypy" ,
"pwsh" ,
"pwsh.exe" ,
"python" ,
"python2" ,
"python3" ,
"ruby" ,
"sed" ,
"sh" ,
"toybox" ,
"wscript" ,
"zsh" ,
]);
const INTERPRETER_LIKE_PATTERNS = [
/^python\d+(?:\.\d+)?$/,
/^ruby\d+(?:\.\d+)?$/,
/^perl\d+(?:\.\d+)?$/,
/^php\d+(?:\.\d+)?$/,
/^node\d+(?:\.\d+)?$/,
];
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,
};
}
Messung V0.5 in Prozent C=100 H=92 G=95
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland