Security and quality in JavaScript projects
A typical JavaScript project runs code from hundreds of strangers, ships its front end to every user's device, and puts user text on screens. Each of those is a door. This lesson shows how attackers use them, and the small set of habits and tools that keep them shut.
By the end you can
- Explain how a malicious or hijacked npm package reaches production, and four defences against it
- Read
npm auditoutput and decide what to fix, pin or ignore - Spot and fix an XSS bug caused by
innerHTML, and explain whyevalis the same kind of risk - Move a secret out of front-end code into a server endpoint
- Add security lint rules and use a short JavaScript review checklist
The everyday picture
A Mangrove Market distribution centre (DC) receives cartons from hundreds of suppliers every day. Most are honest. But the receiving team still checks each delivery against the purchase order: the right supplier, the right item codes, the seal intact, nothing extra in the carton. A carton that looks right but comes from a supplier with a slightly different name gets refused. A supplier whose truck was hijacked on the way can deliver a sealed carton with the wrong contents, so the team also checks samples and keeps records of what came from where.
Your node_modules folder is that DC. Every npm install is a delivery from many suppliers, and some of what arrives runs immediately, before anyone opens the carton. The receiving checks in this lesson are the lockfile (the purchase order), npm audit (the recall list), install script controls (not letting a carton unpack itself) and provenance (a sealed record of which factory made it).
The supply chain: you run strangers' code
A small Node project with ten direct dependencies often installs several hundred packages, because each dependency has its own. Any one of them can run code on a developer laptop, in CI, and in production. Attackers know this. The main routes are:
- Typosquatting. In 2017 a package called
crossenvimitated the popularcross-env. It worked like the real one and also sent the machine's environment variables (tokens, passwords) to the attacker. It went unnoticed for about two weeks. - Malicious install scripts. A package can declare a
preinstallorpostinstallscript. Older npm versions run these automatically duringnpm install, with your user's permissions, before you have imported anything. - Compromised maintainers. On 8 September 2025, a maintainer of widely used packages such as
chalkanddebugentered his login and 2FA code on a fake npm site. The attacker published new versions with code that swapped crypto wallet addresses in browsers. The community noticed within about two hours. A week later a self-copying worm called Shai-Hulud used stolen tokens to inject post-install scripts into more packages; GitHub removed more than 500 of them. - Known vulnerabilities. No attacker involved: an honest package simply has a bug, such as a regular expression that takes minutes on certain input, or a merge function that allows prototype pollution (below).
The lockfile (Node.js and npm) is your first defence: npm ci installs exactly the versions and integrity hashes recorded in package-lock.json, so a new malicious version of a dependency cannot sneak in on the next CI run. It does nothing for a version that was already bad when you locked it, which is why the other defences exist.
Quick check
In which case does a committed lockfile with npm ci NOT protect you?
The lock keeps whatever you locked, good or bad, so a version that was bad at lock time stays installed. A newer bad version cannot arrive without a lock change, and a swapped file fails the integrity hash. That gap is why audits, diff reviews and release-age delays exist.