fix(security): avoid prototype-chain account path checks (#34982)

Merged via squash.

Prepared head SHA: f89cc6a649
Co-authored-by: HOYALIM <166576253+HOYALIM@users.noreply.github.com>
Co-authored-by: dvrshil <81693876+dvrshil@users.noreply.github.com>
Reviewed-by: @dvrshil
This commit is contained in:
Ho Lim
2026-03-04 17:38:09 -08:00
committed by GitHub
parent 809f9513ac
commit da0e245db6
4 changed files with 48 additions and 2 deletions

View File

@@ -1,11 +1,11 @@
import { describe, expect, it } from "vitest";
import type { TranslationMap } from "../../ui/src/i18n/lib/types.ts";
import {
DEFAULT_LOCALE,
SUPPORTED_LOCALES,
loadLazyLocaleTranslation,
resolveNavigatorLocale,
} from "../../ui/src/i18n/lib/registry.ts";
import type { TranslationMap } from "../../ui/src/i18n/lib/types.ts";
function getNestedTranslation(map: TranslationMap | null, ...path: string[]): string | undefined {
let value: string | TranslationMap | undefined = map ?? undefined;

View File

@@ -108,7 +108,7 @@ function hasExplicitProviderAccountConfig(
if (!accounts || typeof accounts !== "object") {
return false;
}
return accountId in accounts;
return Object.hasOwn(accounts, accountId);
}
export async function collectChannelSecurityFindings(params: {

View File

@@ -1998,6 +1998,51 @@ description: test skill
});
});
it("does not treat prototype properties as explicit Discord account config paths", async () => {
await withChannelSecurityStateDir(async () => {
const cfg: OpenClawConfig = {
channels: {
discord: {
enabled: true,
token: "t",
dangerouslyAllowNameMatching: true,
allowFrom: ["Alice#1234"],
accounts: {},
},
},
};
const pluginWithProtoDefaultAccount: ChannelPlugin = {
...discordPlugin,
config: {
...discordPlugin.config,
listAccountIds: () => [],
defaultAccountId: () => "toString",
},
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: true,
plugins: [pluginWithProtoDefaultAccount],
});
const dangerousMatchingFinding = res.findings.find(
(entry) => entry.checkId === "channels.discord.allowFrom.dangerous_name_matching_enabled",
);
expect(dangerousMatchingFinding).toBeDefined();
expect(dangerousMatchingFinding?.title).not.toContain("(account: toString)");
const nameBasedFinding = res.findings.find(
(entry) => entry.checkId === "channels.discord.allowFrom.name_based_entries",
);
expect(nameBasedFinding).toBeDefined();
expect(nameBasedFinding?.detail).toContain("channels.discord.allowFrom:Alice#1234");
expect(nameBasedFinding?.detail).not.toContain("channels.discord.accounts.toString");
});
});
it("audits name-based allowlists on non-default Discord accounts", async () => {
await withChannelSecurityStateDir(async () => {
const cfg: OpenClawConfig = {