Skip to content

Commit 093e51f

Browse files
committed
Security: lazy-load channel audit provider helpers
1 parent c4a5fd8 commit 093e51f

File tree

2 files changed

+56
-30
lines changed

2 files changed

+56
-30
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export {
2+
isNumericTelegramUserId,
3+
normalizeTelegramAllowFromEntry,
4+
} from "../../extensions/telegram/src/allow-from.js";
5+
export { readChannelAllowFromStore } from "../pairing/pairing-store.js";
6+
export {
7+
isDiscordMutableAllowEntry,
8+
isZalouserMutableGroupEntry,
9+
} from "./mutable-allowlist-detectors.js";

src/security/audit-channel.ts

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import {
2-
isNumericTelegramUserId,
3-
normalizeTelegramAllowFromEntry,
4-
} from "../../extensions/telegram/src/allow-from.js";
51
import {
62
hasConfiguredUnavailableCredentialStatus,
73
hasResolvedCredentialValue,
@@ -15,14 +11,18 @@ import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "../con
1511
import type { OpenClawConfig } from "../config/config.js";
1612
import { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
1713
import { formatErrorMessage } from "../infra/errors.js";
18-
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
1914
import { normalizeStringEntries } from "../shared/string-normalization.js";
2015
import type { SecurityAuditFinding, SecurityAuditSeverity } from "./audit.js";
2116
import { resolveDmAllowState } from "./dm-policy-shared.js";
22-
import {
23-
isDiscordMutableAllowEntry,
24-
isZalouserMutableGroupEntry,
25-
} from "./mutable-allowlist-detectors.js";
17+
18+
let auditChannelRuntimeModulePromise:
19+
| Promise<typeof import("./audit-channel.runtime.js")>
20+
| undefined;
21+
22+
function loadAuditChannelRuntimeModule() {
23+
auditChannelRuntimeModulePromise ??= import("./audit-channel.runtime.js");
24+
return auditChannelRuntimeModulePromise;
25+
}
2626

2727
function normalizeAllowFromList(list: Array<string | number> | undefined | null): string[] {
2828
return normalizeStringEntries(Array.isArray(list) ? list : undefined);
@@ -32,12 +32,13 @@ function addDiscordNameBasedEntries(params: {
3232
target: Set<string>;
3333
values: unknown;
3434
source: string;
35+
isDiscordMutableAllowEntry: (value: string) => boolean;
3536
}): void {
3637
if (!Array.isArray(params.values)) {
3738
return;
3839
}
3940
for (const value of params.values) {
40-
if (!isDiscordMutableAllowEntry(String(value))) {
41+
if (!params.isDiscordMutableAllowEntry(String(value))) {
4142
continue;
4243
}
4344
const text = String(value).trim();
@@ -52,25 +53,28 @@ function addZalouserMutableGroupEntries(params: {
5253
target: Set<string>;
5354
groups: unknown;
5455
source: string;
56+
isZalouserMutableGroupEntry: (value: string) => boolean;
5557
}): void {
5658
if (!params.groups || typeof params.groups !== "object" || Array.isArray(params.groups)) {
5759
return;
5860
}
5961
for (const key of Object.keys(params.groups as Record<string, unknown>)) {
60-
if (!isZalouserMutableGroupEntry(key)) {
62+
if (!params.isZalouserMutableGroupEntry(key)) {
6163
continue;
6264
}
6365
params.target.add(`${params.source}:${key}`);
6466
}
6567
}
6668

67-
function collectInvalidTelegramAllowFromEntries(params: {
69+
async function collectInvalidTelegramAllowFromEntries(params: {
6870
entries: unknown;
6971
target: Set<string>;
70-
}): void {
72+
}): Promise<void> {
7173
if (!Array.isArray(params.entries)) {
7274
return;
7375
}
76+
const { isNumericTelegramUserId, normalizeTelegramAllowFromEntry } =
77+
await loadAuditChannelRuntimeModule();
7478
for (const entry of params.entries) {
7579
const normalized = normalizeTelegramAllowFromEntry(entry);
7680
if (!normalized || normalized === "*") {
@@ -383,6 +387,8 @@ export async function collectChannelSecurityFindings(params: {
383387
}
384388

385389
if (plugin.id === "discord") {
390+
const { isDiscordMutableAllowEntry, readChannelAllowFromStore } =
391+
await loadAuditChannelRuntimeModule();
386392
const discordCfg =
387393
(account as { config?: Record<string, unknown> } | null)?.config ??
388394
({} as Record<string, unknown>);
@@ -401,16 +407,19 @@ export async function collectChannelSecurityFindings(params: {
401407
target: discordNameBasedAllowEntries,
402408
values: discordCfg.allowFrom,
403409
source: `${discordPathPrefix}.allowFrom`,
410+
isDiscordMutableAllowEntry,
404411
});
405412
addDiscordNameBasedEntries({
406413
target: discordNameBasedAllowEntries,
407414
values: (discordCfg.dm as { allowFrom?: unknown } | undefined)?.allowFrom,
408415
source: `${discordPathPrefix}.dm.allowFrom`,
416+
isDiscordMutableAllowEntry,
409417
});
410418
addDiscordNameBasedEntries({
411419
target: discordNameBasedAllowEntries,
412420
values: storeAllowFrom,
413421
source: "~/.openclaw/credentials/discord-allowFrom.json",
422+
isDiscordMutableAllowEntry,
414423
});
415424
const discordGuildEntries =
416425
(discordCfg.guilds as Record<string, unknown> | undefined) ?? {};
@@ -423,6 +432,7 @@ export async function collectChannelSecurityFindings(params: {
423432
target: discordNameBasedAllowEntries,
424433
values: guild.users,
425434
source: `${discordPathPrefix}.guilds.${guildKey}.users`,
435+
isDiscordMutableAllowEntry,
426436
});
427437
const channels = guild.channels;
428438
if (!channels || typeof channels !== "object") {
@@ -439,6 +449,7 @@ export async function collectChannelSecurityFindings(params: {
439449
target: discordNameBasedAllowEntries,
440450
values: channel.users,
441451
source: `${discordPathPrefix}.guilds.${guildKey}.channels.${channelKey}.users`,
452+
isDiscordMutableAllowEntry,
442453
});
443454
}
444455
}
@@ -547,6 +558,7 @@ export async function collectChannelSecurityFindings(params: {
547558
}
548559

549560
if (plugin.id === "zalouser") {
561+
const { isZalouserMutableGroupEntry } = await loadAuditChannelRuntimeModule();
550562
const zalouserCfg =
551563
(account as { config?: Record<string, unknown> } | null)?.config ??
552564
({} as Record<string, unknown>);
@@ -560,6 +572,7 @@ export async function collectChannelSecurityFindings(params: {
560572
target: mutableGroupEntries,
561573
groups: zalouserCfg.groups,
562574
source: `${zalouserPathPrefix}.groups`,
575+
isZalouserMutableGroupEntry,
563576
});
564577
if (mutableGroupEntries.size > 0) {
565578
const examples = Array.from(mutableGroupEntries).slice(0, 5);
@@ -586,6 +599,7 @@ export async function collectChannelSecurityFindings(params: {
586599
}
587600

588601
if (plugin.id === "slack") {
602+
const { readChannelAllowFromStore } = await loadAuditChannelRuntimeModule();
589603
const slackCfg =
590604
(account as { config?: Record<string, unknown>; dm?: Record<string, unknown> } | null)
591605
?.config ?? ({} as Record<string, unknown>);
@@ -724,63 +738,66 @@ export async function collectChannelSecurityFindings(params: {
724738
continue;
725739
}
726740

741+
const { readChannelAllowFromStore } = await loadAuditChannelRuntimeModule();
727742
const storeAllowFrom = await readChannelAllowFromStore(
728743
"telegram",
729744
process.env,
730745
accountId,
731746
).catch(() => []);
732747
const storeHasWildcard = storeAllowFrom.some((v) => String(v).trim() === "*");
733748
const invalidTelegramAllowFromEntries = new Set<string>();
734-
collectInvalidTelegramAllowFromEntries({
749+
await collectInvalidTelegramAllowFromEntries({
735750
entries: storeAllowFrom,
736751
target: invalidTelegramAllowFromEntries,
737752
});
738753
const groupAllowFrom = Array.isArray(telegramCfg.groupAllowFrom)
739754
? telegramCfg.groupAllowFrom
740755
: [];
741756
const groupAllowFromHasWildcard = groupAllowFrom.some((v) => String(v).trim() === "*");
742-
collectInvalidTelegramAllowFromEntries({
757+
await collectInvalidTelegramAllowFromEntries({
743758
entries: groupAllowFrom,
744759
target: invalidTelegramAllowFromEntries,
745760
});
746761
const dmAllowFrom = Array.isArray(telegramCfg.allowFrom) ? telegramCfg.allowFrom : [];
747-
collectInvalidTelegramAllowFromEntries({
762+
await collectInvalidTelegramAllowFromEntries({
748763
entries: dmAllowFrom,
749764
target: invalidTelegramAllowFromEntries,
750765
});
751-
const anyGroupOverride = Boolean(
752-
groups &&
753-
Object.values(groups).some((value) => {
766+
let anyGroupOverride = false;
767+
if (groups) {
768+
for (const value of Object.values(groups)) {
754769
if (!value || typeof value !== "object") {
755-
return false;
770+
continue;
756771
}
757772
const group = value as Record<string, unknown>;
758773
const allowFrom = Array.isArray(group.allowFrom) ? group.allowFrom : [];
759774
if (allowFrom.length > 0) {
760-
collectInvalidTelegramAllowFromEntries({
775+
anyGroupOverride = true;
776+
await collectInvalidTelegramAllowFromEntries({
761777
entries: allowFrom,
762778
target: invalidTelegramAllowFromEntries,
763779
});
764-
return true;
765780
}
766781
const topics = group.topics;
767782
if (!topics || typeof topics !== "object") {
768-
return false;
783+
continue;
769784
}
770-
return Object.values(topics as Record<string, unknown>).some((topicValue) => {
785+
for (const topicValue of Object.values(topics as Record<string, unknown>)) {
771786
if (!topicValue || typeof topicValue !== "object") {
772-
return false;
787+
continue;
773788
}
774789
const topic = topicValue as Record<string, unknown>;
775790
const topicAllow = Array.isArray(topic.allowFrom) ? topic.allowFrom : [];
776-
collectInvalidTelegramAllowFromEntries({
791+
if (topicAllow.length > 0) {
792+
anyGroupOverride = true;
793+
}
794+
await collectInvalidTelegramAllowFromEntries({
777795
entries: topicAllow,
778796
target: invalidTelegramAllowFromEntries,
779797
});
780-
return topicAllow.length > 0;
781-
});
782-
}),
783-
);
798+
}
799+
}
800+
}
784801

785802
const hasAnySenderAllowlist =
786803
storeAllowFrom.length > 0 || groupAllowFrom.length > 0 || anyGroupOverride;

0 commit comments

Comments
 (0)