security: simplify remediation steps with copy-paste CLI commands

This commit is contained in:
Claude 2026-01-27 04:02:30 +00:00
parent 83de980d6c
commit 5ae78f5513
No known key found for this signature in database
2 changed files with 22 additions and 31 deletions

View File

@ -138,8 +138,7 @@ export function collectSecretsInConfigFindings(cfg: ClawdbotConfig): SecurityAud
title: "Gateway password is stored in config", title: "Gateway password is stored in config",
detail: detail:
"gateway.auth.password is set in the config file; prefer environment variables for secrets when possible.", "gateway.auth.password is set in the config file; prefer environment variables for secrets when possible.",
remediation: remediation: `Run: export CLAWDBOT_GATEWAY_PASSWORD="your-password" && clawdbot config unset gateway.auth.password`,
"Prefer CLAWDBOT_GATEWAY_PASSWORD (env) and remove gateway.auth.password from disk.",
}); });
} }
@ -188,7 +187,7 @@ export function collectHooksHardeningFindings(cfg: ClawdbotConfig): SecurityAudi
title: "Hooks token reuses the Gateway token", title: "Hooks token reuses the Gateway token",
detail: detail:
"hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.", "hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.",
remediation: "Use a separate hooks.token dedicated to hook ingress.", remediation: `Run: clawdbot config set hooks.token "$(openssl rand -hex 32)"`,
}); });
} }
@ -199,7 +198,7 @@ export function collectHooksHardeningFindings(cfg: ClawdbotConfig): SecurityAudi
severity: "critical", severity: "critical",
title: "Hooks base path is '/'", title: "Hooks base path is '/'",
detail: "hooks.path='/' would shadow other HTTP endpoints and is unsafe.", detail: "hooks.path='/' would shadow other HTTP endpoints and is unsafe.",
remediation: "Use a dedicated path like '/hooks'.", remediation: `Run: clawdbot config set hooks.path /hooks`,
}); });
} }
@ -338,7 +337,7 @@ export function collectModelHygieneFindings(cfg: ClawdbotConfig): SecurityAuditF
"Older/legacy models can be less robust against prompt injection and tool misuse.\n" + "Older/legacy models can be less robust against prompt injection and tool misuse.\n" +
lines + lines +
more, more,
remediation: "Prefer modern, instruction-hardened models for any bot that can run tools.", remediation: `Run: clawdbot config set agents.defaults.model.primary claude-sonnet-4-20250514 (or gpt-5)`,
}); });
} }
@ -356,8 +355,7 @@ export function collectModelHygieneFindings(cfg: ClawdbotConfig): SecurityAuditF
"Smaller/older models are generally more susceptible to prompt injection and tool misuse.\n" + "Smaller/older models are generally more susceptible to prompt injection and tool misuse.\n" +
lines + lines +
more, more,
remediation: remediation: `Run: clawdbot config set agents.defaults.model.primary claude-sonnet-4-20250514 (avoid Haiku; prefer Claude 4.5+ or GPT-5+)`,
"Use the latest, top-tier model for any bot with tools or untrusted inboxes. Avoid Haiku tiers; prefer GPT-5+ and Claude 4.5+.",
}); });
} }
@ -506,8 +504,7 @@ export function collectSmallModelRiskFindings(params: {
exposureDetail + exposureDetail +
`\n` + `\n` +
"Small models are not recommended for untrusted inputs.", "Small models are not recommended for untrusted inputs.",
remediation: remediation: `Run: clawdbot config set agents.defaults.sandbox.mode all && clawdbot config set tools.deny '["group:web","browser"]'`,
'If you must use small models, enable sandboxing for all sessions (agents.defaults.sandbox.mode="all") and disable web_search/web_fetch/browser (tools.deny=["group:web","browser"]).',
}); });
return findings; return findings;
@ -602,7 +599,7 @@ export async function collectPluginsTrustFindings(params: {
(skillCommandsLikelyExposed (skillCommandsLikelyExposed
? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk." ? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
: ""), : ""),
remediation: "Set plugins.allow to an explicit list of plugin ids you trust.", remediation: `Run: clawdbot config set plugins.allow '["plugin-id-1","plugin-id-2"]' (list only trusted plugin IDs)`,
}); });
} }
@ -937,7 +934,7 @@ export function collectExposureMatrixFindings(cfg: ClawdbotConfig): SecurityAudi
detail: detail:
`Found groupPolicy="open" at:\n${openGroups.map((p) => `- ${p}`).join("\n")}\n` + `Found groupPolicy="open" at:\n${openGroups.map((p) => `- ${p}`).join("\n")}\n` +
"With tools.elevated enabled, a prompt injection in those rooms can become a high-impact incident.", "With tools.elevated enabled, a prompt injection in those rooms can become a high-impact incident.",
remediation: `Set groupPolicy="allowlist" and keep elevated allowlists extremely tight.`, remediation: `Run: clawdbot security audit --fix (auto-sets groupPolicy to allowlist)`,
}); });
} }

