fix(security): classify broad Windows SIDs as world principals

Carry Windows ACL world-principal classification through @openclaw/fs-safe@0.2.2 so Anonymous Logon, Guests, Interactive, Network, and Local SID/principal variants are treated as world-equivalent in filesystem audit findings.

Also add regression coverage, changelog coverage, a narrow lint cleanup, and a UI test isolation fix needed by the current CI shard.

Co-authored-by: dwc <118101032587@njust.edu.cn>
This commit is contained in:
dwc1997
2026-05-14 04:19:02 +08:00
committed by GitHub
parent 6a23e26a27
commit cffae53b43
7 changed files with 157 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Security/Windows ACL audit: classify Anonymous Logon, Guests, Interactive, Local, and Network SIDs as world-equivalent principals so broadly writable paths stay critical instead of being downgraded to group-writable. Fixes #74350. (#74383) Thanks @dwc1997.
- Media-understanding: retry transient remote attachment fetch failures before audio or vision processing, so Discord voice notes are not lost after one network/CDN blip. Fixes #74316. Thanks @vyctorbrzezowski and @gabrielexito-stack.
- Control UI: order timestamped live stream and tool items before untimestamped history fallbacks, keeping chat history in visible time order. Fixes #80759. (#81016) Thanks @akrimm702.
- iMessage: stop sending visible `<media:image>` placeholder text for media-only native image sends while preserving the internal echo key that prevents self-echo duplicate replies. (#81209) Thanks @homer-byte.

View File

@@ -0,0 +1,47 @@
diff --git a/dist/permissions.js b/dist/permissions.js
index 67df110..445b04d 100644
--- a/dist/permissions.js
+++ b/dist/permissions.js
@@ -12,6 +12,15 @@ const WORLD_PRINCIPALS = new Set([
"builtin\\users",
"authenticated users",
"nt authority\\authenticated users",
+ "anonymous logon",
+ "nt authority\\anonymous logon",
+ "guests",
+ "builtin\\guests",
+ "interactive",
+ "nt authority\\interactive",
+ "network",
+ "nt authority\\network",
+ "local",
]);
const TRUSTED_BASE = new Set([
"nt authority\\system",
@@ -31,7 +40,16 @@ const TRUSTED_SIDS = new Set([
"s-1-5-32-544",
"s-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464",
]);
-const WORLD_SIDS = new Set(["s-1-1-0", "s-1-5-11", "s-1-5-32-545"]);
+const WORLD_SIDS = new Set([
+ "s-1-1-0",
+ "s-1-5-11",
+ "s-1-5-32-545",
+ "s-1-5-7",
+ "s-1-5-32-546",
+ "s-1-5-4",
+ "s-1-2-0",
+ "s-1-5-2",
+]);
const STATUS_PREFIXES = [
"successfully processed",
"processed",
@@ -202,6 +220,8 @@ function buildTrustedPrincipals(env) {
}
}
const userSid = normalizeSid(env?.USERSID ?? "");
+ // Guard: never add any world-equivalent SID to the trusted set, even if
+ // USERSID is set to one by a malicious process.
if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid)) {
trusted.add(userSid);
}

5
pnpm-lock.yaml generated
View File

@@ -35,6 +35,7 @@ packageExtensionsChecksum: sha256-oc/FAHkBR844HBfph1RZWyRMHHBpIFya25tyv5SGf6s=
patchedDependencies:
'@agentclientprotocol/claude-agent-acp@0.33.1': 3995624bb834cc60fea1461c7ef33f1fcdd8fb58b8f43f2f1490bc689f6e1be2
'@openclaw/fs-safe@0.2.2': 9068eacd1e87c234c1084b7c6855fce28d019506a084e711d137218c4d369313
baileys@7.0.0-rc11: a9aea1790d2c65b1ae543c77faca4119bbfb91ee3b6ca6c38d1cad4f5702ada2
importers:
@@ -103,7 +104,7 @@ importers:
version: 0.6.0
'@openclaw/fs-safe':
specifier: 0.2.2
version: 0.2.2
version: 0.2.2(patch_hash=9068eacd1e87c234c1084b7c6855fce28d019506a084e711d137218c4d369313)
'@slack/bolt':
specifier: 4.7.2
version: 4.7.2(@types/express@5.0.6)
@@ -9982,7 +9983,7 @@ snapshots:
'@openai/codex@0.130.0-win32-x64':
optional: true
'@openclaw/fs-safe@0.2.2':
'@openclaw/fs-safe@0.2.2(patch_hash=9068eacd1e87c234c1084b7c6855fce28d019506a084e711d137218c4d369313)':
optionalDependencies:
jszip: 3.10.1
tar: 7.5.15

View File

@@ -95,5 +95,6 @@ peerDependencyRules:
"prism-media>opusscript": "^0.0.8 || ^0.1.1"
patchedDependencies:
"@openclaw/fs-safe@0.2.2": "patches/@openclaw__fs-safe@0.2.2.patch"
"baileys@7.0.0-rc11": "patches/baileys@7.0.0-rc11.patch"
"@agentclientprotocol/claude-agent-acp@0.33.1": "patches/@agentclientprotocol__claude-agent-acp@0.33.1.patch"

View File

@@ -85,6 +85,42 @@ describe("security audit filesystem Windows findings", () => {
),
).toBe(true);
})(),
(async () => {
const tmp = await tempCases.makeTmpDir("win-anon-world");
const stateDir = path.join(tmp, "state");
await fs.mkdir(stateDir, { recursive: true });
const configPath = path.join(stateDir, "openclaw.json");
await fs.writeFile(configPath, "{}\n", "utf-8");
const findings = await collectFilesystemFindings({
stateDir,
configPath,
platform: "win32",
env: windowsAuditEnv,
execIcacls: async (_cmd: string, args: string[]) => {
const target = args[0];
if (target.endsWith(`${path.sep}state`)) {
return {
stdout: `${target} *S-1-5-18:(F)\n *S-1-5-7:(F)\n`,
stderr: "",
};
}
return {
stdout: `${target} *S-1-5-18:(F)\n DESKTOP-TEST\\Tester:(F)\n`,
stderr: "",
};
},
});
expect(
findings.some(
(finding) =>
finding.checkId === "fs.state_dir.perms_world_writable" &&
finding.severity === "critical",
),
).toBe(true);
expect(
findings.some((finding) => finding.checkId === "fs.state_dir.perms_group_writable"),
).toBe(false);
})(),
]);
});
});

