Metadata-Version: 2.4
Name: clawdbot-security-scanner
Version: 1.0.0
Summary: Security scanner for Clawdbot configuration and state.
Author: Yevhen Bobrov
License: MIT
Project-URL: Homepage, https://github.com/yevhen/clawdbot-security-scanner
Project-URL: Repository, https://github.com/yevhen/clawdbot-security-scanner
Keywords: security,scanner,clawdbot
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Environment :: Console
Classifier: Topic :: Security
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: json5
Requires-Dist: rich
Dynamic: license-file

# Clawdbot Security Scanner

A comprehensive security auditing tool for Clawdbot installations. Includes detailed vulnerability report.  

## Quick start

To run the scanner use the commands below.

### Option A: shell script

These scripts will install **uv** if it is missing.

- **macOS / Linux**:
  ```bash
  ./run-scanner.sh
  # or
  bash run-scanner.sh
  ```
- **Windows (PowerShell)**:
  ```powershell
  .\run-scanner.ps1
  # or (if execution policy blocks it)
  powershell -ExecutionPolicy Bypass -File .\run-scanner.ps1
  ```

### Option B: run directly with uv

The scanner is Python-based and uses **uv** (it installs dependencies automatically).

1. Install uv (one-time): https://astral.sh/uv
2. Run the scanner:
   ```bash
   ./scan.py
   # Windows or non-executable script
   uv run scan.py
   ```
3. Common options:
   ```bash
   ./scan.py --config ~/.clawdbot/clawdbot.json
   ./scan.py --format pretty
   ./scan.py --format markdown
   ./scan.py --format json
   ```

### Option C: run without cloning (pipx / uvx)

One-shot run (no install):
```bash
pipx run clawdbot-security-scanner
# or
uvx clawdbot-security-scanner
```

Install once (adds a `clawdbot-security-scanner` command):
```bash
pipx install clawdbot-security-scanner
clawdbot-security-scanner --config ~/.clawdbot/clawdbot.json
# alias
clawdbot-scan --config ~/.clawdbot/clawdbot.json
```

## Report Metadata

- **Report date:** 2026-01-26
- **Report version:** 1.0
- **Scanner:** `security-scanner/scan.py`
- **Checks:** 37
- **Method:** Static analysis of configuration and state directory permissions
- **Generated by:** AI Agent (GPT-5.2 Codex, High)

## Executive Summary

**Overall risk rating: High.** The scanner identifies multiple critical and high‑severity misconfigurations that can expose the gateway, bypass authentication, or allow command execution. The most severe risks are public exposure of the gateway or control plane, webhook signature bypass in voice integrations, and plaintext secrets in configuration files.

**Top priority fixes (P0):**

1. Disable Tailscale Funnel on gateways (`gateway.tailscale.mode=funnel`).
2. Ensure browser control is loopback‑bound or protected by a control token.
3. Remove plaintext secrets from config files (Nostr, Matrix, MS Teams, Voice‑call, Nextcloud Talk, Tlon).
4. Disable insecure Control UI auth (`gateway.controlUi.allowInsecureAuth=true`).
5. Ensure `system.run` is denied for nodes when the nodes tool is enabled.

## Scope & Methodology

**In scope:**

- Clawdbot configuration (`clawdbot.json`) and related state directories.
- Authentication policies, tool allowlists/denylists, sandbox settings.
- Channel DM and group allowlist policy enforcement.
- Extension/plugin configuration (Matrix, Nostr, Voice, Teams, etc.).

**Out of scope:**

- Runtime network testing, active exploitation, fuzzing, or penetration testing.
- UI/UX security (CSRF/XSS) beyond config‑based flags.
- Infrastructure hardening (OS‑level ACLs beyond file perms in state dirs).

**Methodology:**

- Static review of security‑sensitive code paths.
- Configuration/permission checks encoded as deterministic scanner rules.
- Evidence collection via config and state directory inspection only.

## Severity Model

| Severity | Impact | Likelihood |
|---|---|---|
| **Critical** | Full compromise, public exposure, or unauthenticated control | High if misconfigured |
| **High** | Auth bypass, command execution, or credential exposure | Moderate‑high |
| **Medium** | Cross‑channel leakage, SSRF, or elevated access | Moderate |
| **Low** | Reduced isolation or unintended capability | Low‑moderate |

## Findings Index