View File

@ -273,7 +273,7 @@ function collectGatewayConfigFindings(
severity: "critical", severity: "critical",
title: "Gateway binds beyond loopback without auth", title: "Gateway binds beyond loopback without auth",
detail: `gateway.bind="${bind}" but no gateway.auth token/password is configured.`, detail: `gateway.bind="${bind}" but no gateway.auth token/password is configured.`,
remediation: `Set gateway.auth (token recommended) or bind to loopback.`, remediation: `Run: clawdbot config set gateway.auth.mode token && clawdbot config set gateway.auth.token "$(openssl rand -hex 32)"`,
}); });
} }
@ -286,8 +286,7 @@ function collectGatewayConfigFindings(
"gateway.bind is loopback and gateway.trustedProxies is empty. " + "gateway.bind is loopback and gateway.trustedProxies is empty. " +
"If you expose the Control UI through a reverse proxy, configure trusted proxies " + "If you expose the Control UI through a reverse proxy, configure trusted proxies " +
"so local-client checks cannot be spoofed.", "so local-client checks cannot be spoofed.",
remediation: remediation: `Run: clawdbot config set gateway.trustedProxies '["127.0.0.1"]' (replace with your proxy IPs)`,
"Set gateway.trustedProxies to your proxy IPs or keep the Control UI local-only.",
}); });
} }
@ -299,7 +298,7 @@ function collectGatewayConfigFindings(
detail: detail:
"gateway.bind is loopback but no gateway auth secret is configured. " + "gateway.bind is loopback but no gateway auth secret is configured. " +
"If the Control UI is exposed through a reverse proxy, unauthenticated access is possible.", "If the Control UI is exposed through a reverse proxy, unauthenticated access is possible.",
remediation: "Set gateway.auth (token recommended) or keep the Control UI local-only.", remediation: `Run: clawdbot config set gateway.auth.mode token && clawdbot config set gateway.auth.token "$(openssl rand -hex 32)"`,
}); });
} }
@ -309,7 +308,7 @@ function collectGatewayConfigFindings(
severity: "critical", severity: "critical",
title: "Tailscale Funnel exposure enabled", title: "Tailscale Funnel exposure enabled",
detail: `gateway.tailscale.mode="funnel" exposes the Gateway publicly; keep auth strict and treat it as internet-facing.`, detail: `gateway.tailscale.mode="funnel" exposes the Gateway publicly; keep auth strict and treat it as internet-facing.`,
remediation: `Prefer tailscale.mode="serve" (tailnet-only) or set tailscale.mode="off".`, remediation: `Run: clawdbot config set gateway.tailscale.mode serve (or "off" to disable)`,
}); });
} else if (tailscaleMode === "serve") { } else if (tailscaleMode === "serve") {
findings.push({ findings.push({
@ -327,7 +326,7 @@ function collectGatewayConfigFindings(
title: "Control UI allows insecure HTTP auth", title: "Control UI allows insecure HTTP auth",
detail: detail:
"gateway.controlUi.allowInsecureAuth=true allows token-only auth over HTTP and skips device identity.", "gateway.controlUi.allowInsecureAuth=true allows token-only auth over HTTP and skips device identity.",
remediation: "Disable it or switch to HTTPS (Tailscale Serve) or localhost.", remediation: `Run: clawdbot config set gateway.controlUi.allowInsecureAuth false`,
}); });
} }
@ -338,7 +337,7 @@ function collectGatewayConfigFindings(
title: "DANGEROUS: Control UI device auth disabled", title: "DANGEROUS: Control UI device auth disabled",
detail: detail:
"gateway.controlUi.dangerouslyDisableDeviceAuth=true disables device identity checks for the Control UI.", "gateway.controlUi.dangerouslyDisableDeviceAuth=true disables device identity checks for the Control UI.",
remediation: "Disable it unless you are in a short-lived break-glass scenario.", remediation: `Run: clawdbot config set gateway.controlUi.dangerouslyDisableDeviceAuth false`,
}); });
} }
@ -490,7 +489,7 @@ async function collectChannelSecurityFindings(params: {
severity: "critical", severity: "critical",
title: `${input.label} DMs are open`, title: `${input.label} DMs are open`,
detail: `${policyPath}="open" allows anyone to DM the bot.`, detail: `${policyPath}="open" allows anyone to DM the bot.`,
remediation: `Use pairing/allowlist; if you really need open DMs, ensure ${allowFromKey} includes "*".`, remediation: `Run: clawdbot config set ${policyPath} allowlist (or add "*" to ${allowFromKey} if open DMs are required)`,
}); });
if (!hasWildcard) { if (!hasWildcard) {
findings.push({ findings.push({
@ -519,7 +518,7 @@ async function collectChannelSecurityFindings(params: {
title: `${input.label} DMs share the main session`, title: `${input.label} DMs share the main session`,
detail: detail:
"Multiple DM senders currently share the main session, which can leak context across users.", "Multiple DM senders currently share the main session, which can leak context across users.",
remediation: 'Set session.dmScope="per-channel-peer" to isolate DM sessions per sender.', remediation: `Run: clawdbot config set session.dmScope per-channel-peer`,
}); });
} }
}; };
@ -596,8 +595,7 @@ async function collectChannelSecurityFindings(params: {
title: "Discord slash commands are unrestricted", title: "Discord slash commands are unrestricted",
detail: detail:
"commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.", "commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.",
remediation: remediation: `Run: clawdbot config set commands.useAccessGroups true`,
"Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds.<id>.users (or channels.discord.guilds.<id>.channels.<channel>.users).",
}); });
} else if ( } else if (
useAccessGroups && useAccessGroups &&
@ -612,8 +610,7 @@ async function collectChannelSecurityFindings(params: {
title: "Discord slash commands have no allowlists", title: "Discord slash commands have no allowlists",
detail: detail:
"Discord slash commands are enabled, but neither an owner allowFrom list nor any per-guild/channel users allowlist is configured; /… commands will be rejected for everyone.", "Discord slash commands are enabled, but neither an owner allowFrom list nor any per-guild/channel users allowlist is configured; /… commands will be rejected for everyone.",
remediation: remediation: `Run: clawdbot pair discord (recommended), or: clawdbot config set channels.discord.dm.allowFrom '["YOUR_USER_ID"]'`,
"Add your user id to channels.discord.dm.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds.<id>.users.",
}); });
} }
} }
@ -650,7 +647,7 @@ async function collectChannelSecurityFindings(params: {
title: "Slack slash commands bypass access groups", title: "Slack slash commands bypass access groups",
detail: detail:
"Slack slash/native commands are enabled while commands.useAccessGroups=false; this can allow unrestricted /… command execution from channels/users you didn't explicitly authorize.", "Slack slash/native commands are enabled while commands.useAccessGroups=false; this can allow unrestricted /… command execution from channels/users you didn't explicitly authorize.",
remediation: "Set commands.useAccessGroups=true (recommended).", remediation: `Run: clawdbot config set commands.useAccessGroups true`,
}); });
} else { } else {
const dmAllowFromRaw = (account as { dm?: { allowFrom?: unknown } } | null)?.dm const dmAllowFromRaw = (account as { dm?: { allowFrom?: unknown } } | null)?.dm
@ -672,8 +669,7 @@ async function collectChannelSecurityFindings(params: {
title: "Slack slash commands have no allowlists", title: "Slack slash commands have no allowlists",
detail: detail:
"Slack slash/native commands are enabled, but neither an owner allowFrom list nor any channels.<id>.users allowlist is configured; /… commands will be rejected for everyone.", "Slack slash/native commands are enabled, but neither an owner allowFrom list nor any channels.<id>.users allowlist is configured; /… commands will be rejected for everyone.",
remediation: remediation: `Run: clawdbot pair slack (recommended), or: clawdbot config set channels.slack.dm.allowFrom '["YOUR_USER_ID"]'`,
"Approve yourself via pairing (recommended), or set channels.slack.dm.allowFrom and/or channels.slack.channels.<id>.users.",
}); });
} }
} }
@ -765,8 +761,7 @@ async function collectChannelSecurityFindings(params: {
title: "Telegram group allowlist contains wildcard", title: "Telegram group allowlist contains wildcard",
detail: detail:
'Telegram group sender allowlist contains "*", which allows any group member to run /… commands and control directives.', 'Telegram group sender allowlist contains "*", which allows any group member to run /… commands and control directives.',
remediation: remediation: `Run: clawdbot config set channels.telegram.groupAllowFrom '["YOUR_USER_ID"]' (remove "*" and use explicit IDs)`,
'Remove "*" from channels.telegram.groupAllowFrom and pairing store; prefer explicit user ids/usernames.',
}); });
continue; continue;
} }
@ -786,8 +781,7 @@ async function collectChannelSecurityFindings(params: {
detail: detail:
`Telegram group access is enabled but no sender allowlist is configured; this allows any group member to invoke /… commands` + `Telegram group access is enabled but no sender allowlist is configured; this allows any group member to invoke /… commands` +
(skillsEnabled ? " (including skill commands)." : "."), (skillsEnabled ? " (including skill commands)." : "."),
remediation: remediation: `Run: clawdbot pair telegram (recommended), or: clawdbot config set channels.telegram.groupAllowFrom '["YOUR_USER_ID"]'`,
"Approve yourself via pairing (recommended), or set channels.telegram.groupAllowFrom (or per-group groups.<id>.allowFrom).",
}); });
} }
} }