Postmortem: Our Next.js 15 App Was Hacked via a Third-Party npm Package
A detailed breakdown of how a compromised dependency led to a full application breach, and how we hardened our supply chain.
Incident Summary
On October 12, 2024, our team detected unauthorized access to our production Next.js 15 application, which powers our e-commerce checkout flow. The breach originated from a malicious update to a third-party npm package we used for legacy image optimization, next-legacy-image-optimizer (v2.1.4), which had been compromised via a maintainer phishing attack.
Timeline of Events
- October 10, 2024, 14:22 UTC: The legitimate maintainer of
next-legacy-image-optimizerfell for a phishing email disguised as an npm security alert, handing over their 2FA credentials. - October 10, 2024, 16:45 UTC: Attackers published a malicious v2.1.4 update to the package, injecting a cryptomining script and a backdoor that exfiltrated environment variables.
- October 11, 2024, 09:15 UTC: Our CI/CD pipeline ran
npm installas part of a routine nightly build, pulling the compromised package version without triggering any alerts (we had no dependency pinning for patch versions). - October 11, 2024, 10:30 UTC: The compromised build was deployed to production via our automated Vercel deployment pipeline.
- October 12, 2024, 08:45 UTC: Our monitoring system flagged abnormal CPU usage on Vercel edge nodes, and Sentry caught unhandled errors from the injected script.
- October 12, 2024, 09:20 UTC: We rolled back to the previous build (v1.2.3) and revoked all production environment variables as a precaution.
Root Cause Analysis
We identified three contributing factors to the breach:
- Unpinned Dependency Versions: We used caret (^) semver ranges for all third-party dependencies, allowing automatic patch updates. The malicious v2.1.4 was a valid patch update from v2.1.3, so it was pulled automatically.
- No Supply Chain Scanning: We did not run static analysis or supply chain security tools (e.g., Snyk, npm audit) in our CI/CD pipeline, so the malicious package passed all checks.
- Over-Privileged Environment Variables: Our production build process had access to all environment variables, including
STRIPE_SECRET_KEYandJWT_PRIVATE_KEY, which the backdoor exfiltrated to a remote C2 server.
The malicious package code injected a postinstall script that added a middleware to our Next.js 15 App Router, which ran on every request. The middleware checked for admin routes, exfiltrated cookies and headers, and executed the cryptomining script during off-peak hours.
Impact
- ~12 hours of abnormal cryptomining activity on Vercel edge nodes, leading to a $420 unexpected infrastructure bill.
- Exfiltration of 14 production environment variables, including payment processor keys (no fraudulent transactions occurred, as we rotated keys immediately).
- ~2 hours of downtime during rollback, affecting 1,200+ checkout sessions.
- No customer data was accessed, as PII is stored in a separate, isolated database with no dependency access.
Remediation Steps
- Rolled back to the last known good build (v1.2.3) and pinned all dependencies to exact versions in
package-lock.json, committing the lockfile to version control. - Revoked all production environment variables, rotated secrets, and restricted build-time environment variable access to only required keys using Vercel's environment variable scoping.
- Integrated Snyk into our CI/CD pipeline to scan all dependencies for known vulnerabilities and malicious packages before deployment.
- Replaced
next-legacy-image-optimizerwith Next.js 15's built-innext/imagecomponent, removing the third-party dependency entirely. - Conducted a full audit of all third-party packages, removing 7 unused dependencies and downgrading 3 others to maintainer-verified versions.
Lessons Learned
- Never use caret (^) or tilde (~) semver ranges for dependencies in production applications. Always pin to exact versions and review all dependency updates manually.
- Supply chain security is as important as application security. Run dependency scans in CI/CD, and use tools like Socket to detect suspicious package behavior (e.g., unexpected postinstall scripts, network requests).
- Follow the principle of least privilege for environment variables: only expose what the build process absolutely needs, and never include production secrets in build-time variables if possible.
- Next.js 15's App Router middleware is a high-value target for attackers. Audit all custom middleware and third-party packages that modify the request/response lifecycle.
Conclusion
This breach was a wake-up call for our team to prioritize supply chain security. While the impact was limited, it highlighted gaps in our dependency management and CI/CD processes. We've since implemented all remediation steps, and no further malicious activity has been detected. We hope sharing this postmortem helps other Next.js teams avoid similar pitfalls.
Top comments (0)