TL;DR
An npm package we'd been using for two years had a critical prototype pollution vulnerability. We didn't know until a security researcher contacted us. The vulnerable code was executing on every page load. Updating the dependency took 30 minutes; understanding why we hadn't caught this sooner took much longer.
Modern web apps are built on towers of dependencies. We had 847 packages in our node_modules folder. One of them had been vulnerable for 8 months, and we had no idea.
The Wake-Up Call
A security researcher emailed us on a Tuesday morning. They'd been auditing applications using a popular utility library and found us through Wappalyzer detection.
"Your application appears to be using lodash version 4.17.11, which has a known prototype pollution vulnerability (CVE-2019-10744). This could allow attackers to inject properties into JavaScript objects, potentially leading to remote code execution."
My first reaction was defensive. We barely use lodash directly. But then I checked our package-lock.json and found it was a dependency of a dependency. And yes, we were on a vulnerable version.
Understanding the Risk
Prototype pollution sounds academic until you realize what it means in practice:
- Attackers can modify the behavior of built-in JavaScript objects
- In Node.js, this can sometimes lead to remote code execution
- In browsers, it can enable XSS attacks or authentication bypasses
- The attack surface exists anywhere user input touches affected functions
We ran npm audit for the first time in months. The results were sobering.
$ npm audit
found 23 vulnerabilities (7 low, 12 moderate, 4 high)
run `npm audit fix` to fix them
Twenty-three vulnerabilities. Four of them high severity. And we'd been ignoring this for who knows how long.
Why We Missed It
- Never ran npm audit in CI/CD pipeline
- No automated dependency update process
- Locked to specific versions without review
- Assumed "if it works, don't touch it"
- No security monitoring for third-party packages
The Fix
The immediate fix was simple:
npm audit fix
# For the stubborn ones that needed manual intervention
npm update lodash
npm update minimist
npm update node-fetch
But some packages had breaking changes. We spent two days testing after updates, finding and fixing compatibility issues. It would have been much easier if we'd kept things updated incrementally.
Building Better Practices
After this incident, we implemented proper dependency management:
# .github/workflows/security.yml
name: Security Audit
on:
push:
schedule:
- cron: '0 0 * * 1' # Weekly Monday scan
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm audit --audit-level=moderate
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
We also enabled Dependabot for automated pull requests when updates are available:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- Run npm audit (or equivalent) in your CI/CD pipeline
- Enable automated dependency updates (Dependabot, Renovate)
- Review and update dependencies regularly, not just when things break
- Use tools like Snyk or npm audit to catch vulnerabilities early
- Understand your dependency tree - vulnerabilities hide in transitive dependencies
- Don't ignore security warnings just because your app "works fine"
The Hidden Cost of Neglect
The technical fix took half a day. But the real cost was the risk we'd unknowingly exposed our users to for months. A prototype pollution attack could have allowed:
- Session hijacking through manipulated objects
- Privilege escalation if user roles were object-based
- Data theft through modified API behavior
- Complete server compromise in worst-case scenarios
We were lucky. The researcher who found our vulnerability was ethical. Next time, we might not be so fortunate.
How often should I run npm audit?
Run it on every CI build to catch issues before deployment. Also run weekly scheduled audits to catch newly discovered vulnerabilities in existing dependencies.
::
What's the difference between npm audit and Snyk?
npm audit uses the npm advisory database and is free and built-in. Snyk has a larger vulnerability database, provides fix suggestions, and offers additional features like license compliance checking. Many teams use both.
Should I auto-merge Dependabot PRs?
For patch updates with passing tests, many teams auto-merge. For minor or major updates, manual review is safer. Always have good test coverage before enabling any auto-merge.
::
Scan your vibe coded projects for vulnerable dependencies and security issues.
Check Your Vibe Now