| Check ID | Title | Severity (on fail) | Scope |
|---|---|---|---|
| `tailscale-serve-bypass` | Tailscale Serve bypasses gateway token/password | Medium | Gateway Core & Authentication |
| `tailscale-funnel` | Gateway exposed via Tailscale Funnel | Critical | Gateway Core & Authentication |
| `control-ui-insecure` | Control UI allows insecure auth | High | Gateway Core & Authentication |
| `plaintext-gateway` | Plaintext gateway traffic on non-loopback | High | Gateway Core & Authentication |
| `token-reuse` | Gateway token reused across surfaces | Medium | Gateway Core & Authentication |
| `secret-perms` | Secrets stored on disk with loose perms | High | Gateway Core & Authentication |
| `gateway-token-strength` | Weak or short gateway token | Medium (High if missing) | Gateway Core & Authentication |
| `device-token-rotation` | Long-lived device auth tokens | Medium | Gateway Core & Authentication |
| `dm-policy-open` | Public DMs enabled (dmPolicy=open) | High | Channel DM Policies & Allowlists |
| `allowfrom-wildcard` | Wildcard allowFrom permits any sender | High | Channel DM Policies & Allowlists |
| `group-dm-open` | Group DMs enabled without allowlist | Medium | Channel DM Policies & Allowlists |
| `group-allowlist-wildcard` | Group allowlist wildcard opens group senders | High | Channel DM Policies & Allowlists |
| `discord-group-allowlist-no-channels` | Discord allowlist without channel restrictions | Medium | Channel DM Policies & Allowlists |
| `dm-scope-main-multi` | Shared DM session scope across multiple senders | Medium | Channel DM Policies & Allowlists |
| `sandbox-mode-not-all` | Sandbox disabled for main sessions | High | Agent Sandbox & Tool Isolation |
| `tool-allowlist-plugin-only` | Tool allowlist ignored (plugin-only entries) | Medium | Agent Sandbox & Tool Isolation |
| `sandbox-image-auto-allow` | Sandbox tool allowlist still permits image | Low | Agent Sandbox & Tool Isolation |
| `browser-control-exposed` | Browser control server exposed without token | Critical | Agent Sandbox & Tool Isolation |
| `sandbox-host-browser-control` | Sandbox can control host browser | High | Agent Sandbox & Tool Isolation |
| `cron-system-event-injection` | Cron tool can inject system events | High | Agent Sandbox & Tool Isolation |
| `hooks-workspace-injection` | Workspace hooks writable by agents | High | Agent Sandbox & Tool Isolation |
| `extension-dm-policy-open` | Extension channel DMs open (dmPolicy=open) | High | Plugin/Extension Security |
| `extension-allowfrom-wildcard` | Extension wildcard allowFrom permits any sender | High | Plugin/Extension Security |
| `extension-group-policy-open` | Extension channel groups open (groupPolicy=open) | Medium | Plugin/Extension Security |
| `nostr-private-key-config` | Nostr private key stored in config | Critical | Plugin/Extension Security |
| `matrix-password-config` | Matrix password stored in config | High | Plugin/Extension Security |
| `msteams-app-password-config` | MS Teams app password stored in config | High | Plugin/Extension Security |
| `voice-call-skip-signature` | Voice-call webhook signature verification skipped | Critical | Plugin/Extension Security |
| `voice-call-inbound-open` | Voice-call inbound policy open | High | Plugin/Extension Security |
| `voice-call-credentials-config` | Voice-call provider credentials in config | High | Plugin/Extension Security |
| `voice-call-ngrok-free-tier` | Voice-call ngrok free tier signature bypass | Medium | Plugin/Extension Security |
| `nextcloud-talk-secret-config` | Nextcloud Talk bot secret in config | Medium | Plugin/Extension Security |
| `tlon-code-config` | Tlon/Urbit access code in config | Medium | Plugin/Extension Security |
| `whatsapp-creds-perms` | WhatsApp Web credentials readable by other users | High | Web Interfaces, Nodes & Integrations |
| `nodes-system-run` | Nodes tool can invoke system.run | High | Web Interfaces, Nodes & Integrations |
| `media-url-prefer` | Media understanding prefers URL attachments | Medium | Web Interfaces, Nodes & Integrations |
| `memory-lancedb-exposure` | Memory (LanceDB) data shared across senders | Medium | Web Interfaces, Nodes & Integrations |

---

## Detailed Findings by Scope

