On September 8th, an attacker compromised the npm account of ~qix, a prolific open source maintainer, through a phishing attack. The attacker injected malicious code into widely-used npm packages, redirecting cryptocurrency transactions to attacker-controlled addresses. Although the incident was detected and the malicious versions removed, they had already been downloaded by many users.
This incident highlights a significant vulnerability: a single phishing email bypassed all downstream security controls organizations rely on when pulling packages from npm.
Timeline
September 8th, 2024
- Phishing email sent to ~qix
- Maintainer credentials compromised
- Attacker gains npm publishing rights for multiple packages
- Malicious versions published with crypto-stealing payload
- Downloads begin immediately through normal package manager workflows
Detection and Response
- Snyk and other security researchers identify suspicious package behavior
- npm notifies affected users
- Malicious versions unpublished
- Account access revoked
The time between compromise and detection remains unclear, but even a few hours is enough for thousands of automated CI/CD pipelines to deploy compromised code.
Which Controls Failed or Were Missing
Account Security
The maintainer account lacked multi-factor authentication (MFA). Although npm has supported 2FA since 2017, it remains optional for most accounts. A single password was all that stood between an attacker and the ability to poison packages with millions of downloads.
Code Review
No human reviewed the malicious commits before publication. For packages with significant downstream impact, there was no requirement for a second review, no mandatory review period, and no automated behavioral analysis before the new version hit the registry.
Dependency Pinning
Organizations likely used version ranges (^1.2.0 or ~1.2.0) rather than exact version pins. When the malicious version was published, their next build automatically pulled it, introducing compromised code into production systems without human intervention.
Runtime Monitoring
The malicious code made network calls to external addresses for cryptocurrency redirection. Most organizations lack runtime monitoring that would flag when a utility package suddenly starts making HTTP requests or accessing crypto wallet APIs.
What the Standards Require
PCI DSS v4.0.1 Requirement 6.3.2 mandates "review of custom code prior to release to production or customers." This principle applies to your supply chain: code entering your production environment should face review, whether you wrote it or pulled it from npm.
Requirement 6.4.3 addresses scripts loaded from external sources: "The integrity of scripts is verified before execution." If you're pulling packages from npm without verifying their integrity, you're not meeting this control. npm's package signatures exist, but most organizations don't verify them.
NIST 800-53 Rev 5 Control SA-10 requires organizations to "require the developer of the system, system component, or system service to perform security testing." Treating npm packages as trusted code without verification outsources this control to maintainers who may not have MFA enabled.
ISO/IEC 27001 Control 8.31 implies that new code versions should pass through testing before reaching production. Automatic dependency updates that skip your test environment violate this control.
Lessons and Action Items for Your Team
Enforce MFA for Publishing Accounts
If your team maintains any public packages, require hardware security keys for publishing rights. Create an internal policy: no one publishes to public registries from an account without hardware 2FA.
Pin Exact Versions
Stop using version ranges in production dependencies. Your package.json should specify "dependency": "1.2.3" not "dependency": "^1.2.3". This means manual update decisions. Every version change should be a conscious choice that goes through your testing pipeline.
Implement Package Verification
Use npm audit signatures to verify package signatures before installation. Add this to your CI/CD pipeline as a required step. If a package isn't signed, or the signature doesn't match, the build should fail.
Monitor Dependency Behavior
Deploy runtime application self-protection (RASP) or similar monitoring that alerts when dependencies exhibit unexpected behavior: network calls to new domains, file system access patterns that changed between versions, or crypto wallet API usage in packages that shouldn't touch cryptography.
Create a Package Allowlist
For critical systems, maintain an explicit allowlist of approved packages and versions. New packages or version updates require security review before they're added to the allowlist. Your build process should reject anything not on the list.
Scan for Secrets in Dependencies
The phishing attack succeeded because credentials existed to steal. Run secret scanning tools against your installed node_modules directory. If you find API keys, tokens, or credentials in third-party code, that's a separate vulnerability regardless of whether the package is compromised.
Test Dependency Updates in Isolation
When you update a pinned dependency, deploy the new version to an isolated test environment first. Run your full test suite and monitor for behavioral changes: new network calls, different file access patterns, or performance anomalies.
Establish Incident Response for Supply Chain
Your incident response plan should include a specific playbook for compromised dependencies: How do you identify which systems pulled the malicious version? How quickly can you roll back? Who has authority to block a package across all environments? Test this playbook before you need it.
The ~qix compromise exploited the trust model that makes open source efficient. Your job is to verify that trust without breaking your development velocity. Start with MFA and version pinning. Everything else builds from there.



