mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 03:21:23 +00:00
refactor(security): unify dangerous name matching handling
This commit is contained in:
@@ -14,12 +14,21 @@ import {
|
||||
migrateLegacyConfig,
|
||||
readConfigFileSnapshot,
|
||||
} from "../config/config.js";
|
||||
import { collectProviderDangerousNameMatchingScopes } from "../config/dangerous-name-matching.js";
|
||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||
import { parseToolsBySenderTypedKey } from "../config/types.tools.js";
|
||||
import {
|
||||
listInterpreterLikeSafeBins,
|
||||
resolveMergedSafeBinProfileFixtures,
|
||||
} from "../infra/exec-safe-bin-runtime-policy.js";
|
||||
import {
|
||||
isDiscordMutableAllowEntry,
|
||||
isGoogleChatMutableAllowEntry,
|
||||
isIrcMutableAllowEntry,
|
||||
isMSTeamsMutableAllowEntry,
|
||||
isMattermostMutableAllowEntry,
|
||||
isSlackMutableAllowEntry,
|
||||
} from "../security/mutable-allowlist-detectors.js";
|
||||
import { listTelegramAccountIds, resolveTelegramAccount } from "../telegram/accounts.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { isRecord, resolveHomeDir } from "../utils.js";
|
||||
@@ -192,10 +201,6 @@ function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function asOptionalBoolean(value: unknown): boolean | undefined {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function collectTelegramAccountScopes(
|
||||
cfg: OpenClawConfig,
|
||||
): Array<{ prefix: string; account: Record<string, unknown> }> {
|
||||
@@ -589,148 +594,6 @@ type MutableAllowlistHit = {
|
||||
dangerousFlagPath: string;
|
||||
};
|
||||
|
||||
type ProviderAccountScope = {
|
||||
prefix: string;
|
||||
account: Record<string, unknown>;
|
||||
dangerousNameMatchingEnabled: boolean;
|
||||
dangerousFlagPath: string;
|
||||
};
|
||||
|
||||
function collectProviderAccountScopes(
|
||||
cfg: OpenClawConfig,
|
||||
provider: string,
|
||||
): ProviderAccountScope[] {
|
||||
const scopes: ProviderAccountScope[] = [];
|
||||
const channels = asObjectRecord(cfg.channels);
|
||||
if (!channels) {
|
||||
return scopes;
|
||||
}
|
||||
const providerCfg = asObjectRecord(channels[provider]);
|
||||
if (!providerCfg) {
|
||||
return scopes;
|
||||
}
|
||||
const providerPrefix = `channels.${provider}`;
|
||||
const providerDangerousFlagPath = `${providerPrefix}.dangerouslyAllowNameMatching`;
|
||||
const providerDangerousNameMatchingEnabled = providerCfg.dangerouslyAllowNameMatching === true;
|
||||
scopes.push({
|
||||
prefix: providerPrefix,
|
||||
account: providerCfg,
|
||||
dangerousNameMatchingEnabled: providerDangerousNameMatchingEnabled,
|
||||
dangerousFlagPath: providerDangerousFlagPath,
|
||||
});
|
||||
const accounts = asObjectRecord(providerCfg.accounts);
|
||||
if (!accounts) {
|
||||
return scopes;
|
||||
}
|
||||
for (const key of Object.keys(accounts)) {
|
||||
const account = asObjectRecord(accounts[key]);
|
||||
if (!account) {
|
||||
continue;
|
||||
}
|
||||
const accountPrefix = `${providerPrefix}.accounts.${key}`;
|
||||
const accountDangerousNameMatching = asOptionalBoolean(account.dangerouslyAllowNameMatching);
|
||||
scopes.push({
|
||||
prefix: accountPrefix,
|
||||
account,
|
||||
dangerousNameMatchingEnabled:
|
||||
accountDangerousNameMatching ?? providerDangerousNameMatchingEnabled,
|
||||
dangerousFlagPath:
|
||||
accountDangerousNameMatching == null
|
||||
? providerDangerousFlagPath
|
||||
: `${accountPrefix}.dangerouslyAllowNameMatching`,
|
||||
});
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
function isDiscordMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
const maybeMentionId = text.replace(/^<@!?/, "").replace(/>$/, "");
|
||||
if (/^\d+$/.test(maybeMentionId)) {
|
||||
return false;
|
||||
}
|
||||
for (const prefix of ["discord:", "user:", "pk:"]) {
|
||||
if (!text.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
return text.slice(prefix.length).trim().length === 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isSlackMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
const mentionMatch = text.match(/^<@([A-Z0-9]+)>$/i);
|
||||
if (mentionMatch && /^[A-Z0-9]{8,}$/i.test(mentionMatch[1] ?? "")) {
|
||||
return false;
|
||||
}
|
||||
const withoutPrefix = text.replace(/^(slack|user):/i, "").trim();
|
||||
if (/^[UWBCGDT][A-Z0-9]{2,}$/.test(withoutPrefix)) {
|
||||
return false;
|
||||
}
|
||||
if (/^[A-Z0-9]{8,}$/i.test(withoutPrefix)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isGoogleChatMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
const withoutPrefix = text.replace(/^(googlechat|google-chat|gchat):/i, "").trim();
|
||||
if (!withoutPrefix) {
|
||||
return false;
|
||||
}
|
||||
const withoutUsers = withoutPrefix.replace(/^users\//i, "");
|
||||
return withoutUsers.includes("@");
|
||||
}
|
||||
|
||||
function isMSTeamsMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
const withoutPrefix = text.replace(/^(msteams|user):/i, "").trim();
|
||||
return /\s/.test(withoutPrefix) || withoutPrefix.includes("@");
|
||||
}
|
||||
|
||||
function isMattermostMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^(mattermost|user):/i, "")
|
||||
.replace(/^@/, "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
// Mattermost user IDs are stable 26-char lowercase/number tokens.
|
||||
if (/^[a-z0-9]{26}$/.test(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isIrcMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim().toLowerCase();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^irc:/, "")
|
||||
.replace(/^user:/, "")
|
||||
.trim();
|
||||
return !normalized.includes("!") && !normalized.includes("@");
|
||||
}
|
||||
|
||||
function addMutableAllowlistHits(params: {
|
||||
hits: MutableAllowlistHit[];
|
||||
pathLabel: string;
|
||||
@@ -762,7 +625,7 @@ function addMutableAllowlistHits(params: {
|
||||
function scanMutableAllowlistEntries(cfg: OpenClawConfig): MutableAllowlistHit[] {
|
||||
const hits: MutableAllowlistHit[] = [];
|
||||
|
||||
for (const scope of collectProviderAccountScopes(cfg, "discord")) {
|
||||
for (const scope of collectProviderDangerousNameMatchingScopes(cfg, "discord")) {
|
||||
if (scope.dangerousNameMatchingEnabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -823,7 +686,7 @@ function scanMutableAllowlistEntries(cfg: OpenClawConfig): MutableAllowlistHit[]
|
||||
}
|
||||
}
|
||||
|
||||
for (const scope of collectProviderAccountScopes(cfg, "slack")) {
|
||||
for (const scope of collectProviderDangerousNameMatchingScopes(cfg, "slack")) {
|
||||
if (scope.dangerousNameMatchingEnabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -866,7 +729,7 @@ function scanMutableAllowlistEntries(cfg: OpenClawConfig): MutableAllowlistHit[]
|
||||
}
|
||||
}
|
||||
|
||||
for (const scope of collectProviderAccountScopes(cfg, "googlechat")) {
|
||||
for (const scope of collectProviderDangerousNameMatchingScopes(cfg, "googlechat")) {
|
||||
if (scope.dangerousNameMatchingEnabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -909,7 +772,7 @@ function scanMutableAllowlistEntries(cfg: OpenClawConfig): MutableAllowlistHit[]
|
||||
}
|
||||
}
|
||||
|
||||
for (const scope of collectProviderAccountScopes(cfg, "msteams")) {
|
||||
for (const scope of collectProviderDangerousNameMatchingScopes(cfg, "msteams")) {
|
||||
if (scope.dangerousNameMatchingEnabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -931,7 +794,7 @@ function scanMutableAllowlistEntries(cfg: OpenClawConfig): MutableAllowlistHit[]
|
||||
});
|
||||
}
|
||||
|
||||
for (const scope of collectProviderAccountScopes(cfg, "mattermost")) {
|
||||
for (const scope of collectProviderDangerousNameMatchingScopes(cfg, "mattermost")) {
|
||||
if (scope.dangerousNameMatchingEnabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -953,7 +816,7 @@ function scanMutableAllowlistEntries(cfg: OpenClawConfig): MutableAllowlistHit[]
|
||||
});
|
||||
}
|
||||
|
||||
for (const scope of collectProviderAccountScopes(cfg, "irc")) {
|
||||
for (const scope of collectProviderDangerousNameMatchingScopes(cfg, "irc")) {
|
||||
if (scope.dangerousNameMatchingEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user