fix(exec): harden safe-bin trust and add explicit trusted dirs

This commit is contained in:
Peter Steinberger
2026-02-22 22:42:29 +01:00
parent 08fb38f729
commit 64b273a71c
18 changed files with 123 additions and 55 deletions

View File

@@ -11,16 +11,13 @@ const DEFAULT_SAFE_BIN_TRUSTED_DIRS = [
];
type TrustedSafeBinDirsParams = {
pathEnv?: string | null;
delimiter?: string;
baseDirs?: readonly string[];
extraDirs?: readonly string[];
};
type TrustedSafeBinPathParams = {
resolvedPath: string;
trustedDirs?: ReadonlySet<string>;
pathEnv?: string | null;
delimiter?: string;
};
type TrustedSafeBinCache = {
@@ -29,7 +26,6 @@ type TrustedSafeBinCache = {
};
let trustedSafeBinCache: TrustedSafeBinCache | null = null;
const STARTUP_PATH_ENV = process.env.PATH ?? process.env.Path ?? "";
function normalizeTrustedDir(value: string): string | null {
const trimmed = value.trim();
@@ -39,64 +35,54 @@ function normalizeTrustedDir(value: string): string | null {
return path.resolve(trimmed);
}
function buildTrustedSafeBinCacheKey(pathEnv: string, delimiter: string): string {
return `${delimiter}\u0000${pathEnv}`;
function buildTrustedSafeBinCacheKey(params: {
baseDirs: readonly string[];
extraDirs: readonly string[];
}): string {
return `${params.baseDirs.join("\u0001")}\u0000${params.extraDirs.join("\u0001")}`;
}
export function buildTrustedSafeBinDirs(params: TrustedSafeBinDirsParams = {}): Set<string> {
const delimiter = params.delimiter ?? path.delimiter;
const pathEnv = params.pathEnv ?? "";
const baseDirs = params.baseDirs ?? DEFAULT_SAFE_BIN_TRUSTED_DIRS;
const extraDirs = params.extraDirs ?? [];
const trusted = new Set<string>();
for (const entry of baseDirs) {
// Trust is explicit only. Do not derive from PATH, which is user/environment controlled.
for (const entry of [...baseDirs, ...extraDirs]) {
const normalized = normalizeTrustedDir(entry);
if (normalized) {
trusted.add(normalized);
}
}
const pathEntries = pathEnv
.split(delimiter)
.map((entry) => normalizeTrustedDir(entry))
.filter((entry): entry is string => Boolean(entry));
for (const entry of pathEntries) {
trusted.add(entry);
}
return trusted;
}
export function getTrustedSafeBinDirs(
params: {
pathEnv?: string | null;
delimiter?: string;
baseDirs?: readonly string[];
extraDirs?: readonly string[];
refresh?: boolean;
} = {},
): Set<string> {
const delimiter = params.delimiter ?? path.delimiter;
const pathEnv = params.pathEnv ?? STARTUP_PATH_ENV;
const key = buildTrustedSafeBinCacheKey(pathEnv, delimiter);
const baseDirs = params.baseDirs ?? DEFAULT_SAFE_BIN_TRUSTED_DIRS;
const extraDirs = params.extraDirs ?? [];
const key = buildTrustedSafeBinCacheKey({ baseDirs, extraDirs });
if (!params.refresh && trustedSafeBinCache?.key === key) {
return trustedSafeBinCache.dirs;
}
const dirs = buildTrustedSafeBinDirs({
pathEnv,
delimiter,
baseDirs,
extraDirs,
});
trustedSafeBinCache = { key, dirs };
return dirs;
}
export function isTrustedSafeBinPath(params: TrustedSafeBinPathParams): boolean {
const trustedDirs =
params.trustedDirs ??
getTrustedSafeBinDirs({
pathEnv: params.pathEnv,
delimiter: params.delimiter,
});
const trustedDirs = params.trustedDirs ?? getTrustedSafeBinDirs();
const resolvedDir = path.dirname(path.resolve(params.resolvedPath));
return trustedDirs.has(resolvedDir);
}