mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(security): make allowFrom id-only by default with dangerous name opt-in (#24907)
* fix(channels): default allowFrom to id-only; add dangerous name opt-in * docs(security): align channel allowFrom docs with id-only default
This commit is contained in:
committed by
GitHub
parent
41b0568b35
commit
cfa44ea6b4
@@ -46,6 +46,7 @@ export const IrcAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
dangerouslyAllowNameMatching: z.boolean().optional(),
|
||||
host: z.string().optional(),
|
||||
port: z.number().int().min(1).max(65535).optional(),
|
||||
tls: z.boolean().optional(),
|
||||
|
||||
@@ -78,6 +78,7 @@ export async function handleIrcInbound(params: {
|
||||
const senderDisplay = message.senderHost
|
||||
? `${message.senderNick}!${message.senderUser ?? "?"}@${message.senderHost}`
|
||||
: message.senderNick;
|
||||
const allowNameMatching = account.config.dangerouslyAllowNameMatching === true;
|
||||
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const defaultGroupPolicy = resolveDefaultGroupPolicy(config);
|
||||
@@ -132,6 +133,7 @@ export async function handleIrcInbound(params: {
|
||||
const senderAllowedForCommands = resolveIrcAllowlistMatch({
|
||||
allowFrom: message.isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom,
|
||||
message,
|
||||
allowNameMatching,
|
||||
}).allowed;
|
||||
const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config as OpenClawConfig);
|
||||
const commandGate = resolveControlCommandGate({
|
||||
@@ -153,6 +155,7 @@ export async function handleIrcInbound(params: {
|
||||
message,
|
||||
outerAllowFrom: effectiveGroupAllowFrom,
|
||||
innerAllowFrom: groupAllowFrom,
|
||||
allowNameMatching,
|
||||
});
|
||||
if (!senderAllowed) {
|
||||
runtime.log?.(`irc: drop group sender ${senderDisplay} (policy=${groupPolicy})`);
|
||||
@@ -167,6 +170,7 @@ export async function handleIrcInbound(params: {
|
||||
const dmAllowed = resolveIrcAllowlistMatch({
|
||||
allowFrom: effectiveAllowFrom,
|
||||
message,
|
||||
allowNameMatching,
|
||||
}).allowed;
|
||||
if (!dmAllowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
|
||||
@@ -30,6 +30,8 @@ describe("irc normalize", () => {
|
||||
};
|
||||
|
||||
expect(buildIrcAllowlistCandidates(message)).toContain("alice!ident@example.org");
|
||||
expect(buildIrcAllowlistCandidates(message)).not.toContain("alice");
|
||||
expect(buildIrcAllowlistCandidates(message, { allowNameMatching: true })).toContain("alice");
|
||||
expect(
|
||||
resolveIrcAllowlistMatch({
|
||||
allowFrom: ["alice!ident@example.org"],
|
||||
@@ -38,9 +40,16 @@ describe("irc normalize", () => {
|
||||
).toBe(true);
|
||||
expect(
|
||||
resolveIrcAllowlistMatch({
|
||||
allowFrom: ["bob"],
|
||||
allowFrom: ["alice"],
|
||||
message,
|
||||
}).allowed,
|
||||
).toBe(false);
|
||||
expect(
|
||||
resolveIrcAllowlistMatch({
|
||||
allowFrom: ["alice"],
|
||||
message,
|
||||
allowNameMatching: true,
|
||||
}).allowed,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,12 +77,15 @@ export function formatIrcSenderId(message: IrcInboundMessage): string {
|
||||
return base;
|
||||
}
|
||||
|
||||
export function buildIrcAllowlistCandidates(message: IrcInboundMessage): string[] {
|
||||
export function buildIrcAllowlistCandidates(
|
||||
message: IrcInboundMessage,
|
||||
params?: { allowNameMatching?: boolean },
|
||||
): string[] {
|
||||
const nick = message.senderNick.trim().toLowerCase();
|
||||
const user = message.senderUser?.trim().toLowerCase();
|
||||
const host = message.senderHost?.trim().toLowerCase();
|
||||
const candidates = new Set<string>();
|
||||
if (nick) {
|
||||
if (nick && params?.allowNameMatching === true) {
|
||||
candidates.add(nick);
|
||||
}
|
||||
if (nick && user) {
|
||||
@@ -100,6 +103,7 @@ export function buildIrcAllowlistCandidates(message: IrcInboundMessage): string[
|
||||
export function resolveIrcAllowlistMatch(params: {
|
||||
allowFrom: string[];
|
||||
message: IrcInboundMessage;
|
||||
allowNameMatching?: boolean;
|
||||
}): { allowed: boolean; source?: string } {
|
||||
const allowFrom = new Set(
|
||||
params.allowFrom.map((entry) => entry.trim().toLowerCase()).filter(Boolean),
|
||||
@@ -107,7 +111,9 @@ export function resolveIrcAllowlistMatch(params: {
|
||||
if (allowFrom.has("*")) {
|
||||
return { allowed: true, source: "wildcard" };
|
||||
}
|
||||
const candidates = buildIrcAllowlistCandidates(params.message);
|
||||
const candidates = buildIrcAllowlistCandidates(params.message, {
|
||||
allowNameMatching: params.allowNameMatching,
|
||||
});
|
||||
for (const candidate of candidates) {
|
||||
if (allowFrom.has(candidate)) {
|
||||
return { allowed: true, source: candidate };
|
||||
|
||||
@@ -50,6 +50,14 @@ describe("irc policy", () => {
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
resolveIrcGroupSenderAllowed({
|
||||
groupPolicy: "allowlist",
|
||||
message,
|
||||
outerAllowFrom: ["alice!ident@example.org"],
|
||||
innerAllowFrom: [],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
resolveIrcGroupSenderAllowed({
|
||||
groupPolicy: "allowlist",
|
||||
@@ -57,6 +65,15 @@ describe("irc policy", () => {
|
||||
outerAllowFrom: ["alice"],
|
||||
innerAllowFrom: [],
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
resolveIrcGroupSenderAllowed({
|
||||
groupPolicy: "allowlist",
|
||||
message,
|
||||
outerAllowFrom: ["alice"],
|
||||
innerAllowFrom: [],
|
||||
allowNameMatching: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -142,16 +142,25 @@ export function resolveIrcGroupSenderAllowed(params: {
|
||||
message: IrcInboundMessage;
|
||||
outerAllowFrom: string[];
|
||||
innerAllowFrom: string[];
|
||||
allowNameMatching?: boolean;
|
||||
}): boolean {
|
||||
const policy = params.groupPolicy ?? "allowlist";
|
||||
const inner = normalizeIrcAllowlist(params.innerAllowFrom);
|
||||
const outer = normalizeIrcAllowlist(params.outerAllowFrom);
|
||||
|
||||
if (inner.length > 0) {
|
||||
return resolveIrcAllowlistMatch({ allowFrom: inner, message: params.message }).allowed;
|
||||
return resolveIrcAllowlistMatch({
|
||||
allowFrom: inner,
|
||||
message: params.message,
|
||||
allowNameMatching: params.allowNameMatching,
|
||||
}).allowed;
|
||||
}
|
||||
if (outer.length > 0) {
|
||||
return resolveIrcAllowlistMatch({ allowFrom: outer, message: params.message }).allowed;
|
||||
return resolveIrcAllowlistMatch({
|
||||
allowFrom: outer,
|
||||
message: params.message,
|
||||
allowNameMatching: params.allowNameMatching,
|
||||
}).allowed;
|
||||
}
|
||||
return policy === "open";
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ export type IrcNickServConfig = {
|
||||
export type IrcAccountConfig = {
|
||||
name?: string;
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* Break-glass override: allow nick-only allowlist matching.
|
||||
* Default behavior requires host/user-qualified identities.
|
||||
*/
|
||||
dangerouslyAllowNameMatching?: boolean;
|
||||
host?: string;
|
||||
port?: number;
|
||||
tls?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user