cd /news/ai-agents/show-hn-redhands-mcp-for-multi-stage… · home › topics › ai-agents › article
[ARTICLE · art-169715] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Show HN: RedHands – MCP for multi-stage offensive security tools

Developer Yasin D. C. released RedHands, an open-source Model Context Protocol (MCP) server written in Go with zero external dependencies that chains multi-stage offensive security tools for authorized security assessments. RedHands supports stdio, SSE and WebSocket transports and ships install profiles — minimal (nmap, subfinder, httpx, nuclei), web, network, ad, k8s, uefi, telecom, aegis, sbom, recon and all — plus structured parsing, report generation, a plugin system, rate limiting, caching and audit logging. The release verifies install archives against published SHA256SUMS and is configured as an MCP server in Claude Code, Cursor and VS Code.

read30 min views1 publishedNov 14, 2026
Show HN: RedHands – MCP for multi-stage offensive security tools
Image: Michielbdejong (auto-discovered)

MCP server for authorized security assessments

Built in Go with zero external dependencies. Multi-transport (stdio/SSE/WebSocket), tool chaining,

structured parsing, report generation, plugin system, rate limiting, caching, and audit logging.

go install github.com/yasindce1998/redhands/cmd/redhands@latest
curl -fsSL https://raw.githubusercontent.com/yasindce1998/redhands/main/scripts/install.sh | bash

This verifies a release archive against its published SHA256SUMS and prints MCP configuration instructions. Older releases without checksums must be built from source. To install security tools, use a reviewed checkout containing the dependency lock:

git clone https://github.com/yasindce1998/redhands.git && cd redhands
sudo ./scripts/install-tools.sh --profile web
git clone https://github.com/yasindce1998/redhands.git && cd redhands
make build
sudo make install

sudo make install-tools PROFILE=minimal   # nmap, nuclei, httpx, subfinder
sudo make install-tools PROFILE=web       # web assessment tools
sudo make install-tools PROFILE=network   # network/infrastructure tools
sudo make install-tools PROFILE=ad        # Active Directory tools
sudo make install-tools PROFILE=k8s       # Kubernetes offensive tools
sudo make install-tools PROFILE=uefi      # UEFI bootkit simulation
sudo make install-tools PROFILE=telecom   # StaticZero telecom research (Linux only)
sudo make install-tools PROFILE=aegis     # Aegis-Shadow eBPF research (Linux only)
sudo make install-tools PROFILE=sbom      # SBOM + vulnerability scanning (syft, grype)
sudo make install-tools PROFILE=recon     # deep reconnaissance tools
sudo make install-tools PROFILE=all       # everything

make check-deps
git clone https://github.com/yasindce1998/redhands.git; cd redhands
go build -o bin\redhands.exe .\cmd\redhands

.\scripts\install-tools.ps1 -Profile web
.\scripts\install-tools.ps1 -Check    # show what's installed/missing

Add to your MCP client config:

Claude Code (~/.claude/claude_desktop_config.json), Cursor (.cursor/mcp.json), VS Code (.vscode/mcp.json):

{
  "mcpServers": {
    "redhands": {
      "command": "redhands"
    }
  }
}
redhands

REDHANDS_TRANSPORT=sse redhands  # loopback only; see secure remote setup below

REDHANDS_TRANSPORT=ws redhands  # loopback only

docker compose run --rm -i redhands
Profile Tools Included Use Case
minimal nmap, subfinder, httpx, nuclei Quick recon and scanning
web nmap, recon set (subfinder, amass, dig, waybackurls, gau, arjun), web set (httpx, katana, nikto, whatweb, testssl.sh), ffuf, gobuster, feroxbuster, masscan, rustscan, nuclei, sqlmap Web application testing
network nmap, masscan, rustscan, tshark, chisel, ligolo Network/infrastructure
ad nmap, impacket, certipy, crackmapexec, hashcat, john Active Directory attacks
k8s nmap, kubedagger-client, kubedagger-operator, kubedagger-server Kubernetes offensive (eBPF)
uefi barzakh-adversary, barzakh-scanner, barzakh-server UEFI bootkit adversary simulation
telecom staticzero-defense, staticzero-offense, modem-firmware, sdr-bridge Telecom security research (eBPF, Linux only)
aegis aegis-shadow-defense, aegis-shadow-offense, aegis-c2 Linux eBPF security research (Aegis-Shadow)
sbom syft, grype SBOM generation and vulnerability scanning
recon recon set (subfinder, amass, dig, waybackurls, gau, arjun) + web set (httpx, katana, nikto, whatweb, testssl.sh) Deep reconnaissance
all Everything Full offensive toolkit

Profiles describe desired dependencies, not a guarantee every dependency is available on every OS. RustScan, testssl.sh, and CrackMapExec currently need manual installation; KubeDagger client is Linux-only. Installers report missing tools rather than down unverified replacements. See dependency maintenance.

make build produces redhands, redhands-cli, and redhands-creds in bin/. The CLI uses the same authenticated SSE transport as MCP clients:

REDHANDS_TRANSPORT=sse ./bin/redhands
./bin/redhands-cli tools
./bin/redhands-cli targets add 192.0.2.1 lab-host
./bin/redhands-cli status

