119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
|
|
import { listChannelPlugins } from "../channels/plugins/index.js";
|
|
import type { ChannelId } from "../channels/plugins/types.js";
|
|
import type { ClawdbotConfig } from "../config/config.js";
|
|
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
|
import { note } from "../terminal/note.js";
|
|
|
|
export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|
const warnings: string[] = [];
|
|
|
|
const warnDmPolicy = async (params: {
|
|
label: string;
|
|
provider: ChannelId;
|
|
dmPolicy: string;
|
|
allowFrom?: Array<string | number> | null;
|
|
policyPath?: string;
|
|
allowFromPath: string;
|
|
approveHint: string;
|
|
normalizeEntry?: (raw: string) => string;
|
|
}) => {
|
|
const dmPolicy = params.dmPolicy;
|
|
const policyPath = params.policyPath ?? `${params.allowFromPath}policy`;
|
|
const configAllowFrom = (params.allowFrom ?? []).map((v) =>
|
|
String(v).trim(),
|
|
);
|
|
const hasWildcard = configAllowFrom.includes("*");
|
|
const storeAllowFrom = await readChannelAllowFromStore(
|
|
params.provider,
|
|
).catch(() => []);
|
|
const normalizedCfg = configAllowFrom
|
|
.filter((v) => v !== "*")
|
|
.map((v) => (params.normalizeEntry ? params.normalizeEntry(v) : v))
|
|
.map((v) => v.trim())
|
|
.filter(Boolean);
|
|
const normalizedStore = storeAllowFrom
|
|
.map((v) => (params.normalizeEntry ? params.normalizeEntry(v) : v))
|
|
.map((v) => v.trim())
|
|
.filter(Boolean);
|
|
const allowCount = Array.from(
|
|
new Set([...normalizedCfg, ...normalizedStore]),
|
|
).length;
|
|
|
|
if (dmPolicy === "open") {
|
|
const allowFromPath = `${params.allowFromPath}allowFrom`;
|
|
warnings.push(
|
|
`- ${params.label} DMs: OPEN (${policyPath}="open"). Anyone can DM it.`,
|
|
);
|
|
if (!hasWildcard) {
|
|
warnings.push(
|
|
`- ${params.label} DMs: config invalid — "open" requires ${allowFromPath} to include "*".`,
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (dmPolicy === "disabled") {
|
|
warnings.push(
|
|
`- ${params.label} DMs: disabled (${policyPath}="disabled").`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (allowCount === 0) {
|
|
warnings.push(
|
|
`- ${params.label} DMs: locked (${policyPath}="${dmPolicy}") with no allowlist; unknown senders will be blocked / get a pairing code.`,
|
|
);
|
|
warnings.push(` ${params.approveHint}`);
|
|
}
|
|
};
|
|
|
|
for (const plugin of listChannelPlugins()) {
|
|
if (!plugin.security) continue;
|
|
const accountIds = plugin.config.listAccountIds(cfg);
|
|
const defaultAccountId = resolveChannelDefaultAccountId({
|
|
plugin,
|
|
cfg,
|
|
accountIds,
|
|
});
|
|
const account = plugin.config.resolveAccount(cfg, defaultAccountId);
|
|
const enabled = plugin.config.isEnabled
|
|
? plugin.config.isEnabled(account, cfg)
|
|
: true;
|
|
if (!enabled) continue;
|
|
const configured = plugin.config.isConfigured
|
|
? await plugin.config.isConfigured(account, cfg)
|
|
: true;
|
|
if (!configured) continue;
|
|
const dmPolicy = plugin.security.resolveDmPolicy?.({
|
|
cfg,
|
|
accountId: defaultAccountId,
|
|
account,
|
|
});
|
|
if (dmPolicy) {
|
|
await warnDmPolicy({
|
|
label: plugin.meta.label ?? plugin.id,
|
|
provider: plugin.id,
|
|
dmPolicy: dmPolicy.policy,
|
|
allowFrom: dmPolicy.allowFrom,
|
|
policyPath: dmPolicy.policyPath,
|
|
allowFromPath: dmPolicy.allowFromPath,
|
|
approveHint: dmPolicy.approveHint,
|
|
normalizeEntry: dmPolicy.normalizeEntry,
|
|
});
|
|
}
|
|
if (plugin.security.collectWarnings) {
|
|
const extra = await plugin.security.collectWarnings({
|
|
cfg,
|
|
accountId: defaultAccountId,
|
|
account,
|
|
});
|
|
if (extra?.length) warnings.push(...extra);
|
|
}
|
|
}
|
|
|
|
if (warnings.length > 0) {
|
|
note(warnings.join("\n"), "Security");
|
|
}
|
|
}
|