97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
import path from "node:path";
|
|
|
|
const DEFAULT_SAFE_BIN_TRUSTED_DIRS = [
|
|
"/bin",
|
|
"/usr/bin",
|
|
"/usr/local/bin",
|
|
"/opt/homebrew/bin",
|
|
"/opt/local/bin",
|
|
"/snap/bin",
|
|
"/run/current-system/sw/bin",
|
|
];
|
|
|
|
type TrustedSafeBinDirsParams = {
|
|
baseDirs?: readonly string[];
|
|
extraDirs?: readonly string[];
|
|
};
|
|
|
|
type TrustedSafeBinPathParams = {
|
|
resolvedPath: string;
|
|
trustedDirs?: ReadonlySet<string>;
|
|
};
|
|
|
|
type TrustedSafeBinCache = {
|
|
key: string;
|
|
dirs: Set<string>;
|
|
};
|
|
|
|
let trustedSafeBinCache: TrustedSafeBinCache | null = null;
|
|
|
|
function normalizeTrustedDir(value: string): string | null {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return null;
|
|
}
|
|
return path.resolve(trimmed);
|
|
}
|
|
|
|
export function normalizeTrustedSafeBinDirs(entries?: readonly string[] | null): string[] {
|
|
if (!Array.isArray(entries)) {
|
|
return [];
|
|
}
|
|
const normalized = entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
return Array.from(new Set(normalized));
|
|
}
|
|
|
|
function resolveTrustedSafeBinDirs(entries: readonly string[]): string[] {
|
|
const resolved = entries
|
|
.map((entry) => normalizeTrustedDir(entry))
|
|
.filter((entry): entry is string => Boolean(entry));
|
|
return Array.from(new Set(resolved)).toSorted();
|
|
}
|
|
|
|
function buildTrustedSafeBinCacheKey(entries: readonly string[]): string {
|
|
return resolveTrustedSafeBinDirs(normalizeTrustedSafeBinDirs(entries)).join("\u0001");
|
|
}
|
|
|
|
export function buildTrustedSafeBinDirs(params: TrustedSafeBinDirsParams = {}): Set<string> {
|
|
const baseDirs = params.baseDirs ?? DEFAULT_SAFE_BIN_TRUSTED_DIRS;
|
|
const extraDirs = params.extraDirs ?? [];
|
|
// Trust is explicit only. Do not derive from PATH, which is user/environment controlled.
|
|
return new Set(
|
|
resolveTrustedSafeBinDirs([
|
|
...normalizeTrustedSafeBinDirs(baseDirs),
|
|
...normalizeTrustedSafeBinDirs(extraDirs),
|
|
]),
|
|
);
|
|
}
|
|
|
|
export function getTrustedSafeBinDirs(
|
|
params: {
|
|
baseDirs?: readonly string[];
|
|
extraDirs?: readonly string[];
|
|
refresh?: boolean;
|
|
} = {},
|
|
): Set<string> {
|
|
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({
|
|
baseDirs,
|
|
extraDirs,
|
|
});
|
|
trustedSafeBinCache = { key, dirs };
|
|
return dirs;
|
|
}
|
|
|
|
export function isTrustedSafeBinPath(params: TrustedSafeBinPathParams): boolean {
|
|
const trustedDirs = params.trustedDirs ?? getTrustedSafeBinDirs();
|
|
const resolvedDir = path.dirname(path.resolve(params.resolvedPath));
|
|
return trustedDirs.has(resolvedDir);
|
|
}
|