mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
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:
@@ -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.
|
||||
|
||||
47
patches/@openclaw__fs-safe@0.2.2.patch
Normal file
47
patches/@openclaw__fs-safe@0.2.2.patch
Normal 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
5
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
})(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user