feat(security): fail-closed auth, scope enforcement, audit logging, Docker isolation - #226
Open
fxinfo24 wants to merge 9 commits into
Open
feat(security): fail-closed auth, scope enforcement, audit logging, Docker isolation#226fxinfo24 wants to merge 9 commits into
fxinfo24 wants to merge 9 commits into
Conversation
…udit log - app.run() no longer hardcodes host=0.0.0.0; new --host/HEXSTRIKE_HOST flag defaults to 127.0.0.1 so the 150+ API routes (including /api/command's arbitrary shell exec) aren't LAN-exposed by default - add shared-secret auth via X-HexStrike-Token header, checked with hmac.compare_digest on every route; server exits at startup if HEXSTRIKE_API_TOKEN is unset rather than running unauthenticated - add lightweight GET /ping for fast liveness checks; /health remains the full ~30s tool-availability diagnostic sweep - add opt-in engagement scope enforcement (HEXSTRIKE_SCOPE_FILE): allowlist authorized domains/CIDRs, 403 any request whose target (structured param or parsed out of a raw command string) falls outside it — see scope.example.json - add JSONL audit log (HEXSTRIKE_AUDIT_LOG) of every authenticated request: timestamp, source, method, path, targets, status - update README: document new env vars, fix install/verify examples to include the token header, add .gitignore
…tion - add Dockerfile (kalilinux/kali-rolling base): apt-based core tool set (nmap, masscan, gobuster, sqlmap, hydra, hashcat, radare2, binwalk, etc.) plus Go-based recon tools (nuclei, subfinder, httpx, ffuf) via a timeout-capped, non-fatal install loop so one stalled module fetch can't block the whole build - python deps isolated into a venv rather than system pip, avoiding PEP 668 conflicts with Kali's dpkg-managed packages (bcrypt etc.) - non-root execution (uid 1000); nmap/masscan get raw-socket capabilities via setcap on the binaries specifically, not on the container as a whole - add entrypoint.sh: fails fast with a clear message if HEXSTRIKE_API_TOKEN is unset, reports scope-enforcement status - add .dockerignore - README: document the Docker path with a per-engagement run example (isolated scope file + workspace/audit-log volume per engagement), and clarify why the container's internal 0.0.0.0 bind doesn't contradict the loopback-only guidance elsewhere in the doc Verified end-to-end against a running container: auth enforced, non-root confirmed via whoami/id, workspace + audit log correctly isolated to the per-engagement host directory.
…, xsstrike, sstimap - arsenal-ng, gef, wpprobe, xsstrike, sstimap installed via apt (confirmed present in Kali's live apt index before adding, avoiding a blind package-name guess) - atomic-operator (MITRE ATT&CK/Atomic Red Team technique execution) isn't in apt — installed via pip into its own isolated venv, not the main app venv. Its dependency atomic-operator-runner hard-pins pydantic 1.x (confirmed via its pyproject.toml), which conflicts with mcp/fastmcp's pydantic 2.x requirement — installing it into the shared venv silently downgraded pydantic and broke hexstrike_mcp.py's imports at runtime (verified by hand, then fixed via isolation) - atomic-operator's other dependency still imports the stdlib module, removed in Python 3.13 (Kali-rolling's default python3) — a known, still-open upstream bug (google/python-fire#444) with no fixed release. Patched via a local sed (pipes.quote and shlex.quote are functionally identical — that's fire's own documented migration path) - gef: the apt package only drops gef.py at /usr/share/gdb/gef.py without wiring it into gdb's startup — added a system-wide gdbinit source line - xsstrike: pre-installed fuzzywuzzy (it pip-installs this at runtime on first use otherwise, requiring outbound network access mid-engagement) - arsenal-ng installs fine but is a TUI app requiring a real tty — not usable via /api/command's non-interactive execution, noted as a usage-pattern limitation rather than a bug Sourced from a 10-tool proposal; the other 4 were evaluated and not included: sstimap replaces the already-listed tplmap (unmaintained since ~2019) rather than being added alongside it, legba skipped as redundant with the existing hydra/medusa/patator, fluxion held back over collateral-impact concerns (WiFi deauth affects every client on the channel, not just the scoped target), hydra was already present. All 6 verified working end-to-end against a running container.
…), not pip The previous commit worked around a pydantic 1.x/2.x conflict by isolating atomic-operator into its own pip venv plus a manual sed patch for a stdlib 'pipes' module removal in Python 3.13 (fire's still-open upstream bug, google/python-fire#444). Turns out that was solving a problem the real fix doesn't have: Kali packages this as python3-atomic-operator (confirmed against the live apt index and via kali.org/tools/atomic-operator), with its own dependency graph (python3-fire, python3-atomic-operator-runner, etc.) resolved entirely through apt — completely separate from this project's pip venv, so there's no pydantic conflict to isolate against in the first place, and no pipes-removal patch needed (Kali's own package build already handles it). Net effect: one apt package instead of an isolated venv + symlink + sed patch. Verified clean: hexstrike_mcp.py still imports correctly (pydantic stays at 2.13.4 in the main venv), atomic-operator's CLI works standalone.
…rtipy; retire dirb 7 tools from a 10-tool proposal (3 already covered: Nuclei/ffuf/Responder were already installed). All verified working end-to-end. - rustscan, amass, feroxbuster, python3-impacket, netexec, certipy-ad — confirmed against the live apt index before adding - dirb removed: gobuster + feroxbuster now cover directory brute-forcing more capably, running three overlapping tools isn't worth it - amass's apt wrapper shells out to 'sudo libpostal_data download all' on first run if libpostal's data dir is missing — fails under the non-root hexstrike user, and would pull ~1-2GB even as root for an address-normalization feature amass's actual recon functionality (DNS enum, ASN lookups, cert-transparency, subdomain brute-force) doesn't touch. Satisfied the wrapper's existence check with an empty sentinel instead of downloading real data — verified amass's real subcommands (enum, etc.) work fine without it. fix(docker): stop shadowing system python3 for apt-packaged tool wrappers Root cause, not a one-off patch: this Dockerfile prepended /opt/venv/bin to the global PATH so our own app would resolve pydantic 2.x/mcp/fastmcp correctly. Several apt-packaged tools ship thin wrapper scripts that call bare `python3` rather than an absolute path (confirmed: impacket-secretsdump and the rest of the impacket-* family) — with venv-first PATH, those silently resolved to our isolated venv instead of system python3, and since venvs don't see system dist-packages, they failed with ModuleNotFoundError for a library that was actually installed and present on disk. Root-caused by hand: checked the wrapper's exec line, confirmed the library was on disk via dpkg -L, confirmed system python3's sys.path included dist-packages, isolated it to the PATH shadowing specifically. Fix: removed the global PATH prepend entirely. Our own app now references the venv by absolute path (/opt/venv/bin/pip at build time, /opt/venv/bin/python3 in entrypoint.sh) instead of relying on PATH order. Bare `python3` now resolves to the system interpreter everywhere, so every apt-packaged tool's wrapper works as its maintainer intended. This wasn't impacket-specific — any Kali tool with a similar wrapper pattern was at risk before this fix, impacket just happened to be the one that surfaced it. Verified after the fix: impacket-secretsdump works correctly, and hexstrike_mcp.py still imports cleanly with pydantic held at 2.x in the main venv (checked explicitly via /opt/venv/bin/python3).
hexstrike_mcp.py (the actual client Claude Desktop/Code/VS Code spawn to talk to the server) predates the auth work entirely — HexStrikeClient's requests.Session() never set any header, so every single tool call would have 401'd against the now-hardened server, bare-metal or Docker, with no indication why beyond generic request failures. - inject X-HexStrike-Token from HEXSTRIKE_API_TOKEN env var; log a clear error (not a silent failure) if it's unset, matching the server's own fail-closed posture - switch the startup connectivity check from /health to /ping — /health runs a ~30s full tool-availability sweep, which would stall every MCP client startup for no reason; /ping is the fast liveness check that exists for exactly this Verified against a live Docker engagement: confirmed the auth header is actually sent, confirmed the client connects and the server reports healthy immediately rather than after a 30s wait.
…t field Discovered live, during actual authorized testing against pathosbay.com: scope enforcement checked payload['command'] specifically for embedded domains/IPs, plus a fixed list of structured keys (target/host/url/etc). /api/python/execute uses a different field entirely — 'script' — which accepts arbitrary Python that can make its own HTTP requests to any target. None of that was covered, so scope enforcement had zero visibility into what a submitted script actually touched: it was a full bypass, not a partial gap, for that endpoint and any other similarly-shaped one we hadn't specifically audited. Confirmed exploitable in practice: a python-execute call against an out-of-scope-at-the-time domain returned real page content before this fix landed. Fix: generalized _extract_targets to scan every string value in the request payload for embedded domains/IPs (same heuristic already used for 'command'), not just a hardcoded field name. Closes the specific /api/python/execute gap and any future endpoint with a differently named free-text field, without needing to enumerate every endpoint's parameter naming by hand. Verified against a live engagement: out-of-scope target via /api/python/execute now correctly 403s; in-scope target passes the scope check through to actual execution.
… errors - ENV PATH appended /opt/go/bin instead of prepending, so pip's httpx[cli] console script (installed elsewhere on PATH) silently shadowed ProjectDiscovery's real httpx binary. Go tools now take priority for every binary in the go-install list, not just httpx. - waybackurls was never in the go-install list despite being wired into the MCP tool surface (waybackurls_discovery) -- added. - go-install loop swallowed all stderr via a bare "|| echo SKIPPED", making install failures silent. Now tees the last 20 lines of actual output so failures are diagnosable from the build log. Also bumped the per-package timeout 180s -> 300s; nuclei's install legitimately needs more than 180s on a cold module cache.
…timeout
- _extract_targets() never split comma-separated values before the
scope-domain match, so any multi-host batch call (httpx, dnsx, ...)
in a single target/host field was falsely rejected as out_of_scope.
Confirmed live against the pathosbay.com engagement scope.
- execute_httpx_scan() and /api/tools/httpx both called a bare httpx,
which resolves to pip's httpx[cli] HTTP client (same name, unrelated
tool) ahead of the real recon binary on PATH -- see Dockerfile commit.
Pinned both call sites to HEXSTRIKE_HTTPX_BIN (absolute path,
env-overridable) and replaced "-l {target}" (expects a filename, not
an inline host string) with stdin piping via printf %b.
- execute_amass_scan() and /api/tools/amass never bounded amass's own
runtime. Passive/active enum can run well past the generic 300s
command-timeout wrapper and gets hard-killed mid-write instead of
exiting cleanly. Added an explicit -timeout (HEXSTRIKE_AMASS_TIMEOUT_MIN,
default 4 min), skipped if the caller already supplied one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This server executes arbitrary commands and 150+ offensive-security tools (
/api/command,/api/python/execute,/api/files/create, etc.) with zero authentication and a hardcoded0.0.0.0bind — the README even flags this ('Consider implementing authentication for production deployments') without actually shipping any. Anyone on the same LAN as a running instance currently has unauthenticated remote command execution. This PR closes that gap and adds the controls a real pentest engagement needs: scope enforcement and an audit trail.Changes
Auth (fail-closed)
127.0.0.1by default (--host/HEXSTRIKE_HOSTto override)X-HexStrike-Tokenheader,hmac.compare_digest, checked on every routeHEXSTRIKE_API_TOKENis unset — no silent unauthenticated fallbackNew
/pingendpoint/healthdoes a full ~30s sweep across all tools (subprocess-based detection) — too slow for liveness checks/monitoring/pingis instant, no tool sweep, for that use caseEngagement scope enforcement (opt-in via
HEXSTRIKE_SCOPE_FILE)scope.example.json)target/host/domain/ip/url/rhost/hostname) and targets parsed out of rawcommandstrings (conservative — only known-TLD-suffixed tokens, avoids false positives on filenames likeresults.txt)403on anything out of scopeAudit log
Docker
Dockerfile(Kali-rolling base): apt-based core tool set + Go-based recon tools (nuclei, subfinder, httpx, ffuf) via a timeout-capped, non-fatal install loop so one stalled module fetch can't block the buildpip --break-system-packagescan't safely touch those under PEP 668; hit this directly withbcrypt)nmap/masscanget raw-socket capabilities viasetcapon the binaries specifically, not the container as a wholeTesting
All changes verified against a live instance, both bare-metal and containerized:
whoami/id), confirmednmapfunctional despite non-root, confirmed workspace + audit isolation (file written inside container correctly landed in the host-side per-engagement directory)Compatibility
Everything except the auth requirement is opt-in or additive. The one breaking change is intentional: the server no longer starts without
HEXSTRIKE_API_TOKENset. Given what/api/commanddoes, I don't think that should be optional.