import os from "node:os"; import path from "node:path"; import { runExec } from "../process/exec.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
// Accept an optional leading * which icacls prefixes to SIDs when invoked with /sid // (e.g. *S-1-5-18 instead of S-1-5-18). const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i; const TRUSTED_SIDS = new Set([ "s-1-5-18", "s-1-5-32-544", "s-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464",
]); // SIDs for world-equivalent principals that icacls /sid emits as raw SIDs. // Without this list these would be classified as "group" instead of "world". // S-1-1-0 Everyone // S-1-5-11 Authenticated Users // S-1-5-32-545 BUILTIN\Users const WORLD_SIDS = new Set(["s-1-1-0", "s-1-5-11", "s-1-5-32-545"]); const STATUS_PREFIXES = [ "successfully processed", "processed", "failed processing", "no mapping between account names",
];
function buildTrustedPrincipals(env?: NodeJS.ProcessEnv): Set<string> { const trusted = new Set<string>(TRUSTED_BASE); const principal = resolveWindowsUserPrincipal(env); if (principal) {
trusted.add(normalize(principal)); const parts = principal.split("\\"); const userOnly = parts.at(-1); if (userOnly) {
trusted.add(normalize(userOnly));
}
} const userSid = normalizeSid(env?.USERSID ?? ""); // Guard: never add world-equivalent SIDs (Everyone, Authenticated Users, BUILTIN\\Users) // to the trusted set, even if USERSID is set to one of them by a malicious process. if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid)) {
trusted.add(userSid);
} return trusted;
}
if (SID_RE.test(normalized)) { // Strip the leading * that icacls /sid prefixes to SIDs before lookup. const sid = normalizeSid(normalized); // World-equivalent SIDs must be classified as "world", not "group", so // that callers applying world-write policies catch everyone/authenticated- // users entries the same way they would catch the human-readable names. if (WORLD_SIDS.has(sid)) { return"world";
} if (TRUSTED_SIDS.has(sid) || trustedPrincipals.has(sid)) { return"trusted";
} return"group";
}
async function resolveCurrentUserSid(
exec: ExecFn,
env?: NodeJS.ProcessEnv,
): Promise<string | null> { try { const { stdout, stderr } = await exec(resolveWindowsSystemCommand("whoami.exe", env), [ "/user", "/fo", "csv", "/nh",
]); const match = `${stdout}\n${stderr}`.match(/\*?S-\d+-\d+(?:-\d+)+/i); return match ? normalizeSid(match[0]) : null;
} catch (err) { // Log but do not propagate — SID resolution is best-effort. // Callers fall back to env-based resolution when this returns null.
console.warn("[windows-acl] resolveCurrentUserSid failed:", String(err)); // TODO: replace with a structured logger call once a lightweight per-module // logger is available; console.warn can be noisy on constrained Windows hosts // (e.g. strict output-capture environments or CI runners with limited stdio). returnnull;
}
}
export async function inspectWindowsAcl(
targetPath: string,
opts?: { env?: NodeJS.ProcessEnv; exec?: ExecFn },
): Promise<WindowsAclSummary> { const exec = opts?.exec ?? runExec; try { // /sid outputs security identifiers (e.g. *S-1-5-18) instead of locale- // dependent account names so the audit works correctly on non-English // Windows (Russian, Chinese, etc.) where icacls prints Cyrillic / CJK // characters that may be garbled when Node reads them in the wrong code // page. Fixes #35834. const { stdout, stderr } = await exec(resolveWindowsSystemCommand("icacls.exe", opts?.env), [
targetPath, "/sid",
]); const output = `${stdout}\n${stderr}`.trim(); const entries = parseIcaclsOutput(output, targetPath);
let effectiveEnv = opts?.env;
let { trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.