For HTTPS, set REDHANDS_URL=https://your-server:8443, REDHANDS_API_KEY, and REDHANDS_CLIENT_CA=/path/to/ca.pem. For mTLS, also set REDHANDS_CLIENT_CERT and REDHANDS_CLIENT_KEY. The CLI rejects untrusted certificates, redirects, and non-loopback plain HTTP. REDHANDS_CLIENT_TIMEOUT sets its total deadline (default 6m). Tool failures return a nonzero exit status; calls are never retried automatically.

  • Go 1.26.11+ (for building from source; if using Go 1.27, use 1.27.4+)
  • Security tools on PATH (installed via scripts above, Docker, or manually)

294 tools are registered across 36 toolsets. A default startup serves 287 of them: the two credstore tools additionally require REDHANDS_CREDSTORE_KEY, and the five GoPhish phishing tools additionally require REDHANDS_GOPHISH_* environment variables.

Tool Description
nmap_port_scan TCP/UDP port scanning (SYN, connect, UDP)
nmap_service_detect Service and version detection (-sV)
nmap_os_detect OS fingerprinting (-O)
nmap_vuln_scan NSE vulnerability scripts
Tool Description
subfinder_enum Subdomain enumeration from passive sources
amass_enum Network mapping and ASN discovery (OWASP Amass)
dns_lookup DNS record queries via dig (A, AAAA, MX, NS, TXT, etc.)
waybackurls Fetch archived URLs from the Wayback Machine
gau_urls Fetch URLs from AlienVault OTX, Wayback, CommonCrawl, URLScan
arjun_discover HTTP parameter discovery (hidden GET/POST params)
Tool Description
httpx_probe HTTP service probing with tech detection
katana_crawl Next-gen web crawler (JS crawling, headless)
nikto_scan Web server vulnerability scanner
whatweb_fingerprint Web technology fingerprinting
testssl_scan TLS/SSL encryption testing
Tool Description
ffuf_fuzz Web fuzzing (dirs, params, vhosts)
gobuster_dir Directory/file brute-forcing
feroxbuster_scan Recursive content discovery (forced browsing)
Tool Description
masscan_scan Internet-scale port scanning (10M pps)
rustscan_scan Modern fast port scanner (all 65535 ports in seconds)
Tool Description
sqlmap_scan Automatic SQL injection detection and exploitation
Tool Description
nuclei_scan Template-based vulnerability scanning

Kerberos interaction toolkit — for Windows engagement hosts running RedHands with an operator-compiled Rubeus.exe allowlisted as rubeus (GhostPack ships no compiled releases by design; Linux execution is not possible — the tool P/Invokes Windows LSA APIs).

Tool Description
rubeus_kerberoast
rubeus_asktgt
rubeus_asreproast
rubeus_harvest
rubeus_dump
rubeus_describe Decode and describe a base64 ticket
Tool Description
impacket_secretsdump Dump SAM/LSA/NTDS.dit secrets
impacket_psexec PsExec-style remote execution
impacket_wmiexec WMI-based remote execution
impacket_smbclient SMB client operations
impacket_dcomexec DCOM remote execution
impacket_get_tgt Request Kerberos TGT
impacket_get_st Request service ticket (Kerberoast/delegation)
impacket_ntlmrelay NTLM relay attack
Tool Description
sliver_generate Generate implant (mTLS, HTTP/S, DNS, WireGuard)
sliver_listeners Manage C2 listeners
sliver_sessions List/interact with sessions
sliver_beacons List/interact with beacons
sliver_execute Execute commands on implant
sliver_upload Upload files to implant
sliver_download Download files from implant
sliver_pivot Pivot setup and management
sliver_portfwd Port forwarding through implant
Tool Description
chisel_server Start Chisel reverse tunnel endpoint
chisel_client Chisel client (forward/reverse/SOCKS)
ligolo_start Start Ligolo-ng proxy server
ligolo_route Add routes through Ligolo tunnel
ligolo_listener Manage Ligolo listeners on agent
Tool Description
hashcat_crack Crack hashes (dictionary, rules, masks)
hashcat_benchmark Benchmark hash types
hashcat_show Show cracked from potfile
john_crack Crack passwords with John the Ripper
john_show Show cracked passwords
john_formats List supported hash formats (graceful degradation on core john builds)
hydra_attack
Tool Description
cme_smb SMB enumeration/exec/pass-the-hash
cme_winrm WinRM execution
cme_ldap LDAP enumeration
cme_mssql MSSQL queries/execution
cme_ssh SSH credential spray/exec
Tool Description
certipy_find Find AD CS templates and CAs
certipy_req Request certificates
certipy_auth Authenticate via PKINIT
certipy_shadow Shadow credentials attack
certipy_forge Golden certificate forgery
certipy_relay NTLM relay to AD CS
Tool Description
tshark_capture Capture packets (interface, filter, duration)
tshark_read Read pcap with display filter
tshark_stats Protocol statistics
tshark_extract Extract fields from captures
tshark_follow Follow TCP/UDP/HTTP streams
Tool Description
69 tools eBPF-powered Kubernetes offensive toolkit, including managed C2-server lifecycle ( kubedagger_server_* ) — seetools/kubedagger/
Tool Description
barzakh_list List available UEFI bootkit payload types (134 payloads)
barzakh_generate Generate a UEFI bootkit payload for adversary simulation (x86-64, aarch64, riscv64, power64, armv7)
barzakh_corpus Generate full test corpus of malicious/clean firmware pairs
barzakh_scan Scan firmware images with 138 specialized bootkit detectors (exit 1 = detection, reported as a finding)
barzakh_validate Validate detector coverage (TPR/FPR) against a corpus
barzakh_scenario List/show/run multi-stage ATT&CK-mapped attack chains (6 bundled)
barzakh_robustness Mutation-based detector evasion benchmark
barzakh_detectors List/describe the 138 detectors
barzakh_baseline Create/show firmware baselines
barzakh_intel Query IOC feeds, MISP, ATT&CK, STIX intel
barzakh_server_start Start the Barzakh REST scanner server as a managed background daemon (X-API-Key auth, SQLite history)
barzakh_server_stop Stop a managed Barzakh server
barzakh_server_status Status of managed Barzakh servers
barzakh_inject
barzakh_esp_build
barzakh_qemu Boot firmware in an emulated QEMU/OVMF environment (run config / memory dump)
barzakh_range Boot QEMU/OVMF/swTPM cyber-range suites with assert-on-detection (exit 1 = failed assertions, surfaced as a result)
barzakh_fuzz Protocol-aware firmware fuzzing (dhcpv6/bmp/capsule/httpboot/tcg/hii)
barzakh_history Query the SQLite scan history (list/show/diff/trends)
barzakh_kb Manage the firmware knowledge base (add/list/search/export/import)
barzakh_ml Train/evaluate the ML anomaly models
barzakh_manifest_validate Validate detectors against a labeled corpus manifest with train/validation/test splits
barzakh_info Scanner build/version, detector, architecture, and format metadata
barzakh_report Generate report files (json/html/markdown/sarif/csv/ecs/cef/leef) from scan results

