Summary
The API server listens on every network interface and has no authentication. Anyone who can reach port 8888 can therefore run commands on the host. This is CVE-2025-35028 / GHSA-2j8m-rmq4-rcw5.
The cause is that app.run() hard-codes 0.0.0.0, even though the startup banner says 127.0.0.1.
This PR makes the server local-only by default. It requires a token before the server will listen on any other address.
Nothing changes for local use.
Fixes #122 Fixes #115 Addresses #124, #263
What changes
| Area | Before | After |
|---|---|---|
| Bind address | Always 0.0.0.0 | 127.0.0.1 by default; --host or HEXSTRIKE_HOST to change it |
| Authentication | None | With HEXSTRIKE_API_TOKEN set, every request needs Authorization: Bearer <token> (constant-time compare) |
| No-token mode | Any Host header accepted | Only localhost, 127.0.0.1 or ::1. Blocks DNS rebinding. |
| Startup | Starts anywhere | Refuses a non-local --host without a token, and refuses --debug on a non-local host |
| File and env paths | Request paths joined unchecked | Kept inside /tmp/hexstrike_files and /tmp/hexstrike_envs. Rejects absolute paths, .., symlink escapes, and deleting the base folder. |
| Browser agent | Any URL scheme | http and https only |
| MCP client | No credentials | Sends the token from HEXSTRIKE_API_TOKEN |
Also in this PR:
SECURITY.md: threat model, deployment rules, private reporting.- README:
--hostand token setup. - Sample MCP config: an
envblock for the token. - Minimum Flask version raised to
2.3.2. - 21 regression tests.
- A step-by-step testing guide for reviewers.
Issues
| Issue | Status |
|---|---|
| #122: binds all interfaces | ✅ Fixed |
#115: no SECURITY.md; CVE-2025-35028 | ✅ Fixed |
#124: /api/command RCE | ✅ Fixed for unauthenticated callers ¹ |
#263, #224: injection in /api/tools/* | ✅ Fixed for unauthenticated callers ¹ |
| #204: pickle deserialization | ➖ Not reproducible ² |
| #161: Flask CVE-2023-30861 | ➖ Not exploitable here; Flask floor raised ³ |
¹ The API runs commands by design, so an authenticated caller still can. The token is the control, as SECURITY.md documents.
² No request data is deserialized. The pickle code (hexstrike_server.py:7124, :8189) is text that the exploit generator outputs.
³ The CVE requires Flask sessions, and the server uses none.
It also fixes four problems that have no open issue:
- Path traversal in
/api/files/*. - DNS rebinding.
- The Werkzeug debugger exposed on
0.0.0.0. file://andjavascript:URLs in the browser agent.
⚠️ Breaking change: remote setups
A server on another machine now needs the same token on both sides, or it exits at startup.
On the server:
export HEXSTRIKE_API_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
python3 hexstrike_server.py --host 0.0.0.0In the MCP client config:
"env": { "HEXSTRIKE_API_TOKEN": "<same value>" }The token is sent over plain HTTP. Across networks, use an SSH tunnel or a TLS reverse proxy.
Testing
To validate this yourself, follow tests/SECURITY_TESTING.md.
- Part A (about 2 minutes) runs the regression tests and shows that they fail on
master. - Part B (about 10 minutes) checks a running server by hand. It gives the expected output for each step.
Quick check:
pip install flask requests
python3 -m unittest discover -s tests- 21 of 21 tests pass, on Python 3.13 with mitmproxy stubbed, and on Python 3.12 with the real dependencies (Flask 3.0.3, mitmproxy 10.4.2).
- 19 of the 21 fail on
master. The two that pass there check normal file operations. - Every manual step in the guide was run against a live server:
- Startup refusals: exit 1.
- Bind address:
127.0.0.1. - Foreign
Host:403. - Escaping file paths: refused.
- Token mode:
401,401,200. - MCP client: connects with the token, gets
401without it. - The optional browser step (B7) needs chromedriver and was not run.
- Security review of the full diff: no high-confidence findings.
Not verified:
- A full MCP stdio session driving tools against a live server.
- The client needs
mcp<2, because itsfrom mcp.server.fastmcp import FastMCPimport fails withmcp2.x. This predates this PR.
Out of scope
- Converting the ~90
/api/tools/*handlers fromshell=Trueto argument lists. It would give authenticated callers no capability beyond what/api/commandalready allows. - TLS.
🤖 Generated with Claude Code