From f26cc608727b556566d1891ac8dc8b6e9a242a07 Mon Sep 17 00:00:00 2001 From: M00N7682 Date: Thu, 5 Feb 2026 13:01:12 +0900 Subject: [PATCH] Tests: add test coverage for security/windows-acl.ts Adds comprehensive unit tests for Windows ACL inspection utilities: - resolveWindowsUserPrincipal: username resolution with fallback - parseIcaclsOutput: icacls output parsing - summarizeWindowsAcl: ACL entry classification (trusted/world/group) - inspectWindowsAcl: async ACL inspection with mocked exec - formatWindowsAclSummary: summary string formatting - formatIcaclsResetCommand: reset command string generation - createIcaclsResetCommand: structured reset command generation All 26 tests passing. Co-Authored-By: Claude Opus 4.5 --- src/security/windows-acl.test.ts | 336 +++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 src/security/windows-acl.test.ts diff --git a/src/security/windows-acl.test.ts b/src/security/windows-acl.test.ts new file mode 100644 index 000000000..cf9c3d919 --- /dev/null +++ b/src/security/windows-acl.test.ts @@ -0,0 +1,336 @@ +import { describe, expect, it, vi } from "vitest"; +import { + createIcaclsResetCommand, + formatIcaclsResetCommand, + formatWindowsAclSummary, + inspectWindowsAcl, + parseIcaclsOutput, + resolveWindowsUserPrincipal, + summarizeWindowsAcl, + type WindowsAclEntry, + type WindowsAclSummary, +} from "./windows-acl.js"; + +describe("windows-acl", () => { + describe("resolveWindowsUserPrincipal", () => { + it("returns DOMAIN\\USERNAME when both are present", () => { + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + expect(resolveWindowsUserPrincipal(env)).toBe("WORKGROUP\\TestUser"); + }); + + it("returns just USERNAME when USERDOMAIN is not present", () => { + const env = { USERNAME: "TestUser" }; + expect(resolveWindowsUserPrincipal(env)).toBe("TestUser"); + }); + + it("trims whitespace from values", () => { + const env = { USERNAME: " TestUser ", USERDOMAIN: " WORKGROUP " }; + expect(resolveWindowsUserPrincipal(env)).toBe("WORKGROUP\\TestUser"); + }); + + it("falls back to os.userInfo when USERNAME is empty", () => { + // When USERNAME env is empty, falls back to os.userInfo().username + const env = { USERNAME: "", USERDOMAIN: "WORKGROUP" }; + const result = resolveWindowsUserPrincipal(env); + // Should return a username (from os.userInfo fallback) with WORKGROUP domain + expect(result).toContain("WORKGROUP\\"); + }); + }); + + describe("parseIcaclsOutput", () => { + it("parses standard icacls output", () => { + const output = `C:\\test\\file.txt BUILTIN\\Administrators:(F) + NT AUTHORITY\\SYSTEM:(F) + WORKGROUP\\TestUser:(R) + +Successfully processed 1 files`; + const entries = parseIcaclsOutput(output, "C:\\test\\file.txt"); + expect(entries).toHaveLength(3); + expect(entries[0]).toEqual({ + principal: "BUILTIN\\Administrators", + rights: ["F"], + rawRights: "(F)", + canRead: true, + canWrite: true, + }); + }); + + it("parses entries with inheritance flags", () => { + const output = `C:\\test\\dir BUILTIN\\Users:(OI)(CI)(R)`; + const entries = parseIcaclsOutput(output, "C:\\test\\dir"); + expect(entries).toHaveLength(1); + expect(entries[0].rights).toEqual(["R"]); + expect(entries[0].canRead).toBe(true); + expect(entries[0].canWrite).toBe(false); + }); + + it("filters out DENY entries", () => { + const output = `C:\\test\\file.txt BUILTIN\\Users:(DENY)(W) + BUILTIN\\Administrators:(F)`; + const entries = parseIcaclsOutput(output, "C:\\test\\file.txt"); + expect(entries).toHaveLength(1); + expect(entries[0].principal).toBe("BUILTIN\\Administrators"); + }); + + it("skips status messages", () => { + const output = `Successfully processed 1 files + Failed processing 0 files + No mapping between account names`; + const entries = parseIcaclsOutput(output, "C:\\test\\file.txt"); + expect(entries).toHaveLength(0); + }); + + it("handles quoted target paths", () => { + const output = `"C:\\path with spaces\\file.txt" BUILTIN\\Administrators:(F)`; + const entries = parseIcaclsOutput(output, "C:\\path with spaces\\file.txt"); + expect(entries).toHaveLength(1); + }); + + it("detects write permissions correctly", () => { + // F = Full control (read + write) + // M = Modify (read + write) + // W = Write + // D = Delete (considered write) + // R = Read only + const testCases = [ + { rights: "(F)", canWrite: true, canRead: true }, + { rights: "(M)", canWrite: true, canRead: true }, + { rights: "(W)", canWrite: true, canRead: false }, + { rights: "(D)", canWrite: true, canRead: false }, + { rights: "(R)", canWrite: false, canRead: true }, + { rights: "(RX)", canWrite: false, canRead: true }, + ]; + + for (const tc of testCases) { + const output = `C:\\test\\file.txt BUILTIN\\Users:${tc.rights}`; + const entries = parseIcaclsOutput(output, "C:\\test\\file.txt"); + expect(entries[0].canWrite).toBe(tc.canWrite); + expect(entries[0].canRead).toBe(tc.canRead); + } + }); + }); + + describe("summarizeWindowsAcl", () => { + it("classifies trusted principals", () => { + const entries: WindowsAclEntry[] = [ + { + principal: "NT AUTHORITY\\SYSTEM", + rights: ["F"], + rawRights: "(F)", + canRead: true, + canWrite: true, + }, + { + principal: "BUILTIN\\Administrators", + rights: ["F"], + rawRights: "(F)", + canRead: true, + canWrite: true, + }, + ]; + const summary = summarizeWindowsAcl(entries); + expect(summary.trusted).toHaveLength(2); + expect(summary.untrustedWorld).toHaveLength(0); + expect(summary.untrustedGroup).toHaveLength(0); + }); + + it("classifies world principals", () => { + const entries: WindowsAclEntry[] = [ + { + principal: "Everyone", + rights: ["R"], + rawRights: "(R)", + canRead: true, + canWrite: false, + }, + { + principal: "BUILTIN\\Users", + rights: ["R"], + rawRights: "(R)", + canRead: true, + canWrite: false, + }, + ]; + const summary = summarizeWindowsAcl(entries); + expect(summary.trusted).toHaveLength(0); + expect(summary.untrustedWorld).toHaveLength(2); + expect(summary.untrustedGroup).toHaveLength(0); + }); + + it("classifies current user as trusted", () => { + const entries: WindowsAclEntry[] = [ + { + principal: "WORKGROUP\\TestUser", + rights: ["F"], + rawRights: "(F)", + canRead: true, + canWrite: true, + }, + ]; + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + const summary = summarizeWindowsAcl(entries, env); + expect(summary.trusted).toHaveLength(1); + }); + + it("classifies unknown principals as group", () => { + const entries: WindowsAclEntry[] = [ + { + principal: "DOMAIN\\SomeOtherUser", + rights: ["R"], + rawRights: "(R)", + canRead: true, + canWrite: false, + }, + ]; + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + const summary = summarizeWindowsAcl(entries, env); + expect(summary.untrustedGroup).toHaveLength(1); + }); + }); + + describe("inspectWindowsAcl", () => { + it("returns parsed ACL entries on success", async () => { + const mockExec = vi.fn().mockResolvedValue({ + stdout: `C:\\test\\file.txt BUILTIN\\Administrators:(F) + NT AUTHORITY\\SYSTEM:(F)`, + stderr: "", + }); + + const result = await inspectWindowsAcl("C:\\test\\file.txt", { exec: mockExec }); + expect(result.ok).toBe(true); + expect(result.entries).toHaveLength(2); + expect(mockExec).toHaveBeenCalledWith("icacls", ["C:\\test\\file.txt"]); + }); + + it("returns error state on exec failure", async () => { + const mockExec = vi.fn().mockRejectedValue(new Error("icacls not found")); + + const result = await inspectWindowsAcl("C:\\test\\file.txt", { exec: mockExec }); + expect(result.ok).toBe(false); + expect(result.error).toContain("icacls not found"); + expect(result.entries).toHaveLength(0); + }); + + it("combines stdout and stderr for parsing", async () => { + const mockExec = vi.fn().mockResolvedValue({ + stdout: "C:\\test\\file.txt BUILTIN\\Administrators:(F)", + stderr: "C:\\test\\file.txt NT AUTHORITY\\SYSTEM:(F)", + }); + + const result = await inspectWindowsAcl("C:\\test\\file.txt", { exec: mockExec }); + expect(result.ok).toBe(true); + expect(result.entries).toHaveLength(2); + }); + }); + + describe("formatWindowsAclSummary", () => { + it("returns 'unknown' for failed summary", () => { + const summary: WindowsAclSummary = { + ok: false, + entries: [], + trusted: [], + untrustedWorld: [], + untrustedGroup: [], + error: "icacls failed", + }; + expect(formatWindowsAclSummary(summary)).toBe("unknown"); + }); + + it("returns 'trusted-only' when no untrusted entries", () => { + const summary: WindowsAclSummary = { + ok: true, + entries: [], + trusted: [ + { + principal: "BUILTIN\\Administrators", + rights: ["F"], + rawRights: "(F)", + canRead: true, + canWrite: true, + }, + ], + untrustedWorld: [], + untrustedGroup: [], + }; + expect(formatWindowsAclSummary(summary)).toBe("trusted-only"); + }); + + it("formats untrusted entries", () => { + const summary: WindowsAclSummary = { + ok: true, + entries: [], + trusted: [], + untrustedWorld: [ + { + principal: "Everyone", + rights: ["R"], + rawRights: "(R)", + canRead: true, + canWrite: false, + }, + ], + untrustedGroup: [ + { + principal: "DOMAIN\\OtherUser", + rights: ["M"], + rawRights: "(M)", + canRead: true, + canWrite: true, + }, + ], + }; + const result = formatWindowsAclSummary(summary); + expect(result).toBe("Everyone:(R), DOMAIN\\OtherUser:(M)"); + }); + }); + + describe("formatIcaclsResetCommand", () => { + it("generates command for files", () => { + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + const result = formatIcaclsResetCommand("C:\\test\\file.txt", { isDir: false, env }); + expect(result).toBe( + 'icacls "C:\\test\\file.txt" /inheritance:r /grant:r "WORKGROUP\\TestUser:F" /grant:r "SYSTEM:F"', + ); + }); + + it("generates command for directories with inheritance flags", () => { + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + const result = formatIcaclsResetCommand("C:\\test\\dir", { isDir: true, env }); + expect(result).toContain("(OI)(CI)F"); + }); + + it("uses system username when env is empty (falls back to os.userInfo)", () => { + // When env is empty, resolveWindowsUserPrincipal falls back to os.userInfo().username + const result = formatIcaclsResetCommand("C:\\test\\file.txt", { isDir: false, env: {} }); + // Should contain the actual system username from os.userInfo + expect(result).toContain(":F"); + expect(result).toContain("/grant:r"); + }); + }); + + describe("createIcaclsResetCommand", () => { + it("returns structured command object", () => { + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + const result = createIcaclsResetCommand("C:\\test\\file.txt", { isDir: false, env }); + expect(result).not.toBeNull(); + expect(result?.command).toBe("icacls"); + expect(result?.args).toContain("C:\\test\\file.txt"); + expect(result?.args).toContain("/inheritance:r"); + }); + + it("returns command with system username when env is empty (falls back to os.userInfo)", () => { + // When env is empty, resolveWindowsUserPrincipal falls back to os.userInfo().username + const result = createIcaclsResetCommand("C:\\test\\file.txt", { isDir: false, env: {} }); + // Should return a valid command using the system username + expect(result).not.toBeNull(); + expect(result?.command).toBe("icacls"); + }); + + it("includes display string matching formatIcaclsResetCommand", () => { + const env = { USERNAME: "TestUser", USERDOMAIN: "WORKGROUP" }; + const result = createIcaclsResetCommand("C:\\test\\file.txt", { isDir: false, env }); + const expected = formatIcaclsResetCommand("C:\\test\\file.txt", { isDir: false, env }); + expect(result?.display).toBe(expected); + }); + }); +});