### Scope 1: Gateway Core & Authentication Security

**Files Examined:**

- `src/gateway/server/`
- `src/gateway/server-methods/`
- `src/gateway/protocol/`
- `src/security/`
- `src/infra/device-auth-store.ts`
- `src/infra/device-pairing.ts`
- `src/infra/tls/`
- `src/infra/tailscale.ts`
- `src/infra/ssh-tunnel.ts`
- `src/config/` (auth-related schemas)

**Research Questions:**

- What happens with misconfigured `gateway.auth.mode`?
- Are there bypass paths when `gateway.bind` is not loopback?
- How does Tailscale Serve/Funnel auth interact with password requirements?
- Device pairing token security and expiration
- TLS certificate validation gaps

Findings (expand for details):

<details>
<summary><strong>Tailscale Serve identity bypass</strong> (`tailscale-serve-bypass`) — Medium</summary>

- **Misconfiguration:** `gateway.tailscale.mode=serve` with `gateway.auth.allowTailscale=true`
- **Attack vector:** Tailnet users (or local header spoofing) can authenticate without the gateway token/password.
- **Root cause:** Serve mode accepts Tailscale identity headers as auth when `allowTailscale=true`, skipping shared auth.
- **Mitigation:** Set `gateway.auth.allowTailscale=false` or disable Tailscale Serve.

</details>

<details>
<summary><strong>Gateway exposed via Tailscale Funnel</strong> (`tailscale-funnel`) — Critical</summary>

- **Misconfiguration:** `gateway.tailscale.mode=funnel`
- **Attack vector:** Public internet access; brute-force risk on shared password.
- **Root cause:** Funnel publishes the gateway to the public internet with shared auth.
- **Mitigation:** Prefer `tailscale.mode=serve` or set a strong password and restrict access.

</details>

<details>
<summary><strong>Control UI insecure auth</strong> (`control-ui-insecure`) — High</summary>

- **Misconfiguration:** `gateway.controlUi.allowInsecureAuth=true`
- **Attack vector:** Token-only auth over HTTP can be intercepted; device identity is skipped.
- **Root cause:** Insecure auth bypasses device identity checks and relies on HTTP token only.
- **Mitigation:** Disable `allowInsecureAuth` or serve Control UI over HTTPS.

</details>

<details>
<summary><strong>Plaintext gateway traffic</strong> (`plaintext-gateway`) — High</summary>

- **Misconfiguration:** `gateway.bind != loopback` with `gateway.tls.enabled=false` and/or `gateway.remote.url` uses `ws://`
- **Attack vector:** Network sniffing/MITM captures tokens/passwords or hijacks sessions.
- **Root cause:** Non-loopback gateway traffic is sent without TLS.
- **Mitigation:** Enable TLS for LAN binds and use `wss://` for remote gateways.

</details>

<details>
<summary><strong>Gateway token reused across surfaces</strong> (`token-reuse`) — Medium</summary>

- **Misconfiguration:** `gateway.auth.token` equals `hooks.token` or `browser.controlToken`
- **Attack vector:** Compromise of hooks or browser control token grants gateway access.
- **Root cause:** Shared secret reused across trust boundaries.
- **Mitigation:** Use distinct tokens per surface.

</details>

<details>
<summary><strong>Secrets stored on disk with loose perms</strong> (`secret-perms`) — High</summary>

- **Misconfiguration:** `clawdbot.json` or backups are group/world readable, writable, or symlinked
- **Attack vector:** Local users read/modify gateway secrets or auth config.
- **Root cause:** Secrets stored on disk without strict permissions.
- **Mitigation:** `chmod 600` on `clawdbot.json` and tighten backup perms.

</details>

<details>
<summary><strong>Weak or short gateway token</strong> (`gateway-token-strength`) — Medium (High if missing)</summary>

- **Misconfiguration:** `gateway.auth.mode=token` with missing token or token length < 24
- **Attack vector:** Missing/short tokens are easier to brute-force or fail to enforce auth.
- **Root cause:** Token mode relies on a shared secret; short or missing tokens reduce security.
- **Mitigation:** Generate a long random token (24+ chars) or set env-based token.

</details>

<details>
<summary><strong>Long-lived device auth tokens</strong> (`device-token-rotation`) — Medium</summary>

- **Misconfiguration:** Device token last rotated > 90 days ago
- **Attack vector:** Stolen device tokens remain valid indefinitely.
- **Root cause:** Device tokens lack expiration unless manually rotated/revoked.
- **Mitigation:** Rotate or revoke old device tokens.