Wraps StaticZero, an eBPF telecom security research framework (2G–5G, satellite, baseband firmware). Authorized lab use only.

Warning

telecom_offense_start launches the **telecom interception research ** (features F89–F172: IMSI targeting, GTP/SS7/Diameter interception, RF and satellite domains). Intercepting telecommunications is illegal without authorization in most jurisdictions — including in labs in some. Use only in an isolated lab environment with equipment you own, and only where you hold explicit permission. The tool is approval-gated and requires root.

Tool Description
telecom_doctor Read-only StaticZero defense engine preflight (platform/kernel/BPF checks)
telecom_firmware_analyze Static vulnerability analysis of modem/baseband firmware images
telecom_firmware_extract Extract firmware components with manifest
telecom_firmware_diff Diff two firmware images
telecom_firmware_nv_dump Extract NV items (carrier/band/security config)
telecom_firmware_patch
telecom_firmware_monitor Monitor a modem device protocol stream (diag/at/qmi/mbim; bounded by the execution timeout)
telecom_sdr_scan Receive-only LTE cell scan via SDR
telecom_sdr_capture Receive-only IQ capture via SDR
telecom_sdr_inject
telecom_sdr_relay
telecom_sdr_selftest
telecom_threats Correlated threats from a running defense engine
telecom_alerts Raw alerts from a running defense engine
telecom_stats Engine runtime statistics
telecom_defense_start Start the StaticZero defense engine as a managed background daemon (JSON alerts stream to a file)
telecom_defense_stop Stop a managed defense engine daemon
telecom_defense_status Status of managed defense engine daemons
telecom_offense_start
telecom_offense_stop Stop a managed offense daemon
telecom_offense_status Status of managed offense daemons
telecom_correlator_start Start the protocol correlator as a managed daemon (JSON-lines event ingest on loopback)
telecom_correlator_stop Stop a managed correlator daemon
telecom_correlator_status Status of managed correlator daemons

StaticZero binaries ship as checksum-verified release assets (v0.5.0, Linux only, eBPF embedded) and install via the telecom profile: sudo make install-tools PROFILE=telecom.

Wraps Aegis-Shadow, a Linux eBPF security research platform (educational, VM-only). The analysis tools never execute binaries; aegis_offense_plan renders the manual lab command with risk notes without executing it. The defense engine and the offense rootkit can each be started as a managed background daemon (approval-gated).

[!DANGER] aegis_offense_start launches the **eBPF rootkit ** — process hiding, credential harvesting, log tampering, anti-forensics, and covert C2, running at kernel level with root. Two capabilities are destructive by design: deadman_switch (scorched-earth wipe triggered on tamper or timeout — the VM may become unrecoverable) and wipe_bytecode (erases the loaded eBPF bytecode from memory, defeating live forensics). Both are opt-in, emit loud warnings in every result, and expect a disposable, snapshotted lab VM. Data loss from these flags is expected behavior, not a bug.