View File

@@ -396,6 +396,71 @@ Successfully processed 1 files`;
],
expected: { untrustedWorld: 1 },
},
{
name: "Anonymous Logon SID (S-1-5-7) is world, not group",
entries: [
aclEntry({
principal: "*S-1-5-7",
rights: ["R"],
rawRights: "(R)",
canRead: true,
canWrite: false,
}),
],
expected: { untrustedWorld: 1 },
},
{
name: "BUILTIN\\\\Guests SID (S-1-5-32-546) is world, not group",
entries: [
aclEntry({
principal: "*S-1-5-32-546",
rights: ["R"],
rawRights: "(R)",
canRead: true,
canWrite: false,
}),
],
expected: { untrustedWorld: 1 },
},
{
name: "Interactive SID (S-1-5-4) is world, not group",
entries: [
aclEntry({
principal: "*S-1-5-4",
rights: ["R"],
rawRights: "(R)",
canRead: true,
canWrite: false,
}),
],
expected: { untrustedWorld: 1 },
},
{
name: "Local SID (S-1-2-0) is world, not group",
entries: [
aclEntry({
principal: "*S-1-2-0",
rights: ["R"],
rawRights: "(R)",
canRead: true,
canWrite: false,
}),
],
expected: { untrustedWorld: 1 },
},
{
name: "Network SID (S-1-5-2) is world, not group",
entries: [
aclEntry({
principal: "*S-1-5-2",
rights: ["R"],
rawRights: "(R)",
canRead: true,
canWrite: false,
}),
],
expected: { untrustedWorld: 1 },
},
] as const)("$name", ({ entries, env, expected }) => {
expectSummaryCounts(entries, expected, env);
});

View File

@@ -1107,10 +1107,10 @@ describe("grouped chat rendering", () => {
const objectUrl = "blob:managed-image";
vi.stubGlobal(
"URL",
Object.assign(URL, {
createObjectURL: vi.fn(() => objectUrl),
revokeObjectURL: vi.fn(),
}),
class extends URL {
static override createObjectURL = vi.fn(() => objectUrl);
static override revokeObjectURL = vi.fn();
},
);
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
const headers = init?.headers as Headers;