</details>

---

### Scope 2: Channel DM Policies & Allowlists

**Files Examined:**

- `src/channels/`
- `src/channels/allowlists/`
- `src/pairing/`
- `src/routing/`
- `src/telegram/` (bot/, monitor/)
- `src/discord/` (monitor/)
- `src/slack/` (http/, monitor/)
- `src/signal/` (monitor/)
- `src/imessage/` (monitor/)
- `src/whatsapp/`
- `src/config/sessions/`

**Research Questions:**

- What happens when `dmPolicy="open"` + `allowFrom=["*"]`?
- Pairing code brute-force potential
- Group allowlist bypass scenarios
- Cross-channel routing leaks
- requireMention bypass in groups

Findings (expand for details):

<details>
<summary><strong>Public DMs enabled</strong> (`dm-policy-open`) — High</summary>

- **Misconfiguration:** `channels.<channel>.dmPolicy=open` or `channels.<channel>.dm.policy=open`
- **Attack vector:** Any sender can DM the bot; pairing/allowlist bypass with routing hijack risk.
- **Root cause:** DM policy bypasses pairing/allowlist and updates last‑route on the shared session key.
- **Mitigation:** Use `dmPolicy=pairing` or `dmPolicy=allowlist` with explicit `allowFrom`.

</details>

<details>
<summary><strong>Wildcard allowFrom permits any sender</strong> (`allowfrom-wildcard`) — High</summary>

- **Misconfiguration:** `allowFrom` contains `*`
- **Attack vector:** Allowlists match everyone; authorization bypass and control-command bypass.
- **Root cause:** For Slack/Discord/Telegram/iMessage/Signal, `allowFrom` is also used for command authorization, so any group sender can issue control commands and bypass `requireMention` via `resolveMentionGatingWithBypass`.
- **Mitigation:** Remove `*`, use explicit sender IDs/handles, and keep group allowlists separate.

</details>

<details>
<summary><strong>Group DMs enabled without allowlist</strong> (`group-dm-open`) — Medium</summary>

- **Misconfiguration:** Slack/Discord `dm.groupEnabled=true` with empty or wildcard `groupChannels`
- **Attack vector:** Any Slack/Discord group DM can trigger the bot without filtering.
- **Root cause:** Group DMs are only filtered when `groupChannels` is set.
- **Mitigation:** Define explicit `dm.groupChannels` or disable `groupEnabled`.

</details>

<details>
<summary><strong>Group allowlist wildcard opens senders</strong> (`group-allowlist-wildcard`) — High</summary>

- **Misconfiguration:** `groupPolicy=allowlist` with wildcard `groupAllowFrom` or fallback wildcard `allowFrom`
- **Attack vector:** Any group sender can reach the bot despite allowlist mode.
- **Root cause:** Group allowlist falls back to `allowFrom`; a wildcard negates sender filtering.
- **Mitigation:** Set explicit `groupAllowFrom` (no `*`) or intentionally use `groupPolicy=open`.

</details>

<details>
<summary><strong>Discord allowlist without channel restrictions</strong> (`discord-group-allowlist-no-channels`) — Medium</summary>

- **Misconfiguration:** `channels.discord.guilds.<id>` missing `channels` allowlist
- **Attack vector:** All channels in the allowlisted guild become eligible.
- **Root cause:** Discord allowlist policy only applies when per‑channel lists are configured.
- **Mitigation:** Add explicit channel allowlists under `guilds.<id>.channels`.

</details>

<details>
<summary><strong>Shared DM session scope</strong> (`dm-scope-main-multi`) — Medium</summary>

- **Misconfiguration:** `session.dmScope=main` with multiple `allowFrom` entries
- **Attack vector:** Multiple DM senders share a session, enabling cross-channel reply leaks.
- **Root cause:** DM routing uses a single session key in `main` scope.
- **Mitigation:** Use `dmScope=per-peer` or `per-channel-peer` and limit allowlists.

</details>

---

### Scope 3: Agent Sandbox, Tools & Command Execution

**Files Examined:**

- `src/agents/sandbox/`
- `src/agents/tools/`
- `src/infra/exec-safety.ts`
- `src/infra/exec-approvals.ts`
- `src/browser/`
- `src/browser/routes/`
- `src/cron/`
- `src/cron/isolated-agent/`
- `src/process/`
- `src/hooks/`
- `src/hooks/bundled/`