Tool Description
aegis_alerts_parse Parse/filter NDJSON alert files from the defense engine
aegis_alerts_tail Tail the last N alerts from an alert file
aegis_modules_list Reference catalog of defense modules and offense features
aegis_defense_config_read Read/validate a defense engine hot-reload config
aegis_defense_config_write Write a defense engine config (threshold, window)
aegis_offense_plan Dry-run planner: validate offense feature flags and render the exact manual command line with risk notes
aegis_defense_start Start the Aegis-Shadow defense engine as a managed background daemon (NDJSON alerts stream to a file; requires root)
aegis_defense_stop Stop a managed defense engine daemon
aegis_defense_status Status of managed defense engine daemons
aegis_offense_start ☠️ Launch the Aegis-Shadow eBPF rootkit as a managed daemon (destructive deadman_switch /wipe_bytecode are opt-in with warnings; disposable VM only)
aegis_offense_stop Stop a managed offense daemon
aegis_offense_status Status of managed offense daemons
aegis_c2_send ☠️ Send a command to a running rootkit over its encrypted C2 channel (hide/unhide pid, obfuscate file, exfil, kill-switch; fire-and-forget; requires the aegis-c2 CLI from Aegis-Shadow ≥ v0.2.0)

ExploitDB search and CVE enrichment — the connective tissue from scanner findings to Metasploit: nuclei finding → CVE → public exploits → msf_module_run. Requires the searchsploit binary + on-disk database (distro exploitdb package — manual install, not checksum-pinnable).

Tool Description
searchsploit_search Search ExploitDB by keyword (JSON results)
searchsploit_cve Map a CVE ID to public exploits (EDB-IDs, paths)
searchsploit_edb Look up an EDB-ID (exploit file path + copy hint)
cve_enrich Extract CVEs from scanner output text, map to exploits, and suggest Metasploit modules (EDB .rb paths →msf_module_run candidates)

Generic embedded firmware analysis with binwalk v2 — completes the firmware story alongside UEFI (Barzakh) and baseband (modem-firmware). Requires binwalk (v2, Python — distro/pip manual install; do not use the v3 Rust rewrite, different CLI).

Tool Description
firmware_scan Signature scan with a structured JSON summary (signatures → record rules)
firmware_extract
firmware_entropy Entropy analysis (encrypted/compressed region detection)

SBOM generation and vulnerability scanning with Anchore syft + grype — checksum-pinned releases, installed by the sbom profile.

Tool Description
sbom_generate Generate an SBOM (syft-json, CycloneDX, SPDX) from an image, directory, or file
vuln_scan_image Vulnerability-scan an image or SBOM with grype (exit-2-on-findings surfaced as a result with a severity summary)
vuln_scan_sbom Scan an existing SBOM file with grype

Wraps Metasploit Framework (msfvenom + msfconsole). Console tools generate validated .rc resource files from structured parameters — never msfconsole -x — so console-command injection is structurally impossible. Metasploit is not checksum-pinnable (no stable release binaries); install it manually (apt install metasploit-framework on Kali/Parrot) and the allowlist discovers msfconsole/ msfvenom on PATH. msfconsole startup takes 10–60s; runs are bounded by REDHANDS_TIMEOUT.

Tool Description
msf_payload_generate
msf_payload_list List payloads, encoders, formats, platforms, or architectures
msf_module_info Show module documentation (exploit/auxiliary/post/payload/encoder/nop)
msf_module_run
msf_resource_run .rc resource file (arbitrary console commands; approval-gated)
Tool Description
redhands_health Server health check and binary dependency status

RedHands supports four transport modes:

Transport Use Case Config
stdio (default) Local use with Claude Code, Cursor, VS Code REDHANDS_TRANSPORT=stdio
HTTP (Streamable) Modern MCP 2026-07-28 clients: stateless single-endpoint JSON-RPC REDHANDS_TRANSPORT=http (REDHANDS_HTTP_ADDR , default127.0.0.1:8082 ;REDHANDS_HTTP_PATH , default/mcp )
SSE Legacy HTTP clients (deprecated-era transport) REDHANDS_TRANSPORT=sse (loopback by default)
WebSocket Real-time bidirectional (custom transport) REDHANDS_TRANSPORT=ws (loopback by default)

The HTTP transport implements the modern spec: POST-only /mcp endpoint, required MCP-Protocol-Version/ Mcp-Method (and Mcp-Name for tools/call) headers validated against the body, 405 for GET/DELETE, 404+-32601 for unknown methods, 202 for notifications, and per-request _meta protocol version validation (-32022 with the supported list). Legacy clients that open with initialize are served by the dual-era handshake on every transport. Non-loopback listeners require authentication and TLS.

Mode Config Description
none (default) REDHANDS_AUTH=none No authentication
API key REDHANDS_AUTH=apikey +REDHANDS_API_KEY=<secret> X-API-Key header validation
mTLS REDHANDS_AUTH=mtls + cert/key/CA env vars Client certificate verification

Chain tools sequentially with variable substitution:

{
  "method": "workflow/run",
  "params": {
    "steps": [
      {"tool": "nmap_port_scan", "params": {"target": "10.0.0.1"}},
      {"tool": "nuclei_scan", "params": {"target": "http://10.0.0.1"}}
    ]
  }
}

Variables: $prev.text (previous step output), $step[N].text (Nth step output). Substitutions contain the full formatted text, not extracted targets; only pass them to parameters that accept that format. All steps honor dry-run, rate limits and scope.

Campaigns extend workflows with conditions, parallel fan-out, failure recovery (goto:), checkpointing, resume, cancellation, and playbook templates. Every step executes through the same middleware chain as a direct tools/call (audit, auth, rate limits, scope, dry-run, approval, concurrency limits).

