Menu

Star on GitHub · 3.1k

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.

Every tool, one container: meet Bolt, the tool server

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:

ToolWhat it does
subfinderPassive subdomain enumeration from multiple OSINT sources
nmapPort scanning, service detection, OS fingerprinting, NSE scripts
nucleiCVE and misconfig scanning with community templates (9000+)
nuclei_update_templatesPull latest nuclei templates
httpxHTTP probing: status codes, page titles, tech detection (Wappalyzer)
ffufDirectory, file, and vhost fuzzing with wordlists
run_commandShell 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:latest

One 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.

One command, anywhere: Bolt, drop it into any network

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?