Skip to content

Commit da0e245

Browse files
HOYALIMdvrshil
andauthored
fix(security): avoid prototype-chain account path checks (#34982)
Merged via squash. Prepared head SHA: f89cc6a Co-authored-by: HOYALIM <[email protected]> Co-authored-by: dvrshil <[email protected]> Reviewed-by: @dvrshil
1 parent 809f951 commit da0e245

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
1717

1818
### Fixes
1919

20+
- Security/audit account handling: avoid prototype-chain account IDs in audit validation by using own-property checks for `accounts`. (#34982) Thanks @HOYALIM.
2021
- Agents/session usage tracking: preserve accumulated usage metadata on embedded Pi runner error exits so failed turns still update session `totalTokens` from real usage instead of stale prior values. (#34275) thanks @RealKai42.
2122
- Nodes/system.run approval hardening: use explicit argv-mutation signaling when regenerating prepared `rawCommand`, and cover the `system.run.prepare -> system.run` handoff so direct PATH-based `nodes.run` commands no longer fail with `rawCommand does not match command`. (#33137) thanks @Sid-Qin.
2223
- Models/custom provider headers: propagate `models.providers.<name>.headers` across inline, fallback, and registry-found model resolution so header-authenticated proxies consistently receive configured request headers. (#27490) thanks @Sid-Qin.

src/i18n/registry.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { describe, expect, it } from "vitest";
2-
import type { TranslationMap } from "../../ui/src/i18n/lib/types.ts";
32
import {
43
DEFAULT_LOCALE,
54
SUPPORTED_LOCALES,
65
loadLazyLocaleTranslation,
76
resolveNavigatorLocale,
87
} from "../../ui/src/i18n/lib/registry.ts";
8+
import type { TranslationMap } from "../../ui/src/i18n/lib/types.ts";
99

1010
function getNestedTranslation(map: TranslationMap | null, ...path: string[]): string | undefined {
1111
let value: string | TranslationMap | undefined = map ?? undefined;

src/security/audit-channel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function hasExplicitProviderAccountConfig(
108108
if (!accounts || typeof accounts !== "object") {
109109
return false;
110110
}
111-
return accountId in accounts;
111+
return Object.hasOwn(accounts, accountId);
112112
}
113113

114114
export async function collectChannelSecurityFindings(params: {

src/security/audit.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,6 +1998,51 @@ description: test skill
19981998
});
19991999
});
20002000

2001+
it("does not treat prototype properties as explicit Discord account config paths", async () => {
2002+
await withChannelSecurityStateDir(async () => {
2003+
const cfg: OpenClawConfig = {
2004+
channels: {
2005+
discord: {
2006+
enabled: true,
2007+
token: "t",
2008+
dangerouslyAllowNameMatching: true,
2009+
allowFrom: ["Alice#1234"],
2010+
accounts: {},
2011+
},
2012+
},
2013+
};
2014+
2015+
const pluginWithProtoDefaultAccount: ChannelPlugin = {
2016+
...discordPlugin,
2017+
config: {
2018+
...discordPlugin.config,
2019+
listAccountIds: () => [],
2020+
defaultAccountId: () => "toString",
2021+
},
2022+
};
2023+
2024+
const res = await runSecurityAudit({
2025+
config: cfg,
2026+
includeFilesystem: false,
2027+
includeChannelSecurity: true,
2028+
plugins: [pluginWithProtoDefaultAccount],
2029+
});
2030+
2031+
const dangerousMatchingFinding = res.findings.find(
2032+
(entry) => entry.checkId === "channels.discord.allowFrom.dangerous_name_matching_enabled",
2033+
);
2034+
expect(dangerousMatchingFinding).toBeDefined();
2035+
expect(dangerousMatchingFinding?.title).not.toContain("(account: toString)");
2036+
2037+
const nameBasedFinding = res.findings.find(
2038+
(entry) => entry.checkId === "channels.discord.allowFrom.name_based_entries",
2039+
);
2040+
expect(nameBasedFinding).toBeDefined();
2041+
expect(nameBasedFinding?.detail).toContain("channels.discord.allowFrom:Alice#1234");
2042+
expect(nameBasedFinding?.detail).not.toContain("channels.discord.accounts.toString");
2043+
});
2044+
});
2045+
20012046
it("audits name-based allowlists on non-default Discord accounts", async () => {
20022047
await withChannelSecurityStateDir(async () => {
20032048
const cfg: OpenClawConfig = {

0 commit comments

Comments
 (0)