mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
test(security): isolate windows acl user fallback
This commit is contained in:
@@ -2,24 +2,8 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { WindowsAclEntry, WindowsAclSummary } from "./windows-acl.js";
|
||||
|
||||
const MOCK_USERNAME = "MockUser";
|
||||
const userInfoMock = vi.hoisted(() =>
|
||||
vi.fn(() => ({
|
||||
username: MOCK_USERNAME,
|
||||
uid: -1,
|
||||
gid: -1,
|
||||
shell: "C:\\Windows\\System32\\cmd.exe",
|
||||
homedir: "C:\\Users\\MockUser",
|
||||
})),
|
||||
);
|
||||
|
||||
vi.mock("node:os", async () => {
|
||||
const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks");
|
||||
return mockNodeBuiltinModule(
|
||||
() => vi.importActual<typeof import("node:os")>("node:os"),
|
||||
{ userInfo: userInfoMock as unknown as typeof import("node:os").userInfo },
|
||||
{ mirrorToDefault: true },
|
||||
);
|
||||
});
|
||||
const mockUserInfo = () => ({ username: MOCK_USERNAME });
|
||||
const emptyUserInfo = () => ({ username: "" });
|
||||
|
||||
let createIcaclsResetCommand: typeof import("./windows-acl.js").createIcaclsResetCommand;
|
||||
let formatIcaclsResetCommand: typeof import("./windows-acl.js").formatIcaclsResetCommand;
|
||||
@@ -125,7 +109,7 @@ describe("windows-acl", () => {
|
||||
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);
|
||||
const result = resolveWindowsUserPrincipal(env, mockUserInfo);
|
||||
// Should return a username (from os.userInfo fallback) with WORKGROUP domain
|
||||
expect(result).toBe(`WORKGROUP\\${MOCK_USERNAME}`);
|
||||
});
|
||||
@@ -653,6 +637,7 @@ Successfully processed 1 files`;
|
||||
const result = formatIcaclsResetCommand("C:\\test\\file.txt", {
|
||||
isDir: false,
|
||||
env: {},
|
||||
userInfo: mockUserInfo,
|
||||
});
|
||||
// Should contain the actual system username from os.userInfo
|
||||
expect(result).toContain(`"${MOCK_USERNAME}:F"`);
|
||||
@@ -678,6 +663,7 @@ Successfully processed 1 files`;
|
||||
const result = createIcaclsResetCommand("C:\\test\\file.txt", {
|
||||
isDir: false,
|
||||
env: {},
|
||||
userInfo: mockUserInfo,
|
||||
});
|
||||
// Should return a valid command using the system username
|
||||
expect(result).not.toBeNull();
|
||||
@@ -717,17 +703,10 @@ Successfully processed 1 files`;
|
||||
});
|
||||
|
||||
it("returns null when no username can be resolved (line 348)", () => {
|
||||
// Temporarily make os.userInfo().username empty so resolveWindowsUserPrincipal returns null
|
||||
userInfoMock.mockReturnValueOnce({
|
||||
username: "",
|
||||
uid: -1,
|
||||
gid: -1,
|
||||
shell: "",
|
||||
homedir: "",
|
||||
});
|
||||
const result = createIcaclsResetCommand("C:\\test\\file.txt", {
|
||||
isDir: false,
|
||||
env: { USERNAME: "", USERDOMAIN: "" },
|
||||
userInfo: emptyUserInfo,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
@@ -22,6 +22,14 @@ export type WindowsAclSummary = {
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type WindowsUserInfoProvider = () => { username?: string | null };
|
||||
|
||||
export type IcaclsResetCommandOptions = {
|
||||
isDir: boolean;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
userInfo?: WindowsUserInfoProvider;
|
||||
};
|
||||
|
||||
const INHERIT_FLAGS = new Set(["I", "OI", "CI", "IO", "NP"]);
|
||||
const WORLD_PRINCIPALS = new Set([
|
||||
"everyone",
|
||||
@@ -66,14 +74,18 @@ const STATUS_PREFIXES = [
|
||||
];
|
||||
|
||||
const normalize = (value: string) => normalizeLowercaseStringOrEmpty(value);
|
||||
const defaultWindowsUserInfo: WindowsUserInfoProvider = () => os.userInfo();
|
||||
|
||||
function normalizeSid(value: string): string {
|
||||
const normalized = normalize(value);
|
||||
return normalized.startsWith("*") ? normalized.slice(1) : normalized;
|
||||
}
|
||||
|
||||
export function resolveWindowsUserPrincipal(env?: NodeJS.ProcessEnv): string | null {
|
||||
const username = env?.USERNAME?.trim() || os.userInfo().username?.trim();
|
||||
export function resolveWindowsUserPrincipal(
|
||||
env?: NodeJS.ProcessEnv,
|
||||
userInfo: WindowsUserInfoProvider = defaultWindowsUserInfo,
|
||||
): string | null {
|
||||
const username = env?.USERNAME?.trim() || userInfo().username?.trim();
|
||||
if (!username) {
|
||||
return null;
|
||||
}
|
||||
@@ -361,18 +373,18 @@ export function formatWindowsAclSummary(summary: WindowsAclSummary): string {
|
||||
|
||||
export function formatIcaclsResetCommand(
|
||||
targetPath: string,
|
||||
opts: { isDir: boolean; env?: NodeJS.ProcessEnv },
|
||||
opts: IcaclsResetCommandOptions,
|
||||
): string {
|
||||
const user = resolveWindowsUserPrincipal(opts.env) ?? "%USERNAME%";
|
||||
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo) ?? "%USERNAME%";
|
||||
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
||||
return `icacls "${targetPath}" /inheritance:r /grant:r "${user}:${grant}" /grant:r "*S-1-5-18:${grant}"`;
|
||||
}
|
||||
|
||||
export function createIcaclsResetCommand(
|
||||
targetPath: string,
|
||||
opts: { isDir: boolean; env?: NodeJS.ProcessEnv },
|
||||
opts: IcaclsResetCommandOptions,
|
||||
): { command: string; args: string[]; display: string } | null {
|
||||
const user = resolveWindowsUserPrincipal(opts.env);
|
||||
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user