From 5ae78f5513e2a4025db6f5e9f3412b6c80ac9a5a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Jan 2026 04:02:30 +0000 Subject: [PATCH] security: simplify remediation steps with copy-paste CLI commands --- src/security/audit-extra.ts | 19 ++++++++----------- src/security/audit.ts | 34 ++++++++++++++-------------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/security/audit-extra.ts b/src/security/audit-extra.ts index 89b1f3e63..e3cf9705f 100644 --- a/src/security/audit-extra.ts +++ b/src/security/audit-extra.ts @@ -138,8 +138,7 @@ export function collectSecretsInConfigFindings(cfg: ClawdbotConfig): SecurityAud title: "Gateway password is stored in config", detail: "gateway.auth.password is set in the config file; prefer environment variables for secrets when possible.", - remediation: - "Prefer CLAWDBOT_GATEWAY_PASSWORD (env) and remove gateway.auth.password from disk.", + remediation: `Run: export CLAWDBOT_GATEWAY_PASSWORD="your-password" && clawdbot config unset gateway.auth.password`, }); } @@ -188,7 +187,7 @@ export function collectHooksHardeningFindings(cfg: ClawdbotConfig): SecurityAudi title: "Hooks token reuses the Gateway token", detail: "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", title: "Hooks base path is '/'", 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" + lines + 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" + lines + more, - remediation: - "Use the latest, top-tier model for any bot with tools or untrusted inboxes. Avoid Haiku tiers; prefer GPT-5+ and Claude 4.5+.", + remediation: `Run: clawdbot config set agents.defaults.model.primary claude-sonnet-4-20250514 (avoid Haiku; prefer Claude 4.5+ or GPT-5+)`, }); } @@ -506,8 +504,7 @@ export function collectSmallModelRiskFindings(params: { exposureDetail + `\n` + "Small models are not recommended for untrusted inputs.", - remediation: - '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"]).', + remediation: `Run: clawdbot config set agents.defaults.sandbox.mode all && clawdbot config set tools.deny '["group:web","browser"]'`, }); return findings; @@ -602,7 +599,7 @@ export async function collectPluginsTrustFindings(params: { (skillCommandsLikelyExposed ? "\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: `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.", - remediation: `Set groupPolicy="allowlist" and keep elevated allowlists extremely tight.`, + remediation: `Run: clawdbot security audit --fix (auto-sets groupPolicy to allowlist)`, }); } diff --git a/src/security/audit.ts b/src/security/audit.ts index a96213134..1481eccbb 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -273,7 +273,7 @@ function collectGatewayConfigFindings( severity: "critical", title: "Gateway binds beyond loopback without auth", 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. " + "If you expose the Control UI through a reverse proxy, configure trusted proxies " + "so local-client checks cannot be spoofed.", - remediation: - "Set gateway.trustedProxies to your proxy IPs or keep the Control UI local-only.", + remediation: `Run: clawdbot config set gateway.trustedProxies '["127.0.0.1"]' (replace with your proxy IPs)`, }); } @@ -299,7 +298,7 @@ function collectGatewayConfigFindings( detail: "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.", - 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", title: "Tailscale Funnel exposure enabled", 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") { findings.push({ @@ -327,7 +326,7 @@ function collectGatewayConfigFindings( title: "Control UI allows insecure HTTP auth", detail: "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", detail: "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", title: `${input.label} DMs are open`, 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) { findings.push({ @@ -519,7 +518,7 @@ async function collectChannelSecurityFindings(params: { title: `${input.label} DMs share the main session`, detail: "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", 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.", - remediation: - "Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds..users (or channels.discord.guilds..channels..users).", + remediation: `Run: clawdbot config set commands.useAccessGroups true`, }); } else if ( useAccessGroups && @@ -612,8 +610,7 @@ async function collectChannelSecurityFindings(params: { title: "Discord slash commands have no allowlists", 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.", - remediation: - "Add your user id to channels.discord.dm.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds..users.", + remediation: `Run: clawdbot pair discord (recommended), or: clawdbot config set channels.discord.dm.allowFrom '["YOUR_USER_ID"]'`, }); } } @@ -650,7 +647,7 @@ async function collectChannelSecurityFindings(params: { title: "Slack slash commands bypass access groups", 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.", - remediation: "Set commands.useAccessGroups=true (recommended).", + remediation: `Run: clawdbot config set commands.useAccessGroups true`, }); } else { const dmAllowFromRaw = (account as { dm?: { allowFrom?: unknown } } | null)?.dm @@ -672,8 +669,7 @@ async function collectChannelSecurityFindings(params: { title: "Slack slash commands have no allowlists", detail: "Slack slash/native commands are enabled, but neither an owner allowFrom list nor any channels..users allowlist is configured; /… commands will be rejected for everyone.", - remediation: - "Approve yourself via pairing (recommended), or set channels.slack.dm.allowFrom and/or channels.slack.channels..users.", + remediation: `Run: clawdbot pair slack (recommended), or: clawdbot config set channels.slack.dm.allowFrom '["YOUR_USER_ID"]'`, }); } } @@ -765,8 +761,7 @@ async function collectChannelSecurityFindings(params: { title: "Telegram group allowlist contains wildcard", detail: 'Telegram group sender allowlist contains "*", which allows any group member to run /… commands and control directives.', - remediation: - 'Remove "*" from channels.telegram.groupAllowFrom and pairing store; prefer explicit user ids/usernames.', + remediation: `Run: clawdbot config set channels.telegram.groupAllowFrom '["YOUR_USER_ID"]' (remove "*" and use explicit IDs)`, }); continue; } @@ -786,8 +781,7 @@ async function collectChannelSecurityFindings(params: { detail: `Telegram group access is enabled but no sender allowlist is configured; this allows any group member to invoke /… commands` + (skillsEnabled ? " (including skill commands)." : "."), - remediation: - "Approve yourself via pairing (recommended), or set channels.telegram.groupAllowFrom (or per-group groups..allowFrom).", + remediation: `Run: clawdbot pair telegram (recommended), or: clawdbot config set channels.telegram.groupAllowFrom '["YOUR_USER_ID"]'`, }); } }