**Research Questions:**

- Sandbox escape when `sandbox.mode` is misconfigured
- Tool allowlist/denylist bypass scenarios
- Browser CDP control exposure
- Cron job injection with elevated privileges
- Hook injection vectors
- system.run permission escalation via nodes

Findings (expand for details):

<details>
<summary><strong>Sandbox disabled for main sessions</strong> (`sandbox-mode-not-all`) — High</summary>

- **Misconfiguration:** `sandbox.mode=off` or `non-main` for any agent
- **Attack vector:** Untrusted prompts run tools on the host via the main session.
- **Root cause:** Main session uses host tools when sandbox is off or only applies to non‑main sessions.
- **Mitigation:** Set `agents.defaults.sandbox.mode=all` for untrusted workloads.

</details>

<details>
<summary><strong>Tool allowlist ignored (plugin-only entries)</strong> (`tool-allowlist-plugin-only`) — Medium</summary>

- **Misconfiguration:** `tools.allow` / `agents.*.tools.allow` contains only plugin/unknown entries
- **Attack vector:** Core tools remain available because plugin-only allowlists are stripped.
- **Root cause:** Clawdbot drops allowlists without any core tool or group.
- **Mitigation:** Include at least one core tool/group or add explicit deny rules.

</details>

<details>
<summary><strong>Sandbox allowlist still permits image</strong> (`sandbox-image-auto-allow`) — Low</summary>

- **Misconfiguration:** Sandbox allowlist set without `image` and `image` not denied
- **Attack vector:** `image` tool is auto-added unless explicitly denied.
- **Root cause:** Sandbox policy includes `image` by default.
- **Mitigation:** Add `image` to `tools.sandbox.tools.deny` or include it explicitly.

</details>

<details>
<summary><strong>Browser control server exposed without token</strong> (`browser-control-exposed`) — Critical</summary>

- **Misconfiguration:** `browser.controlUrl` bound to non-loopback with no `browser.controlToken`
- **Attack vector:** Remote clients can drive CDP sessions.
- **Root cause:** Browser control server lacks authentication without a control token.
- **Mitigation:** Bind to loopback or set `browser.controlToken` and firewall access.

</details>

<details>
<summary><strong>Sandbox can control host browser</strong> (`sandbox-host-browser-control`) — High</summary>

- **Misconfiguration:** `sandbox.browser.allowHostControl=true` with sandbox active
- **Attack vector:** Sandboxed sessions can drive the host browser (cookies, tabs, auth).
- **Root cause:** `allowHostControl` routes sandbox tool calls to the host control server.
- **Mitigation:** Set `allowHostControl=false` or restrict browser tools for untrusted sessions.

</details>

<details>
<summary><strong>Cron tool can inject system events</strong> (`cron-system-event-injection`) — High</summary>

- **Misconfiguration:** `cron.enabled=true` and `cron` tool allowed by policy
- **Attack vector:** Cron jobs enqueue `systemEvent` payloads into main session (system role).
- **Root cause:** Cron supports systemEvent injection when tool is allowed.
- **Mitigation:** Disable cron or deny the `cron` tool for untrusted sessions.

</details>

<details>
<summary><strong>Workspace hooks writable by agents</strong> (`hooks-workspace-injection`) — High</summary>

- **Misconfiguration:** `hooks.internal.enabled=true` with writable workspace access
- **Attack vector:** Agents can write hook handlers and gain host code execution.
- **Root cause:** Internal hooks dynamically load handler code from workspace/extraDirs.
- **Mitigation:** Set `workspaceAccess=ro/none` for untrusted agents or disable internal hooks.

</details>

---

### Scope 4: Plugin/Extension Channel Security

**Files Examined:**

- `extensions/msteams/src/`
- `extensions/matrix/src/`
- `extensions/zalo/src/`
- `extensions/zalouser/src/`
- `extensions/bluebubbles/src/`
- `extensions/voice-call/src/`
- `extensions/nostr/src/`
- `extensions/mattermost/src/`
- `extensions/nextcloud-talk/src/`
- `extensions/tlon/src/`
- `src/plugins/`
- `src/plugins/runtime/`

**Research Questions:**

