hexstrike-ai
by acaranta
README.md
# HexStrike AI — Docker + Remote MCP
Dockerized [HexStrike AI](https://github.com/0x4m4/hexstrike-ai) on the
[linuxserver.io](https://www.linuxserver.io/) Kali base image, with a
**new remote, streamable-HTTP MCP server** so the full HexStrike toolset can be
driven over the network (e.g. from Claude Code) instead of only from a local
stdio client.
> [!WARNING]
> **This exposes autonomous execution of offensive security tools over the
> network.** A client connected to the MCP endpoint can effectively perform
> remote command execution across the container's tool arsenal. Read the
> [Security](#security) section before running this anywhere but an isolated
> lab. Always set `HEXSTRIKE_MCP_TOKEN`, only run on a trusted/isolated
> network or VPN, and only target systems you are explicitly authorized to
> test.
> [!NOTE]
> **No prebuilt image is published.** Because the full Kali toolset makes the
> image **large (>10 GB — roughly ~17 GB built)**, it is **not** pushed to Docker
> Hub or any registry. You build it yourself from this repository (see
> [Quick Start](#quick-start)); make sure the Docker host has enough free disk.
## Contents
- [Features](#features)
- [Overview](#overview)
- [Architecture](#architecture)
- [Ports](#ports)
- [Quick Start](#quick-start)
- [Connecting from Claude Code](#connecting-from-claude-code)
- [Environment Variables](#environment-variables)
- [Persistence](#persistence)
- [Web Desktop Access](#web-desktop-access)
- [Health Check](#health-check)
- [Security](#security)
- [Tool Coverage](#tool-coverage)
- [Updating the Upstream Pin](#updating-the-upstream-pin)
- [Credits & License](#credits--license)
## Features
- The full upstream HexStrike AI toolkit — 150+ security tools driven through
its Flask API — pre-installed on a Kali-based image, no manual environment
setup.
- **New:** `hexstrike_mcp_http.py`, a streamable-HTTP MCP server that exposes
every upstream `@mcp.tool()` definition over the network on port `8889`
(path `/mcp`), so it can be driven remotely instead of only via local stdio.
- Optional bearer-token authentication guarding the MCP endpoint.
- Browser-accessible KDE desktop (Selkies/KasmVNC, inherited from the base
image) for interactive work alongside automated tool runs.
- `s6-overlay` v3 process supervision with explicit service ordering — the
MCP service waits for the HexStrike API server to be defined as a
dependency.
- Persistent `/config` volume: per-tool virtualenvs and file-manager state
survive container restarts.
- Reproducible builds: `uv` manages the Python environment, and upstream
sources are pulled at a pinned git commit rather than vendored.
## Overview
[HexStrike AI](https://github.com/0x4m4/hexstrike-ai) upstream ships two
Python entry points:
- **`hexstrike_server.py`** — a Flask API server listening on port `8888`
that executes 150+ external security tools via `subprocess` and exposes
their results over HTTP (including a `/health` endpoint that reports tool
availability).
- **`hexstrike_mcp.py`** — a [FastMCP](https://github.com/modelcontextprotocol/python-sdk)
server that translates MCP tool calls into HTTP calls against the API
server above. Upstream only runs it over the **stdio** transport, so it can
only be driven by an MCP client on the same machine as the container.
This repository adds **`hexstrike_mcp_http.py`**, which imports upstream's
`hexstrike_mcp.py` module and reuses its `setup_mcp_server()` function
wholesale — every tool definition, unchanged — but serves it over the MCP
**streamable-HTTP** transport on port `8889` at path `/mcp` instead of stdio.
That's the only functional difference: the same tool surface, reachable
remotely (e.g. from Claude Code running on a different machine) rather than
only from a co-located process.
[`uv`](https://docs.astral.sh/uv/) (Astral) manages the Python interpreter
and dependency environment and is used to run both processes at container
start.
## Architecture
- **Base image:** `lscr.io/linuxserver/kali-linux:latest`, which provides a
browser-accessible KDE desktop (Selkies/KasmVNC) on ports `3000` (HTTP) and
`3001` (HTTPS), Kali-rolling apt repos, and `s6-overlay` v3 init.
- **Added services:** two `s6-overlay` `longrun` services are layered on top
of the base image's init system and registered in the `user` service
bundle:
- `root/etc/s6-overlay/s6-rc.d/hexstrike-server` — runs
`uv run --no-sync python hexstrike_server.py --port 8888` as the `abc`
user.
- `root/etc/s6-overlay/s6-rc.d/hexstrike-mcp` — runs
`uv run --no-sync python hexstrike_mcp_http.py --server http://127.0.0.1:8888 --host 0.0.0.0 --port 8889`
as the `abc` user. Its `dependencies.d/hexstrike-server` entry orders it
to start **after** the API server.
- **Upstream sources are not vendored.** The `Dockerfile` `git clone`s
`0x4m4/hexstrike-ai` at build time and checks out a **pinned commit**
(`ARG HEXSTRIKE_REF`, default `d689933ff579d839c676c82b231f8e98326c5f04`),
copying only `hexstrike_server.py`, `hexstrike_mcp.py`, and
`hexstrike-ai-mcp.json` into `/app`. This keeps the repo small and the
upstream bump a one-line change.
- **Python environment:** a `uv`-managed CPython **3.12** (pinned via
`.python-version`), with dependencies from `pyproject.toml` installed into
`/app/.venv` via `uv sync --no-dev` at build time.
- **Persistence bootstrap:** `root/custom-cont-init.d/10-hexstrike-persist`
runs as root before the longrun services start (see
[Persistence](#persistence)).
## Ports
| Port | Protocol | Description |
|------|----------|-------------|
| 3000 | HTTP | Web desktop (Selkies/KasmVNC), inherited from the linuxserver Kali base image |
| 3001 | HTTPS | Web desktop, TLS |
| 8888 | HTTP | HexStrike AI API server (`hexstrike_server.py`) |
| 8889 | HTTP | HexStrike AI MCP server, streamable-HTTP transport, endpoint path `/mcp` |
## Quick Start
### Docker Compose (recommended)
All configuration lives directly in `docker-compose.yml` — there is no `.env`
file. Before the first launch, edit the `environment:` block and set:
- `HEXSTRIKE_MCP_TOKEN` — the bearer token for the MCP endpoint. Generate a
strong value with `openssl rand -hex 32` and paste it in (or leave it empty
for an open endpoint — lab/CTF on an isolated network only).
- `CUSTOM_USER` / `PASSWORD` — the web desktop basic-auth login.
Then build and start:
```bash
# generate a token to paste into docker-compose.yml:
openssl rand -hex 32
docker compose up -d --build
```
The build is large and slow, and **there is no prebuilt image to pull** — the
resulting image is **>10 GB (~17 GB)**, so it is not published to any registry
and must be built locally. It installs the `kali-linux-headless` metapackage
plus a long list of named tools, compiles `angr` / `pwntools` wheels (hence
`build-essential` + dev headers in the image), builds three Go tools from
source, and downloads a `rustscan` release package. Expect the first build to
take a significant amount of time and **plenty of free disk** (budget ~20 GB+
headroom, more while iterating).
### Plain Docker
```bash
docker build -t hexstrike-ai:local .
docker run -d \
--name hexstrike-ai \
--cap-add=NET_RAW --cap-add=NET_ADMIN \
--shm-size=1g \
-p 3000:3000 -p 3001:3001 -p 8888:8888 -p 8889:8889 \
-v "$(pwd)/config:/config" \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Etc/UTC \
-e CUSTOM_USER=kali \
-e PASSWORD=changeme \
-e HEXSTRIKE_MCP_TOKEN="$(openssl rand -hex 32)" \
hexstrike-ai:local
```
`--cap-add=NET_RAW --cap-add=NET_ADMIN` and `--shm-size=1g` are required —
see [Security](#security) and the `docker-compose.yml` comments for why.
## Connecting from Claude Code
Once the container is up, point Claude Code at the MCP endpoint on port
`8889`. If `HEXSTRIKE_MCP_TOKEN` is set (strongly recommended — see
[Security](#security)), every request must include a matching
`Authorization: Bearer <token>` header; the endpoint only accepts
unauthenticated requests if the token is left unset.
**Project/user MCP config (`.mcp.json` or Claude Code settings):**
```json
{
"mcpServers": {
"hexstrike-ai": {
"type": "http",
"url": "http://<HOST>:8889/mcp",
"headers": { "Authorization": "Bearer <HEXSTRIKE_MCP_TOKEN>" }
}
}
}
```
**Or via the CLI:**
```bash
claude mcp add --transport http hexstrike-ai http://<HOST>:8889/mcp \
--header "Authorization: Bearer <TOKEN>"
```
Replace `<HOST>` with the container host's address/hostname and
`<HEXSTRIKE_MCP_TOKEN>` / `<TOKEN>` with the value you generated above. If
you deliberately left `HEXSTRIKE_MCP_TOKEN` unset, omit the `headers` /
`--header` argument — the endpoint is open in that case.
> **`Invalid Host header` / HTTP 421?** The MCP streamable-HTTP transport has
> built-in DNS-rebinding protection that, by default, only trusts a `localhost`
> `Host` header. This image **disables that check by default** so you can reach
> the endpoint over the LAN by IP or hostname. If you instead see this error,
> either upgrade to this image (the default already handles it) or set
> `HEXSTRIKE_MCP_ALLOWED_HOSTS` to the exact `host:port` value(s) your clients
> send.
## Environment Variables
| Variable | Where set | Default | Description |
|----------|-----------|---------|--------------|
| `PUID` | `docker-compose.yml` / `-e` | `1000` | linuxserver convention: UID the `abc` service user runs as inside the container. |
| `PGID` | `docker-compose.yml` / `-e` | `1000` | linuxserver convention: GID the `abc` service user runs as. |
| `TZ` | `docker-compose.yml` / `-e` | `Etc/UTC` | Container timezone. |
| `CUSTOM_USER` | `docker-compose.yml` | `kali` | Basic-auth username for the web desktop (linuxserver Kali base image). |
| `PASSWORD` | `docker-compose.yml` | `changeme` | Basic-auth password for the web desktop. |
| `HEXSTRIKE_MCP_TOKEN` | `docker-compose.yml` | *(empty)* | Bearer token required on the `:8889/mcp` endpoint. Set it to a value from `openssl rand -hex 32`; leave empty only for an isolated/open lab setup. |
| `HEXSTRIKE_SERVER_PORT` | optional, commented example in `docker-compose.yml` | `8888` | Port the HexStrike API server binds to. Both `s6` run scripts read this; if you change it you must also update the published port mapping. |
| `HEXSTRIKE_MCP_PORT` | optional, commented example in `docker-compose.yml` | `8889` | Port the MCP streamable-HTTP server binds to (same caveat as above). |
| `HEXSTRIKE_MCP_HOST` | optional | `0.0.0.0` | Interface the MCP server binds to; passed straight through by the `hexstrike-mcp` run script and read as an env fallback by `hexstrike_mcp_http.py`. |
| `HEXSTRIKE_MCP_ALLOWED_HOSTS` | optional, `-e` | *(empty)* | Comma-separated `Host`/`Origin` allowlist for the transport's DNS-rebinding protection (e.g. `hexstrike.lan:8889,10.0.0.5:8889`). **When empty (default) the Host check is disabled**, so the endpoint is reachable via any address/hostname. Set it to lock the endpoint to specific hostnames. |
| `HEXSTRIKE_MCP_TOOL_FILTER` / `_INCLUDE_ONLY` / `_EXCLUDE_TOOLS` / `_KEEP_TOOLS` | optional, `-e` | `auto` / empty | Control which MCP tools are exposed — see [MCP tool filtering](#mcp-tool-filtering-fewer-tools-for-the-model). |
`hexstrike_mcp_http.py` also accepts `--server` / `HEXSTRIKE_SERVER_URL`,
`--path` / `HEXSTRIKE_MCP_PATH`, `--timeout` / `HEXSTRIKE_TIMEOUT`, and
`--allowed-hosts` / `HEXSTRIKE_MCP_ALLOWED_HOSTS` overrides. The shipped `s6`
run script only passes `--server`, `--host`, and `--port`; the rest are read
from their environment fallbacks (so `HEXSTRIKE_MCP_ALLOWED_HOSTS` works when
set on the container) or keep their built-in defaults (`/mcp`, upstream's
default request timeout, and the Host check disabled).
## Persistence
The `/config` volume is the linuxserver-convention home directory and is the
only path that survives container recreation. The cont-init script
`root/custom-cont-init.d/10-hexstrike-persist` runs as root before the
longrun services start and:
1. Creates `/config/hexstrike/envs`, `/config/hexstrike/files`, and
`/config/hexstrike/output`.
2. Replaces upstream's **hardcoded** `/tmp/hexstrike_envs` and
`/tmp/hexstrike_files` paths with symlinks into `/config/hexstrike/envs`
and `/config/hexstrike/files` respectively, so per-tool Python virtualenvs
HexStrike builds on demand, and its file-manager read/write area, survive
container restarts.
3. `chown`s the persistent tree to `abc:abc` (respecting `PUID`/`PGID`) via
`lsiown`.
Dynamic `/tmp/<tool>_output` directories created by individual tool runs are
**not** symlinked and remain ephemeral — point a given tool explicitly at
`/config/hexstrike/output` if you need its output to survive a restart.
## Web Desktop Access
Open `https://<HOST>:3001/` in a browser and log in with the `CUSTOM_USER` /
`PASSWORD` credentials configured above (the HTTP desktop is also available
on port `3000` without TLS).
## Health Check
```bash
curl http://<HOST>:8888/health
```
Returns JSON from the HexStrike API server listing which of its tools were
detected as available on `PATH` inside the container.
## Security
> [!WARNING]
> The MCP endpoint on `8889` lets a connected client autonomously drive
> execution of offensive security tools inside the container — this is
> effectively **remote command execution** by design, gated only by whatever
> bearer token you configure.
- **Always set `HEXSTRIKE_MCP_TOKEN`.** Generate it with
`openssl rand -hex 32` and keep it secret. Leaving it empty makes the MCP
endpoint unauthenticated.
- **Only expose this on a trusted/isolated network or VPN.** Do not publish
port `8889` (or `8888`) to the open internet.
- **Only target systems you are explicitly authorized to test.** The
container ships real offensive security tooling capable of active
scanning, exploitation, credential attacks, and more.
- The container is granted `NET_RAW` and `NET_ADMIN` capabilities (see
`docker-compose.yml` / the `docker run` example) because several tools —
`nmap` SYN scans, `masscan`, `responder`, etc. — need raw sockets. These
are narrower than `--privileged` but still meaningfully elevated; grant
them only if you need those scan types.
- Auth is implemented as a minimal ASGI middleware
(`BearerTokenASGIMiddleware` in `hexstrike_mcp_http.py`) that inspects the
`Authorization` header with a constant-time comparison and rejects
unauthenticated requests to the MCP path with `401` before they reach the
MCP app — it does not buffer or otherwise interfere with the
streamable-HTTP/SSE response stream.
## Tool Coverage
The tool set is curated, not exhaustive:
- The `kali-linux-headless` metapackage, plus a list of tools named
explicitly in the `Dockerfile` (the ones HexStrike's `/health` endpoint
probes, and common recon/web/exploitation utilities) — e.g. `nmap`,
`masscan`, `gobuster`, `feroxbuster`, `ffuf`, `dirb`, `dirsearch`, `nikto`,
`sqlmap`, `wpscan`, `whatweb`, `wafw00f`, `hydra`, `john`, `hashcat`,
`medusa`, `evil-winrm`, `amass`, `dnsenum`, `dnsrecon`, `fierce`,
`theharvester`, `responder`, `nuclei`, `subfinder`, `httpx-toolkit`,
`arjun`, `paramspider`, `hakrawler`, `getallurls` (`gau`), `netexec`,
`enum4linux-ng`, `smbclient`, `seclists`, `wordlists`, `binwalk`,
`foremost`, `steghide`, `exiftool`, `radare2`, and `gdb`.
- Three Go-built tools compiled from source at build time and then stripped
of the Go toolchain: `katana`, `dalfox`, `waybackurls`.
- `rustscan`, installed from its latest GitHub release (the `.deb` ships
inside a `rustscan.deb.zip` asset, which the build unzips before installing;
best-effort — the build continues without it if the asset can't be resolved).
Some heavy or licensed tools referenced by upstream HexStrike — **Ghidra,
IDA, Burp Suite Pro, Maltego, Nessus**, and various cloud/forensics utilities
— are **not** installed in this image to keep the build tractable.
### MCP tool filtering (fewer tools for the model)
Upstream registers **~150 MCP tools**. That overflows the function-tool limit
of some model providers (OpenAI caps at ~128) and surfaces tools that can only
fail because their CLI binary isn't installed. To avoid this, the MCP server
**hides any tool whose backing binary is not present** (checked with
`which` at startup, since the MCP process runs in the same container as the
tools). A typical build exposes ~115 tools instead of 150. The startup log
prints the count and the hidden list.
Control it via environment variables on the container:
| Variable | Default | Effect |
|----------|---------|--------|
| `HEXSTRIKE_MCP_TOOL_FILTER` | `auto` | `auto` hides tools with a missing binary; `off` exposes all ~150. |
| `HEXSTRIKE_MCP_INCLUDE_ONLY` | *(empty)* | Comma-separated whitelist — expose **only** these tools (overrides auto). |
| `HEXSTRIKE_MCP_EXCLUDE_TOOLS` | *(empty)* | Comma-separated tools to always hide. |
| `HEXSTRIKE_MCP_KEEP_TOOLS` | *(empty)* | Comma-separated tools to always keep (protects them from auto-drop). |
## Updating the Upstream Pin
Upstream sources are cloned at build time at the commit in
`ARG HEXSTRIKE_REF` (`Dockerfile`). To pick up newer upstream changes, bump
that pin and rebuild:
```bash
docker build -t hexstrike-ai:local --build-arg HEXSTRIKE_REF=<new-sha> .
```
or, with Compose, uncomment and set the `args: HEXSTRIKE_REF:` entry under
the `build:` key in `docker-compose.yml` and run `docker compose up -d --build`.
## Credits & License
- [HexStrike AI](https://github.com/0x4m4/hexstrike-ai) by
[0x4m4](https://github.com/0x4m4) — the security automation platform this
image packages, released under the MIT License. See upstream's `LICENSE`
file for full terms.
- [linuxserver.io](https://www.linuxserver.io/) `kali-linux` image — the base
image providing the Kali environment and web desktop.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues