Bolt
The containerized tool server: the industry-standard security toolkit in one Docker container, every tool exposed as an MCP call, so agents run real binaries in a sandbox instead of on your host.
A pentest toolchain is dozens of binaries, nmap, nuclei, ffuf, httpx, subfinder, sqlmap, amass, katana, each with its own install, dependencies, and version quirks. On a tester's laptop that's manageable. For an AI agent that needs to call these tools programmatically, it's a mess: the agent would need shell access to the host, the binaries on PATH, the right permissions, and hope nothing conflicts.Bolt solves this by packaging the industry-standard security toolkit into a single Docker container and exposing every tool as an MCP tool call.

What it is
Bolt is a plugin-based MCP server that runs in a Docker container. Each security tool is a plugin, a TypeScript module that wraps a CLI binary with a typed schema (Zod), an executor, and a health check. CyberStrike (or any MCP client) connects to Bolt over HTTP, discovers the available tools via MCP's ListTools, and calls them with structured arguments. The container handles the binary, the sandbox, and the output. No local installs.
The tools
Bolt ships with 6 plugins exposing 7 tools:
| Tool | What it does |
|---|---|
subfinder | Passive subdomain enumeration from multiple OSINT sources |
nmap | Port scanning, service detection, OS fingerprinting, NSE scripts |
nuclei | CVE and misconfig scanning with community templates (9000+) |
nuclei_update_templates | Pull latest nuclei templates |
httpx | HTTP probing: status codes, page titles, tech detection (Wappalyzer) |
ffuf | Directory, file, and vhost fuzzing with wordlists |
run_command | Shell escape hatch: any command the container can run |
Each tool has a typed input schema the agent can read. nmap takes target,ports, scan_type (syn/connect/udp/version/os),scripts, timing (T0 to T5). nuclei takes target,templates, tags, severity,rate_limit. ffuf takes url (with FUZZ keyword),wordlist, method, headers,filter_code, match_code, threads. The agent doesn't build CLI strings, it fills in typed fields.
run_command is the escape hatch: when no dedicated plugin exists, the agent can run any shell command the container supports. This is how ad-hoc tools (curl, dig, whois, openssl, socat) get used without writing a plugin for each one.
The container
Bolt's Docker image is built on Ubuntu 24.04 with Bun and Go. The Go tools (subfinder, nuclei, httpx, ffuf) are compiled from source at build time. nmap comes from apt. The entire image is ~800MB, compared to ~5GB for Kali. It runs with NET_RAWand NET_ADMIN capabilities (nmap SYN scans need raw sockets), a/data volume for persistence, and a health check on port 3001.
docker run -d --name bolt -p 3001:3001 \
-e MCP_ADMIN_TOKEN=$(openssl rand -hex 32) \
--cap-add NET_RAW --cap-add NET_ADMIN \
-v bolt-data:/data \
ghcr.io/cyberstrikeus/bolt:latestOne container, one command. The agent never touches the host.
Security
Bolt takes auth seriously because it runs destructive tools.
Ed25519 client pairing. No shared API keys. CyberStrike generates an Ed25519 keypair, exchanges public keys with Bolt via a short-lived pairing code (5-minute TTL), and signs every subsequent request with"{timestamp}\n{nonce}\n{method}\n{path}\n{bodyHash}". The server verifies the signature with constant-time comparison. Credentials are stored per-server in ~/.local/share/cyberstrike/bolt-keys/.
Middleware pipeline. Every request passes through four layers before reaching a tool:
- Connection throttle: 100 concurrent connections globally, 10 per IP
- Rate limiter: 300 req/min per IP, 100 req/min per authenticated client (token bucket plus sliding window hybrid), 10 pairing attempts per 5 minutes
- Security validator: 1MB body limit, 8KB header limit, 30-second request timeout
- Error handler: structured error responses, never leaks stack traces
Optional TLS. Pass --tls with key/cert paths, or let Bolt generate self-signed certs for local use.
Execution guardrails. Each tool has a timeout (nmap: 600s, nuclei: 900s, ffuf: 600s). Output is capped at 100KB per stream. Timed-out processes get SIGTERM, then SIGKILL after 5 seconds.

How CyberStrike connects
CyberStrike discovers Bolt servers from its config:
{ "bolt": { "mylab": { "url": "http://localhost:3001", "adminToken": "..." } } }On first connect, CyberStrike runs the Ed25519 pairing flow automatically. After that, every tool call is signed. The tools appear in the agent's tool list alongside CyberStrike's native tools, the agent doesn't know or care that nmap runs in a container on another machine.
From the UI, /bolt shows available Bolt servers and their tools. Toggle servers on and off per session.
Writing a plugin
A plugin is a TypeScript file exporting a PluginDef:
export const plugin: PluginDef = {
name: "my-tool",
version: "0.1.0",
tools: [{
name: "my_tool",
description: "Does something useful",
schema: { target: z.string() },
execute: async (args, ctx) => {
const r = await ctx.exec("my-tool", [args.target])
return { content: [{ type: "text", text: r.stdout }] }
},
}],
check: async () => {
const p = Bun.spawn(["which", "my-tool"], { stdout: "pipe" })
return { installed: (await p.exited) === 0 }
},
}Add the package name to bolt.config.json, install the binary in the Dockerfile, rebuild. The plugin loader resolves workspace packages, checks binary availability at startup, and logs tool counts. A missing binary logs a warning but doesn't crash, the tool fails gracefully at call time.
Why not just give the agent shell access?
Three reasons: isolation (the agent runs tools in a container, not on your workstation), portability (the same Bolt instance serves multiple CyberStrike sessions, on different machines, with the same toolchain), andauditability (every tool call is an MCP request with typed arguments, logged and rate-limited, not an opaque shell command). The agent gets the tools it needs without the blast radius of a root shell.
Bolt · TypeScript/Bun · Docker · AGPL-3.0-only. MCP transport: Streamable HTTP on port 3001.
Was this article helpful?