- Extension-specific allowlist implementations - are they consistent/secure?
- OAuth token storage and rotation in extensions
- Voice-call authentication (Twilio/Plivo credentials exposure)
- Matrix homeserver trust model vulnerabilities
- Nostr private key exposure risks
- Plugin isolation and cross-plugin data access
- Extension credential storage patterns

Findings (expand for details):

<details>
<summary><strong>Extension DMs open</strong> (`extension-dm-policy-open`) — High</summary>

- **Misconfiguration:** Extension `dmPolicy=open`
- **Attack vector:** Any sender can DM the bot via extension channel.
- **Root cause:** Extension DM policy bypasses pairing/allowlist the same as core channels.
- **Mitigation:** Use `dmPolicy=pairing` or `dmPolicy=allowlist` with explicit `allowFrom`.

</details>

<details>
<summary><strong>Extension wildcard allowFrom</strong> (`extension-allowfrom-wildcard`) — High</summary>

- **Misconfiguration:** Extension `allowFrom` contains `*`
- **Attack vector:** Extension DM allowlists match everyone; authorization bypassed.
- **Root cause:** Wildcard allowlists match all senders.
- **Mitigation:** Replace `*` with explicit sender IDs/handles.

</details>

<details>
<summary><strong>Extension group policy open</strong> (`extension-group-policy-open`) — Medium</summary>

- **Misconfiguration:** Extension `groupPolicy=open`
- **Attack vector:** Any group/room can trigger the bot.
- **Root cause:** Group policies allow all rooms when set to open.
- **Mitigation:** Use `groupPolicy=allowlist` with explicit group IDs.

</details>

<details>
<summary><strong>Nostr private key in config</strong> (`nostr-private-key-config`) — Critical</summary>

- **Misconfiguration:** `channels.nostr.privateKey` plaintext
- **Attack vector:** Local file read exposes Nostr identity; attacker can impersonate.
- **Root cause:** Secret stored directly in config file.
- **Mitigation:** Use env reference: `privateKey: "${NOSTR_PRIVATE_KEY}"`.

</details>

<details>
<summary><strong>Matrix password in config</strong> (`matrix-password-config`) — High</summary>

- **Misconfiguration:** `channels.matrix.password` plaintext
- **Attack vector:** Local file read exposes Matrix credentials for account takeover.
- **Root cause:** Password stored directly in config file.
- **Mitigation:** Use env reference or access token.

</details>

<details>
<summary><strong>MS Teams app password in config</strong> (`msteams-app-password-config`) — High</summary>

- **Misconfiguration:** `channels.msteams.appPassword` plaintext
- **Attack vector:** Local file read exposes Azure AD client secret.
- **Root cause:** App password stored directly in config file.
- **Mitigation:** Use env reference: `appPassword: "${MSTEAMS_APP_PASSWORD}"`.

</details>

<details>
<summary><strong>Voice-call signature verification skipped</strong> (`voice-call-skip-signature`) — Critical</summary>

- **Misconfiguration:** `voice-call.skipSignatureVerification=true`
- **Attack vector:** Attacker can forge Twilio/Plivo webhooks to inject fake calls/transcripts.
- **Root cause:** Signature verification is explicitly bypassed.
- **Mitigation:** Set `skipSignatureVerification=false`.

</details>

<details>
<summary><strong>Voice-call inbound policy open</strong> (`voice-call-inbound-open`) — High</summary>

- **Misconfiguration:** `voice-call.inboundPolicy=open`
- **Attack vector:** Any phone number can call the bot.
- **Root cause:** Inbound policy allows any caller.
- **Mitigation:** Use `inboundPolicy=allowlist` or disable inbound calls.

</details>

<details>
<summary><strong>Voice-call credentials in config</strong> (`voice-call-credentials-config`) — High</summary>

- **Misconfiguration:** Provider auth tokens stored in plaintext (Twilio/Plivo/Telnyx)
- **Attack vector:** Local file read exposes telephony credentials (billing/call control).
- **Root cause:** Secrets stored directly in config.
- **Mitigation:** Use env references for provider credentials.

</details>

<details>
<summary><strong>Voice-call ngrok free-tier bypass</strong> (`voice-call-ngrok-free-tier`) — Medium</summary>

- **Misconfiguration:** `tunnel.provider=ngrok` with `allowNgrokFreeTier=true`
- **Attack vector:** Signature verification failures are silently allowed on ngrok URLs.
- **Root cause:** Free‑tier setting allows signature failures.
- **Mitigation:** Set `allowNgrokFreeTier=false` or use paid tier with custom domain.