{
  "method": "campaign/run",
  "params": {
    "id": "web-engage-1",
    "name": "Web assessment",
    "steps": [
      {"id": "portscan", "tool": "nmap_port_scan", "params": {"target": "10.0.0.1"}},
      {"id": "webscan", "tool": "nuclei_scan", "params": {"target": "http://10.0.0.1"}},
      {"parallel": [
        {"id": "nikto", "tool": "nikto_scan", "params": {"target": "http://10.0.0.1"}},
        {"id": "tls", "tool": "testssl_scan", "params": {"target": "10.0.0.1:443"}}
      ], "on_fail": "continue"},
      {"id": "report", "tool": "loot_add", "params": {"description": "done"}}
    ]
  }
}
Field Description
id Unique step identifier used by conditions and ${id...} references
tool /params Tool name and JSON params (exactly like tools/call )
parallel Array of sub-steps executed concurrently (≤16, no nesting)
condition Skip the step unless the condition holds
on_fail stop (default),continue , orgoto:<step_id> recovery jump
{"condition": {"step": "portscan", "op": "json_gt", "path": "hosts.0.ports.0", "value": "0"}}

Operators: success, failure, contains, not_contains, equals, not_equals, plus JSON-aware json_exists, json_not_exists, json_equals, json_not_equals, json_contains, json_gt, json_lt evaluated against a previous step's structured output (step may be a step ID, "$prev", or a zero-based index).

