- Published on
Inside Clawdbot: Infrastructure, Security Subsystems, and the Plugin SDK
- Authors

- Name
- Avasdream
- @avasdream_
This is the third post in my Clawdbot deep dive series. The first covered memory architecture, the second covered the agent system and AI providers. Here I'm looking at everything that makes Clawdbot run reliably as infrastructure: scheduling, security, extensibility, device pairing, and service management.
The source analysis covers ~42K lines across the open-source codebase.
Cron: Scheduling with Isolation
Clawdbot has a built-in cron system that goes beyond simple timer-based execution. It handles three schedule types, runs jobs in isolated agent contexts, detects stuck jobs, and prunes run history.
Three Schedule Types
// One-shot (fire once, optionally delete after)
{ kind: "cron", expr: "0 8 28 1 *", tz: "Europe/Berlin" }
// Recurring (standard cron expressions)
{ kind: "cron", expr: "0 19 * * *", tz: "Europe/Berlin" }
// Interval (every N milliseconds)
{ kind: "interval", everyMs: 1800000 } // Every 30 minutes
Cron expressions use standard 5-field format with timezone support. The tz field accepts IANA timezone names, so "Europe/Berlin" correctly handles CET/CEST transitions.
Isolated Agent Execution
Each cron job runs in its own agent context — separate from your main session. This means:
- A cron job can't see your current conversation
- Cron jobs don't pollute your session with tool calls and intermediate output
- If a cron job fails or hangs, it doesn't block your main session
- Jobs target a specific session via
sessionTarget(usually"main") and deliver their output there
The payload is injected as a system event, and the agent processes it as if it received a message.
Stuck Job Detection
The cron scheduler tracks execution state per job:
{
state: {
nextRunAtMs: 1769583600000,
lastRunAtMs: 1769490000000,
lastStatus: "completed",
lastDurationMs: 45000,
lastError: null
}
}
If a job exceeds its expected duration or fails repeatedly, the scheduler marks it and prevents cascading failures. Jobs can be configured with deleteAfterRun: true for one-shot reminders that clean up after themselves.
Run Log with Pruning
Each job maintains a run log tracking execution history — timestamps, durations, success/failure status, and error messages. Old entries are automatically pruned to prevent unbounded growth. You can inspect recent runs via:
clawdbot cron runs --job <jobId>
Security: 7 Subsystems
Clawdbot's security isn't a single layer — it's seven interlocking subsystems, each addressing a different threat vector.
1. Injection Detection
The first line of defense against prompt injection. Clawdbot's access control philosophy is: identity first, scope second, model last. The assumption is that the model can be manipulated, so the system is designed to limit blast radius when it is.
- DM pairing gates who can even talk to the bot. Unknown senders get a pairing code — messages aren't processed until approved
- Group allowlists restrict which groups the bot participates in
- Mention gating prevents the bot from processing every message in a group — only @mentions trigger it
- Tool policy (the 9-layer system) restricts what tools are available, so even a successful injection can't access tools the session doesn't have
The design treats the model as an untrusted component and enforces policy at the Gateway level, not through prompt instructions.
2. SSRF Prevention with DNS Pinning
When the agent fetches URLs (via web_fetch or browser tools), Clawdbot prevents Server-Side Request Forgery attacks. The web fetch implementation:
- Resolves DNS before connecting and pins the resolved IP
- Blocks private/internal IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, link-local)
- Prevents DNS rebinding — the resolved address is checked against blocklists after resolution, not before
This stops an attacker from crafting a URL that resolves to 127.0.0.1 or an internal service. The agent can fetch public URLs but cannot be tricked into accessing internal infrastructure.
3. Ed25519 Device Identity
Every client (CLI, macOS app, iOS/Android node) generates an Ed25519 keypair on first run. The device identity is derived from the public key fingerprint and used throughout the protocol:
{
device: {
id: "device_fingerprint",
publicKey: "base64-ed25519-pubkey",
signature: "base64-signature",
signedAt: 1737264000000,
nonce: "server-provided-nonce"
}
}
During connection, the Gateway sends a connect.challenge with a random nonce. The client signs this nonce with its private key. The Gateway verifies the signature, confirming the client controls the claimed device identity.
This prevents:
- Replay attacks — Nonces are single-use
- Device impersonation — The private key never leaves the device
- Token theft — Even if a device token is stolen, the attacker can't sign new challenges
Non-local connections (anything not from loopback) must sign the challenge. Local connections can be auto-approved for same-host UX.
4. Exec Safety
Command execution has its own security model beyond the 9-layer tool policy:
Exec approvals are enforced on the execution host (gateway or node):
| Setting | Effect |
|---|---|
security: "deny" | Block all host exec requests |
security: "allowlist" | Only allowlisted commands run |
security: "full" | Everything allowed |
ask: "on-miss" | Prompt the user for unlisted commands |
ask: "always" | Prompt on every command |
Allowlists use glob patterns against resolved binary paths:
{
allowlist: [
{ pattern: "~/Projects/**/bin/rg" },
{ pattern: "/opt/homebrew/bin/*" }
]
}
Each entry tracks last-used timestamps and resolved paths for audit. There's also a safe bins concept — stdin-only utilities like jq, grep, sort, head, tail that can run in allowlist mode without explicit entries, because they can only operate on piped input and reject file path arguments.
Shell chaining (&&, ||, ;) is allowed only when every segment satisfies the allowlist independently.
5. Audit Framework
The built-in security audit (clawdbot security audit) is a runtime scanner that checks 7 categories:
clawdbot security audit # Quick scan
clawdbot security audit --deep # Live Gateway probe
clawdbot security audit --fix # Auto-apply safe defaults
What it checks:
- Inbound access: DM policies, group policies, allowlists — can strangers trigger the bot?
- Tool blast radius: Elevated tools + open rooms — could injection lead to shell access?
- Network exposure: Gateway bind address, auth mode, Tailscale Serve/Funnel
- Browser control: Remote nodes, relay ports, CDP endpoints
- Disk hygiene: File permissions, symlinks, config includes
- Plugins: Extensions loaded without explicit allowlists
- Model hygiene: Legacy models that may be less instruction-hardened
The --fix flag applies safe guardrails: tightens open group policies to allowlists, re-enables sensitive log redaction, and sets restrictive file permissions (700 for ~/.clawdbot, 600 for config files).
6. Formal Verification (TLA+)
This is unusual for an open-source project: Clawdbot maintains formal security models in TLA+ with machine-checked proofs. Each security claim has:
- A positive model that TLC verifies (the property holds across the state space)
- A negative model that produces a counterexample trace (proving the model catches real bugs)
Currently verified claims:
| Property | What It Proves |
|---|---|
| Gateway exposure | Binding beyond loopback without auth enables remote compromise |
| nodes.run pipeline | Requires command allowlist + declared commands + live approval |
| Approval tokens | Tokenized to prevent replay attacks |
| Pairing store | Respects TTL and pending-request caps |
| Ingress gating | Unauthorized control commands can't bypass mention gating |
| Routing isolation | DMs from distinct peers don't collapse into the same session |
This isn't a proof that the TypeScript implementation is bug-free — the models abstract over implementation details. But they provide a machine-checked regression suite for the security-critical protocol logic.
7. Credential Isolation
Credentials are scattered across multiple locations by design — no single file contains everything:
| Credential | Location |
|---|---|
| WhatsApp session | ~/.clawdbot/credentials/whatsapp/*/creds.json |
| Telegram bot token | Config or env var |
| Discord bot token | Config or env var |
| Pairing allowlists | ~/.clawdbot/credentials/*-allowFrom.json |
| Model auth profiles | ~/.clawdbot/agents/*/agent/auth-profiles.json |
| Device tokens | ~/.clawdbot/devices/paired.json |
| Exec approvals | ~/.clawdbot/exec-approvals.json |
File permissions are enforced (600 for sensitive files, 700 for directories). The security audit checks these on every run.
Plugin SDK
Clawdbot's plugin system uses a manifest-based architecture with JSON Schema validation and jiti transpilation (TypeScript → JS at runtime, no build step required).
Plugin Manifest
Every plugin has a clawdbot.plugin.json:
{
"id": "voice-call",
"version": "1.0.0",
"configSchema": {
"type": "object",
"properties": {
"provider": { "type": "string", "enum": ["twilio", "vonage"] },
"apiKey": { "type": "string" }
}
},
"uiHints": {
"apiKey": { "label": "API Key", "sensitive": true },
"provider": { "label": "Provider", "placeholder": "twilio" }
}
}
The configSchema is JSON Schema — validated at Gateway startup. Invalid plugin configs prevent the Gateway from booting, same as invalid core config. The uiHints provide labels and sensitivity markers for the Control UI form renderer.
What Plugins Can Register
Plugins run in-process with the Gateway and can register:
- Gateway RPC methods — Extend the WebSocket protocol with custom request types
- HTTP handlers — Add REST endpoints on the Gateway
- Agent tools — Custom tools the model can call
- CLI commands — Extend
clawdbotwith new subcommands - Background services — Long-running tasks that start with the Gateway
- Skills — Bundled SKILL.md folders loaded when the plugin is enabled
- Auto-reply commands — Execute without invoking the AI agent (slash commands that bypass the model)
Discovery and Precedence
Plugins are discovered from multiple locations, in order:
1. Config paths (plugins.load.paths)
2. Workspace extensions (<workspace>/.clawdbot/extensions/)
3. Global extensions (~/.clawdbot/extensions/)
4. Bundled extensions (shipped with Clawdbot, disabled by default)
First match wins. Bundled plugins must be explicitly enabled via plugins.entries.<id>.enabled or clawdbot plugins enable <id>.
Package Packs
A single directory can contain multiple plugins via package.json:
{
"name": "my-pack",
"clawdbot": {
"extensions": ["./src/safety.ts", "./src/tools.ts"]
}
}
Each entry becomes a plugin. The id is derived from name/<fileBase>.
Plugin Slots (Exclusive Categories)
Some plugin categories are mutually exclusive. Only one plugin can own a slot:
{
plugins: {
slots: {
memory: "memory-core" // or "memory-lancedb" or "none"
}
}
}
If multiple plugins declare the same slot, only the selected one loads.
Security Considerations
Plugins are trusted code — they run in the Gateway process with full access. The SDK provides guardrails:
- Allow/deny lists —
plugins.allowandplugins.denycontrol which plugins load - Config validation — JSON Schema prevents misconfiguration
- NPM install isolation —
clawdbot plugins installusesnpm pack+npm install --omit=dev, but lifecycle scripts can execute code during install - Explicit enable — Bundled plugins default to disabled
The recommendation: pin exact versions, inspect code before enabling, prefer official plugins.
Node Pairing
Nodes are companion devices (macOS, iOS, Android, headless Linux) that connect to the Gateway and provide capabilities the Gateway doesn't have — cameras, screens, local canvases, location services.
Crypto Pairing Flow
- Node connects to the Gateway WebSocket with
role: "node"and presents its Ed25519 device identity - Gateway sends a
connect.challengewith a random nonce - Node signs the nonce and returns it
- Gateway verifies the signature and creates a pairing request
- Owner approves via
clawdbot devices approve <requestId> - Gateway issues a device token scoped to the node role and its declared capabilities
# List pending pairing requests
clawdbot devices list
# Approve a node
clawdbot devices approve abc123
# Check connected nodes
clawdbot nodes status
Capability Declaration
Nodes declare what they can do at connect time:
{
role: "node",
caps: ["camera", "canvas", "screen", "location", "voice"],
commands: ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
permissions: {
"camera.capture": true,
"screen.record": false
}
}
The Gateway treats these as claims and enforces server-side allowlists. A node declaring screen.record doesn't automatically get access — the user must also approve screen recording permissions.
macOS Companion App
The macOS app can run in node mode, connecting to the Gateway's WebSocket and exposing local capabilities:
- Canvas: WebView for rendering agent-generated HTML/A2UI
- Camera: Front/back camera snapshots and video clips
- Screen: Screen recording
- system.run: Execute commands on the Mac (gated by exec approvals)
The app manages its own exec approval UI — a settings panel where you configure security policy, allowlists, and per-agent overrides.
Browser Proxy
Nodes can proxy browser control. The Gateway can invoke browser actions on a remote node's browser, enabling scenarios where:
- The Gateway runs on a headless VPS
- A macOS node provides the browser for web automation
- CDP (Chrome DevTools Protocol) connections are proxied through the node
This is gated by sandbox policy — sandbox.browser.allowHostControl must be explicitly enabled, and custom control URLs go through allowlists.
Daemon Management
Clawdbot runs as a background service across macOS, Linux, and Windows. Each platform uses its native service manager.
Platform Support
| Platform | Service Manager | Command |
|---|---|---|
| macOS | launchd | ~/Library/LaunchAgents/com.clawdbot.gateway.plist |
| Linux | systemd (user) | ~/.config/systemd/user/clawdbot-gateway.service |
| Windows | Task Scheduler (schtasks) | Scheduled task at logon |
Installation
# Install during onboarding (recommended)
clawdbot onboard --install-daemon
# Or standalone
clawdbot service install
clawdbot service start
clawdbot service status
On Linux, user services require linger to survive logout:
sudo loginctl enable-linger "$USER"
Multi-Profile Support
Multiple Gateway instances can run simultaneously with separate profiles:
clawdbot --profile work gateway
clawdbot --profile personal gateway
Each profile gets its own state directory, config, and service instance. This enables running separate agents for different contexts (work vs personal) on the same machine.
Health and Recovery
The daemon is configured for automatic restart on failure:
- launchd:
KeepAliveensures restart on crash - systemd:
Restart=on-failurewith configurable restart delay - schtasks: Logon trigger re-launches after reboot
Health checks are available via CLI:
clawdbot status # Quick overview
clawdbot health # Gateway connectivity
clawdbot doctor # Full diagnostic (config, deps, auth)
clawdbot gateway probe # Live WebSocket probe
How It All Connects
These systems are independent but composable:
- Cron jobs can trigger security audits, memory maintenance, or notification digests — each running in an isolated agent context
- Plugins can register new tools that flow through the same 9-layer policy engine as built-in tools
- Nodes extend the Gateway's capabilities while maintaining the same crypto identity and approval model
- The daemon ensures everything stays running, with health checks that validate the full stack
The security subsystems work in layers: device identity authenticates connections, pairing gates access, tool policy restricts capabilities, exec approvals guard command execution, sandbox isolates untrusted sessions, the audit framework catches misconfigurations, and the TLA+ models verify the protocol logic is correct.
Series
- How Clawdbot Remembers: Memory Architecture
- Inside Clawdbot: Agent System & AI Providers
- Infrastructure, Security & Plugin SDK (this post)