</details>

<details>
<summary><strong>Nextcloud Talk secret in config</strong> (`nextcloud-talk-secret-config`) — Medium</summary>

- **Misconfiguration:** `channels.nextcloud-talk.botSecret` plaintext
- **Attack vector:** Local file read exposes webhook signing secret.
- **Root cause:** Bot secret stored directly in config.
- **Mitigation:** Use env ref or `botSecretFile`.

</details>

<details>
<summary><strong>Tlon/Urbit access code in config</strong> (`tlon-code-config`) — Medium</summary>

- **Misconfiguration:** `channels.tlon.code` plaintext
- **Attack vector:** Local file read exposes Urbit ship access code.
- **Root cause:** Access code stored directly in config.
- **Mitigation:** Use env reference: `code: "${TLON_CODE}"`.

</details>

---

### Scope 5: Web Interfaces, Nodes & External Integrations

**Files Examined:**

- `src/web/`
- `src/web/inbound/`
- `src/web/auto-reply/`
- `src/node-host/`
- `src/providers/`
- `src/media/`
- `src/media-understanding/`
- `src/infra/net/ssrf.ts`
- `extensions/google-antigravity-auth/`
- `extensions/google-gemini-cli-auth/`
- `extensions/qwen-portal-auth/`
- `extensions/copilot-proxy/`
- `extensions/memory-lancedb/`

**Research Questions:**

- WebChat authentication and session hijacking risks
- Control UI CSRF/XSS vectors
- Node protocol command injection
- SSRF protection gaps in media fetching
- OAuth redirect URI validation
- Credential storage in `~/.clawdbot/credentials/`
- Memory extension data exposure

Findings (expand for details):

<details>
<summary><strong>WhatsApp credentials readable by other users</strong> (`whatsapp-creds-perms`) — High</summary>

- **Misconfiguration:** WhatsApp authDir/`creds.json` are group/world readable/writable or symlinked
- **Attack vector:** Local users can copy `creds.json` to hijack WhatsApp Web sessions.
- **Root cause:** WhatsApp session state is stored on disk without enforced perms.
- **Mitigation:** `chmod 700` on auth dirs and `chmod 600` on `creds.json`.

</details>

<details>
<summary><strong>Nodes tool can invoke `system.run`</strong> (`nodes-system-run`) — High</summary>

- **Misconfiguration:** `gateway.nodes.denyCommands` missing `system.run` while nodes tool allowed
- **Attack vector:** Remote node invoke can execute arbitrary commands on paired nodes.
- **Root cause:** Node command policy allows `system.run` unless explicitly denied.
- **Mitigation:** Add `system.run` to `gateway.nodes.denyCommands` or deny the nodes tool.

</details>

<details>
<summary><strong>Media URL SSRF exposure</strong> (`media-url-prefer`) — Medium</summary>

- **Misconfiguration:** `tools.media.<capability>.attachments.prefer=url` with media understanding enabled
- **Attack vector:** Gateway fetches untrusted URLs; SSRF to internal services.
- **Root cause:** URL attachments are fetched without strict host validation in some paths.
- **Mitigation:** Prefer file/path attachments or disable URL preference.

</details>

<details>
<summary><strong>Memory (LanceDB) exposure across senders</strong> (`memory-lancedb-exposure`) — Medium</summary>

- **Misconfiguration:** `memory-lancedb` autoCapture/autoRecall enabled with multiple DM senders allowed
- **Attack vector:** One sender can access memories captured from other senders/groups.
- **Root cause:** LanceDB stores a shared DB; auto‑recall does not isolate senders.
- **Mitigation:** Limit allowlists to a single owner or disable autoCapture/autoRecall.

</details>

---

## Appendix A: Running the Scanner

```bash
# Run the scanner from the repo (default: pretty output in a TTY; markdown when piped)
./security-scanner/scan.py

# Specify config file
./security-scanner/scan.py --config ~/.clawdbot/clawdbot.json

# Choose an output format
./security-scanner/scan.py --format pretty
./security-scanner/scan.py --format markdown
./security-scanner/scan.py --format json

# Run without cloning (PyPI)
pipx run clawdbot-security-scanner
uvx clawdbot-security-scanner
```

## Appendix B: Validation / Tests

```bash
./security-scanner/test.py
```

This script runs representative configurations and asserts that expected failures are detected for each check.