Placeholders can extract fields from prior results, not just whole text. Tools emitting JSON (raw or in a markdown ``` json fence) are queryable by dot-path with numeric array indices:

$prev.text                     full text of the previous step
$prev.json.findings.0.severity field extraction
$step[2].json.hosts            whole array from step 2 (native type preserved)
${portscan.json.hosts.0.ip}    reference by step ID

A placeholder that makes up an entire string value is substituted natively (numbers stay numbers, arrays stay arrays); placeholders embedded in a larger string are substituted as text. Unresolvable placeholders pass through untouched.

record rules map JSON arrays in a step's output to target database entries, so campaigns build the host/service/credential/finding picture automatically:

{
  "id": "discover",
  "tool": "kubedagger_k8s_discover",
  "params": {"namespace": "default"},
  "mitre": ["T1613"],
  "record": [
    {"type": "host", "path": "nodes", "fields": {"ip": "ip", "hostname": "name", "os": "os"}},
    {"type": "host", "path": "pods", "fields": {"ip": "pod_ip", "hostname": "name"}}
  ]
}

Entry types: host, service, credential, finding. Recorded entries are tagged campaign:<workflow_id> and become queryable via the targetdb_* tools and the attack graph. Recording failures warn in the step result without failing the campaign.

Steps accept mitre: ["T1613", "T1552.001"] technique tags. Campaign results aggregate the unique techniques of executed steps as mitre_techniques, which can be passed to kubedagger_detection (action: "generate", techniques as a comma-separated list) to produce matching Falco/Sigma/audit detection rules. The shipped k8s-recon-chain playbook demonstrates recording and tagging end to end, including a final detection-rule generation step.

Method Params Description
campaign/run steps or template +vars Execute a campaign synchronously; checkpoints every step
campaign/start same as campaign/run Async : with the Tasks extension declared, returns aCreateTaskResult (taskId, ttlMs, pollIntervalMs) and runs detached from the request; without it, identical tocampaign/run . Idempotent: re-starting a running ID returns the live task
campaign/status workflow_id Load persisted state
campaign/list — List all saved campaigns
campaign/resume workflow_id Continue a d/cancelled campaign
campaign/cancel workflow_id Cancel a running campaign (works cross-replica: the engine re-reads persisted state at each checkpoint)
campaign/templates — List playbook templates
campaign/template/get name Fetch one template
campaign/template/save template JSON Validate and persist a playbook
tasks/get taskId Poll a task: working /input_required /completed /failed /cancelled ; terminal states carry the result or error
tasks/cancel taskId Cooperatively cancel a task (acks immediately)
tasks/update taskId ,inputResponses Submit approval responses for a d task ( approval for a sequential gate,approval-<sub_step_id> per gated sub-step inside a parallel group; unknown keys are acked and ignored)

Campaigns interrupted mid-step (cancellation or timeout) checkpoint as d and re-run the interrupted step on resume. Every campaign is single-writer (an advisory flock per workflow ID): a duplicate start of a running campaign returns its live task instead of corrupting the checkpoint.

Tools that require operator approval (high-impact tools not listed in REDHANDS_APPROVED_TOOLS) no longer need to block. When the request declares the Tasks extension, an approval-gated tools/call answers with resultType: "input_required" and an elicitation/create approval request; the client re-issues the same call with inputResponses: {"approval": {"approved": true}} and the single call executes (audited as an approval bypass). Inside a task-tracked campaign, an approval-gated step s the task (input_required via tasks/get), the response arrives through tasks/update, and campaign/resume re-runs the step. Requests without the capability fail closed with the classic message. Responses never ride in tool arguments — only in the dedicated inputResponses field.

Parallel groups use the same machinery per sub-step instead of failing the gated sub-step outright: when one or more sub-steps of a group trip approval gates, the campaign s ONCE and tasks/get shows one inputRequest per gated sub-step, keyed approval-<sub_step_id>. Answer each individually (tasks/update with inputResponses: {"approval-<sub_step_id>": {"approved": true}}; unknown or stale keys are acked and ignored). On campaign/resume the group replays partially: sub-steps that already completed are not re-executed, sub-steps that failed for other reasons keep their recorded failure, approved sub-steps re-run once with their own one-call bypass, denied sub-steps fail with approval denied and flow through the group's on_fail (stop/continue/goto), and unanswered gates re-trip with fresh request ids. A goto that re-enters a group is a fresh execution — the per-run completion set resets and every sub-step re-runs. Capability-less requests keep the classic fail-closed behavior for group sub-steps too.

Playbooks are reusable, versioned campaign templates stored as JSON in REDHANDS_WORKFLOW_TEMPLATES_DIR (default ./playbooks/). Shipped examples: recon-to-vuln, web-assessment, k8s-recon-chain, ad-kerberoast-chain, firmware-bootkit-assessment, telecom-defense-monitoring, firmware-purple-team. Point-and-shoot a playbook:

{
  "method": "campaign/run",
  "params": {
    "template": "recon-to-vuln",
    "vars": {"domain": "target.example", "severity": "critical,high"}
  }
}

Campaigns can bind to an engagement with the engagement parameter. The named engagement must exist and be the active engagement — the campaign fails fast instead of silently running against the wrong scope, and every step's targets are checked against that engagement's scope by the middleware:

{
  "method": "campaign/run",
  "params": {
    "engagement": "lab-1",
    "template": "web-assessment",
    "vars": {"target": "https://target.example"}
  }
}

Bindings are persisted with the campaign state and re-validated on resume, so switching engagements between runs rejects a stale binding.

Limits: 128 steps per campaign, 16 steps per parallel group, 64 goto recovery jumps, 16 MiB cumulative output — mirroring workflow hard caps.

Built-in parsers for common tool output formats:

  • Nmap XML — parses-oX output into structured hosts/ports/services
  • Nuclei JSON — parses JSONL findings into severity-grouped results
  • Masscan JSON — parses-oJ output into host/port pairs

Generate reports from workflow results:

{
  "method": "report/generate",
  "params": {
    "format": "markdown",
    "title": "Assessment report",
    "sections": [{"title": "Summary", "tool": "nmap_port_scan", "content": "Reviewed scan results"}]
  }
}

Formats: markdown (.md) and html (self-contained with inline CSS).

Define custom tools via JSON without writing Go:

{
  "name": "custom_quick_scan",
  "description": "Quick nmap top-100 scan",
  "binary": "nmap",
  "args_template": ["-sS", "--top-ports", "100", "{{.target}}"],
  "input_schema": {
    "type": "object",
    "properties": {
      "target": {"type": "string", "description": "Target IP or hostname"}
    },
    "required": ["target"]
  }
}

Drop JSON files in REDHANDS_PLUGINS_DIR (default ./plugins/) and they appear in tools/list automatically.

All settings via environment variables:

Variable Default Description
REDHANDS_TOOLSETS (all) Comma-separated toolsets to register (approval still applies)
REDHANDS_TIMEOUT 5m Execution timeout per tool call
REDHANDS_MAX_OUTPUT 10485760 Max output bytes per execution (10MB)
REDHANDS_RATE_LIMIT 10 Token bucket refill rate (requests/sec)
REDHANDS_RATE_BURST 20 Token bucket burst capacity
REDHANDS_CACHE_TTL 5m Result cache TTL
REDHANDS_CACHE_SIZE 100 Max pure-function results (oldest-expiry eviction)
REDHANDS_AUDIT_FILE audit.jsonl Audit log file path
REDHANDS_TRANSPORT stdio Transport mode: stdio ,sse , orws
REDHANDS_SSE_ADDR 127.0.0.1:8080 SSE HTTP listen address
REDHANDS_WS_ADDR 127.0.0.1:8081 WebSocket listen address
REDHANDS_AUTH none Auth mode: none ,apikey , ormtls
REDHANDS_API_KEY — API key (required when auth=apikey)
REDHANDS_TLS_CERT — TLS certificate path
REDHANDS_TLS_KEY — TLS private key path
REDHANDS_TLS_CA — CA certificate for mTLS client verification
REDHANDS_PLUGINS_DIR ./plugins Directory for JSON plugin definitions
REDHANDS_PLUGIN_POLL_INTERVAL 5s Plugin directory polling interval
REDHANDS_CREDSTORE_KEY — Strong encryption key required for credential persistence
REDHANDS_CREDSTORE_FILE credstore.enc Encrypted credential store file
REDHANDS_TARGETDB_FILE targetdb.json Target database file
REDHANDS_TARGETDB_AUTOSAVE true Persist target database changes immediately
REDHANDS_ENGAGEMENT_DIR engagements Engagement metadata directory
REDHANDS_DRY_RUN false Preview executions without running tools
REDHANDS_OPSEC_PROFILE — OPSEC profile name (parsed; proxy routing not yet wired)
REDHANDS_IOC_FILE — Indicator-of-compromise list file (parsed; matching not yet wired)
REDHANDS_TCP_ADDR — TCP transport listen address; mTLS only (API-key headers are not supported over TCP)
REDHANDS_DASHBOARD_ADDR — Dashboard HTTP listen address (parsed; dashboard not yet wired)
REDHANDS_GOPHISH_URL — GoPhish API base URL (enables the phishing toolset)
REDHANDS_GOPHISH_API_KEY — GoPhish API key
REDHANDS_MAX_CONCURRENT 4 Maximum active tool calls across clients and workflows
REDHANDS_MAX_QUEUED 16 Maximum queued calls; further requests fail without executing
REDHANDS_APPROVED_TOOLS — Exact comma-separated names approved by the operator for high-impact/unclassified tools
REDHANDS_WORKFLOW_TIMEOUT 30m Total deadline for a sequential workflow or campaign
REDHANDS_WORKFLOW_STATE_DIR workflow_state Campaign checkpoint directory
REDHANDS_WORKFLOW_TEMPLATES_DIR playbooks Playbook template directory
REDHANDS_WORKFLOW_MAX_PARALLEL 4 Max concurrent steps inside a campaign parallel group (≤32)
REDHANDS_EVIDENCE_DIR evidence Artifact directory with persistent metadata index
REDHANDS_LOOT_DIR loot Directory containing the atomic loot.json store

Non-loopback listeners require authentication and TLS. API keys are accepted only in the X-API-Key header. stdio uses local OS access controls, not API-key authentication. Caching is limited to pure MITRE/risk-score lookups, never scans or state-changing tools. High-impact, mixed-mode, and unclassified tools (including plugins) require explicit operator approval via REDHANDS_APPROVED_TOOLS. Dry-run previews do not require this approval and never execute tools. tools/list publishes policy metadata and MCP safety hints. See security and deployment notes for HTTPS/mTLS setup, scope limitations, credential migration, and remaining release work.

Evidence metadata persists in REDHANDS_EVIDENCE_DIR/index.json. Artifacts use random IDs rather than host/tool text in filenames, and are checked against recorded size and SHA-256 values when the store opens. Old files with no index remain untouched and are not automatically listed. Corrupt metadata or missing/changed indexed artifacts stop startup instead of silently losing their history.

Loot writes are atomic, and malformed loot.json files fail startup. Both stores are single-process stores: do not point multiple servers at the same directories. Artifacts and loot are private files, not encrypted storage; protect their directories and backups.

evidence_screenshot requires operator approval and cutycapt installed on PATH at startup. It uses the shared executor, private staging, HTTP(S)-only URLs, and PNG validation. CutyCapt may require a display/headless environment; it is not bundled in the current Docker image. Browser redirects and subresources still need network isolation.

See evidence and loot recovery before moving stores, repairing corrupt files, or handling legacy/unindexed artifacts.

Enable only specific toolsets:

export REDHANDS_TOOLSETS=nmap,impacket,crackmapexec,certipy,sliver,crack

export REDHANDS_TOOLSETS=nmap,recon,web,fuzz,scan,vuln

export REDHANDS_TOOLSETS=nmap,scan,tshark,tunnel

export REDHANDS_TOOLSETS=barzakh,telecom,aegis

export REDHANDS_TOOLSETS=
  • Binary allowlist — only known security tools can be executed
  • Argument validation — shell metacharacter rejection for external-tool arguments
  • Target validation and scope — explicit network targets checked against active engagement scope; not a complete network sandbox
  • Destructive-capability posture — tools that can destroy data or destroy a machine (aegis_offense_start withdeadman_switch orwipe_bytecode ) are opt-in, approval-gated, and emit loud warnings in every result; read the warnings in theAegis-Shadow andTelecom sections before using the offense tools
  • Output size cap — 10MB per execution
  • Execution timeout — configurable per tool call
  • Rate limiting — token bucket prevents abuse
  • Authentication — API key or mTLS for remote transports
  • Audit logging — direct calls and workflow steps logged as JSONL; logging failures reported to stderr
make build          # Build binary to bin/
make test           # Run tests with race detector
make lint           # Run golangci-lint
make docker         # Build Docker image
make install        # Install binary to /usr/local/bin
make install-tools  # Install security tools (PROFILE=minimal|web|network|ad|k8s|uefi|telecom|aegis|sbom|recon|all)
make check-deps     # Check which tools are available

Additional regression checks:

go vet ./...
python3 -m unittest discover -s scripts -p 'test_*.py'
go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 ./...

The wrapper unit tests build argv strings but verify them only against a MockExecutor. When an upstream binary changes its CLI, nothing catches it — the tools/livecheck suite does. It runs the real binaries and asserts that the flags, subcommands and enum values the wrappers emit are still accepted (help-text assertions plus safe dry-parses terminated by --help/--echo; nothing scans a target or mutates state).

make test-live

Notes:

The suite is gated behind the livebin build tag, sogo test ./... and the normal CI jobs are unaffected. #

Binaries are discovered via PATH ; a binary that is not installed is reported asSKIP ... binary not on PATH , never as a failure. The suite is safe to run on any machine. #

A check can also skip with a KNOWN CLI DRIFT /SKIPPED (distro quirk) message when the pinned or distro binary genuinely rejects the wrapper's argv (e.g. Ubuntu ships core john without the jumbo-only--list , and a broken whatweb package). Those skips are deliberate, printed in every run, and must lead to a wrapper fix or a dependency bump — never to deleting the check. #

In the Docker image (which ships ~25 pinned binaries but no Go toolchain), compile the suite on the host and run it inside the container, where the binaries live:

CGO_ENABLED=0 go test -tags=livebin -c -o livecheck.test ./tools/livecheck/
docker run --rm --network none -v "$PWD/livecheck.test:/usr/local/bin/livecheck.test:ro" \
  --entrypoint /usr/local/bin/livecheck.test redhands

The live-binaries CI job instead installs a minimal binary set on ubuntu-latest (apt plus the checksum-verified pinned releases viascripts/dependencies.py ) and runsgo test -tags=livebin directly. #

When you change a wrapper's argv, update the matching entry in tools/livecheck/table.go so drift detection stays meaningful.

Tier 1 (tools/livecheck) proves binaries still accept the wrappers' argv. Tier 2 (testdata/ golden fixtures) proves output shapes. The tools/e2e suite (Tier 3) proves real execution: it drives JSON-RPC tools/call and campaign/run requests into a fully wired in-process server — the same BinaryExecutor (allowlist from binaries.Discover()), the same middleware chain (audit to a temp file, rate limits, engagement scope, approval gates, concurrency limiter), the same registry with builtin tool policies, and the same campaign recorder as cmd/redhands — and asserts on what the real binaries actually did.

make test-e2e

Scenarios (all skip cleanly when their binary is missing):

  • Barzakh purple-team loop (offline, deterministic) —barzakh_corpus generates a corpus into a temp dir,barzakh_scan scans one malicious sample (scanner exit 1 = bootkit detected must surface as aresult with the severity summary rendered),barzakh_validate validates the corpus.
  • Campaign with real execution and record rules — acampaign/run whose steps execute the real corpus+scan tools through the middleware chain; the scan step's finding record rule persists the scanner's realfindings array into the target database, asserted via the store.
  • nmap against a test-owned listener — the suite opens a127.0.0.1:0 listener and runsnmap_port_scan (connect scan; SYN needs root) andnmap_service_detect against that exact port.
  • nuclei with a test-authored template — a minimalhttp -protocol template (static word matcher on a body the local test server always serves, severityinfo ) is written to a temp file and passed via thetemplates parameter; nothing is downloaded.
  • telecom_doctor —staticzero-defense --doctor --json : asserts the DoctorReport renders with a checks array and theebpf_object check passes (the release binary embeds the eBPF object); privilege failures are expected and fine.
  • telecom_firmware_analyze — a synthetic modem image (documented fixture generator with a fixed seed for the padding) analyzed withmodem-firmware analyze ; asserts the vulnerability report, its findings (strcpy, weak ciphers, debug interfaces), and the written report file.
  • kubedagger_k8s_discover against no cluster — only asserts the call returns a well-formed response without hanging (the wrapper's empty-stdout handling is intentionally not pinned).HOME is redirected so the client cannot pick up a developer's real kubeconfig.

Safety rules enforced by the suite:

  • The only network targets are 127.0.0.1 /localhost listeners the test itself owns; there is no network egress beyond the local binaries' own startup.
  • Everything deterministic: :0 listeners, fixed seeds for synthetic inputs, temp dirs, bounded polling (no sleeps) for the async audit log.
  • Gated behind the livee2e build tag, sogo test ./... and the normal CI jobs are unaffected.
  • Executing tools are pre-approved in the harness config exactly like a reviewed REDHANDS_APPROVED_TOOLS setup, so the approval middleware itself is exercised (not bypassed).

The live-e2e CI job installs the scenario binaries (nmap via apt; nuclei via the pinned go install; barzakh, StaticZero, and KubeDagger binaries via the checksum-verified pinned releases in scripts/dependencies.json) and runs go test -tags=livee2e ./tools/e2e/.... When you add a scenario that needs a new binary, add it to that job's install steps.

docker build -t redhands .
docker run -i redhands

docker compose run --rm -i redhands

docker compose --profile secure up redhands-secure

The Docker image includes a subset of supported security tools (check dependencies before use; some tools require additional installation or privileges):

  • apt/source : nmap, masscan, tshark, hashcat, john, nikto, whatweb
  • pip : impacket, certipy-ad
  • Go tools : subfinder, httpx, nuclei, ffuf, katana, gobuster, gau, waybackurls
  • Releases : chisel, ligolo-ng, kubedagger (client, operator, server v0.5.0), barzakh (adversary, scanner, server v0.7.1), staticzero (defense, modem-firmware, sdr-bridge v0.5.0), aegis-shadow-defense (v0.2.0)

MIT

RedHands is provided for authorized security testing, education, and research purposes only. You are solely responsible for ensuring you have explicit, written permission before using this tool against any system, network, or application. Unauthorized access to computer systems is illegal and punishable under applicable law including the Computer Fraud and Abuse Act (CFAA), Computer Misuse Act, and equivalent legislation worldwide.

The authors and contributors of RedHands accept no liability for misuse or damage caused by this software. Use responsibly and ethically.

── more in #ai-agents 4 stories · sorted by recency
── more on @redhands 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-redhands-mcp…] indexed:0 read:30min 2026-11-14 · —