The string ^1.0.0 is in virtually every package.json on the planet. Most developers type it without thinking. But in 2026, with 454,648 malicious npm packages published in a single year — a 75% year-over-year increase — that little caret symbol is one of the most consequential security decisions you make when adding a dependency. This guide breaks down exactly what semver ranges do, how attackers exploit them, the real CVEs they enabled, and what defenses work in 2026.
What Semantic Versioning Actually Promises (And Doesn't)
Semantic Versioning (semver) is a contract: MAJOR.MINOR.PATCH. The promise is that MAJOR bumps introduce breaking changes, MINOR adds features backward-compatibly, and PATCH fixes bugs. npm wraps this contract in range operators that define which future versions are acceptable.
Here's what each operator actually means for security:
# Caret (^) — allows MINOR and PATCH updates
"axios": "^1.6.0" → accepts 1.6.0 to <2.0.0
# Tilde (~) — allows PATCH updates only
"axios": "~1.6.0" → accepts 1.6.0 to <1.7.0
# Exact — pinned, no automatic updates
"axios": "1.6.0" → accepts only 1.6.0
# Wildcard (*) — accepts anything
"axios": "*" → accepts any version (never use this)
# Range — explicit bounds
"axios": ">=1.6.0 <2.0.0" → same as ^1.6.0 but explicit
The security implication of ^: if an attacker publishes axios@1.99.0 with a backdoor, every project using "axios": "^1.6.0" that runs npm install (not npm ci) will pull it in automatically. No review. No warning. This is the semver range exploit.
CVE-2022-25883: The Vulnerability Inside the Version Checker
Before covering how ranges are weaponized, there is a critical CVE affecting the semver package itself — the very library that evaluates version ranges across the npm ecosystem.
CVE-2022-25883 (CVSS 5.3) is a Regular Expression Denial of Service (ReDoS) vulnerability in the semver npm package before versions 7.5.2, 6.3.1, and 5.7.2. The new Range() function used backtracking-prone regex patterns to parse version strings. When fed crafted input, processing time grew exponentially — allowing a remote attacker to trigger denial of service by passing untrusted user data as a version range.
// Triggering CVE-2022-25883 (illustrative — patched in 7.5.2+)
const semver = require('semver') // version < 7.5.2
// Crafted input causes catastrophic backtracking
const maliciousRange = '1.' + '2'.repeat(100000)
semver.validRange(maliciousRange) // blocks event loop for seconds
What made CVE-2022-25883 severe in practice was not its CVSS score, but its cascade: the semver package receives 643 million downloads per week (Snyk, 2026). Every package manager, build tool, linter, and CI platform that touched npm version resolution used it. IBM, Red Hat, Ubuntu, and Rocky Linux all shipped security bulletins. The real lesson: a single vulnerability in a foundational utility propagates to thousands of packages without any of them declaring a direct dependency on the vulnerable code.
How Attackers Weaponize Semver Ranges in 2026
Understanding CVE-2022-25883 is about a vulnerability in semver. But the broader attack surface is about how semver ranges are used as an attack vector. There are three primary patterns:
1. Malicious Version Publishing Into Existing Ranges
The most direct attack: publish a new version of a legitimate package that falls within existing ^ or ~ ranges in thousands of projects. When developers run npm install, they pull the malicious version automatically. This is exactly what happened with the September 2025 npm supply chain attack, which compromised chalk, debug, and 16 other packages with a combined 2.6 billion weekly downloads (ArmorCode, 2025).
The attack window is measured in hours. Registries typically remove malicious packages within 24-48 hours — but in that window, any developer who ran npm install is compromised. Critically, lockfiles protect against this only if npm ci is used. npm install will update to the latest matching version, silently overwriting the lockfile.
2. Version Number Jumping to Trigger Ranges
In the March 2026 Axios compromise, attackers published axios@1.14.1. The real Axios was at version 1.7.x — there was no legitimate 1.14.1. But because projects using "axios": "^1.0.0" accept any version under 2.0.0, the malicious 1.14.1 became the resolution target. Since 1.14.1 is numerically higher than 1.7.x, npm's resolution algorithm treated it as the "latest" acceptable version (Socket.dev, 2026).
This version jumping technique exploits a fundamental assumption in semver: that only legitimate maintainers publish versions. When account credentials are stolen or a maintainer is social-engineered, the entire range becomes an attack vector.
3. Transitive Dependency Range Compromise
Most npm projects do not directly depend on the packages that ultimately compromise them. The average project has 79 transitive dependencies (Sonatype 2026). Each one of those transitive dependencies has its own semver ranges. If any package in the dependency graph uses a broad range (^ or *) for a dependency that gets compromised, the malicious version propagates automatically.
This is why 52% of development teams miss vulnerability SLAs (Snyk 2024): they focus on direct dependencies and don't realize their transitive exposure. A malicious package can be 5 levels deep in the dependency tree and still execute code on install via postinstall scripts.
# Your package.json (direct deps — looks safe)
{
"dependencies": {
"express": "^4.18.0", # 4.18.0 → <5.0.0
"lodash": "^4.17.21" # 4.17.21 → <5.0.0
}
}
# express@4.18.3/package.json (transitive)
{
"dependencies": {
"path-to-regexp": "^6.2.1" # 6.2.1 → <7.0.0
# If path-to-regexp@6.99.0 is published with a backdoor,
# express@4.18.3 pulls it automatically on next npm install
}
}
Lockfile Injection: Semver's Hidden Blindspot
Lockfiles (package-lock.json, yarn.lock) exist specifically to freeze the dependency graph and prevent unintended range resolution. But they introduce their own attack surface — one that semver ranges make worse.
Security researcher Liran Tal documented lockfile injection attacks as early as 2019: an attacker with write access to a pull request can modify the lockfile to point a dependency at a malicious version, while the package.json range remains visually unchanged. Reviewers focus on source code changes, not lockfile diffs. And npm install (as opposed to npm ci) trusts and updates the lockfile — it does not validate that the resolved version still satisfies the declared range.
In the Axios incident, GitGuardian found that 111 Dependabot pull requests propagated the malicious lockfile entry, with 60% of them auto-merged without human review (GitGuardian, April 2026). The broadness of the semver range (^1.0.0) meant Dependabot had no reason to flag the version bump as abnormal.
The Security vs. Flexibility Trade-off
It would be tempting to conclude: "pin everything to exact versions." The reality is more nuanced, and the Renovate documentation captures the trade-off well.
Pinning exact versions ensures you never automatically pull in a compromised package. But it also means you never automatically pull in a security patch. If axios@1.6.0 has a vulnerability patched in axios@1.7.4, a project pinned to 1.6.0 stays vulnerable until someone manually bumps the version. Given that 46% of security patches now go uninstalled within 30 days of release (Sonatype 2026), pinning can create a different kind of vulnerability debt.
The practical recommendation is a layered approach:
- Direct production dependencies: use
~(tilde, patch-only) or exact pinning. Avoid^in productiondependencies. - Dev dependencies:
^is acceptable — these don't ship to users and the risk profile is lower. - Commit lockfiles: always. Treat them as a security control, not noise.
- Use
npm ciin CI/CD: it enforces the lockfile and fails if package.json and the lockfile diverge. - Implement a version cooldown: don't auto-install newly published packages for 48-72 hours. Most malicious packages are detected and removed within that window.
What npm audit Does (and Doesn't) Tell You About Semver
Running npm audit against your project will surface known CVEs in your dependency tree — including CVE-2022-25883 if your semver is outdated. But npm audit has a critical limitation with semver ranges: it only checks against the currently resolved versions in your lockfile. It does not simulate what a future npm install without the lockfile might resolve to.
This means a project can pass npm audit today and be compromised tomorrow if a malicious version is published that falls within an existing ^ range — and then npm install is run in a fresh environment (common in CI/CD pipelines that don't persist or validate lockfiles).
# INSECURE: ignores lockfile, resolves to latest matching version
npm install # can pull malicious ^range matches
npm audit # only audits current lockfile state
# SECURE: enforces lockfile, fails on mismatch
npm ci # exact lockfile reproduction
npm audit # now auditing a deterministic dependency tree
# Also: validate your lockfile explicitly
npx lockfile-lint --path package-lock.json \
--type npm \
--allowed-hosts npm \
--validate-https
Auditing and Fixing Semver Risks in Your Project
Here is a practical checklist to assess your current semver exposure:
Step 1 — Check your semver version
# Check what semver version is in your project (direct + transitive)
npm list semver --all | grep semver
# Anything < 7.5.2 on 7.x, < 6.3.1 on 6.x, < 5.7.2 on 5.x is vulnerable (CVE-2022-25883)
# Force a safe version if needed
npm install semver@latest
Step 2 — Audit your range operators
# Find all caret ranges in your package.json
grep -E '"[^"]+": "\^' package.json
# Find wildcard ranges (always dangerous)
grep -E '"[^"]+": "\*"' package.json
# Check for "latest" tags (equivalent to *)
grep -E '"[^"]+": "latest"' package.json
Step 3 — Validate lockfile integrity
# Ensure lockfile is always committed (never in .gitignore)
cat .gitignore | grep lock # should return nothing
# Use npm ci (not npm install) in all automated environments
# Add to CI config:
# - run: npm ci
# not:
# - run: npm install
Step 4 — Enable continuous monitoring
Manual auditing is a snapshot in time. A dependency that was safe yesterday can be compromised today. Tools like CVE OptiBot scan your lockfiles daily and alert you the moment a package in your dependency tree is flagged — including when a new malicious version falls within an existing semver range in your lockfile.
Real-World Semver Exploit Timeline (2022–2026)
| Date | Incident | Semver Vector | Impact |
|---|---|---|---|
| Jun 2023 | CVE-2022-25883 — semver ReDoS | Malformed version string input to new Range() |
643M downloads/week affected, IBM/Red Hat bulletins |
| Sep 2025 | chalk, debug & 16 packages compromised | ^ ranges auto-resolved to backdoored minor versions |
2.6 billion weekly downloads at risk |
| Mar 2026 | Axios 1.14.1 RAT injection | Version jump (1.7.x → 1.14.1) within ^1.0.0 range |
80% of cloud environments, 111 Dependabot PRs, 60% auto-merged |
| Apr 2026 | CanisterWorm / Bitwarden CLI | Compromised CI/CD token → new semver-valid version published | 93 min live before detection, TeamPCP group |
npm's Native Protections: What Actually Helps
npm has introduced several mechanisms to reduce semver range exploitation risk. Understanding which ones work helps prioritize your defenses:
Package provenance (npm 9.5+): Links packages to their source repository and build pipeline via signed attestations. A malicious version published from an unrecognized CI environment will fail provenance verification. This directly addresses version-jumping attacks where attackers use stolen credentials outside normal CI.
# Check provenance for a package
npm audit signatures axios
# Look for: "verified" with repository match
Trusted publisher (npmjs.com): Packages can configure which GitHub Actions workflow is authorized to publish. This blocks credential theft attacks — even if an attacker steals an npm token, they can't publish unless the token was generated from the authorized workflow.
npm audit fix vs npm audit fix --force: The standard npm audit fix only updates packages within their semver ranges. The --force flag overrides this constraint and can install breaking changes. Never use --force in automated pipelines without testing — it's a common footgun that introduces regressions under the guise of security fixes.
Semver and Transitive CVE Monitoring: The 2026 Reality
In 2026, manual semver hygiene is necessary but not sufficient. The median time to exploitation of a newly published CVE is under 5 days (Security Boulevard 2026). The NVD median time to publishing CVSS scores is 41 days (Sonatype 2026). You have a 36-day gap where a CVE exists, is being actively exploited, and your tools won't tell you about it unless they use alternative data sources.
This gap is especially dangerous for transitive dependencies: you may not even know a vulnerable package is in your dependency graph. Running npm audit in CI is table stakes — but it's not sufficient when the threat moves faster than vulnerability databases.
A complete semver defense strategy in 2026 requires:
- Tighter range operators for production dependencies (
~or pinned) - Lockfile-enforced CI/CD pipelines (
npm ci) - Lockfile integrity validation (lockfile-lint)
- npm provenance checks for critical packages
- Continuous CVE monitoring across the full dependency tree — including transitives
Frequently Asked Questions
What is the difference between ^ and ~ in npm semver?
The caret operator (^) allows updates to the leftmost non-zero digit — so ^1.2.3 accepts versions from 1.2.3 up to (but not including) 2.0.0. The tilde operator (~) is narrower: ~1.2.3 accepts only patch updates, from 1.2.3 to <1.3.0. For security, ~ is safer than ^ in production dependencies because it limits the attack surface to patch versions only.
Is CVE-2022-25883 still relevant in 2026?
Yes, for two reasons. First, many projects indirectly depend on old versions of semver through transitive chains, and may not have upgraded. Second, CVE-2022-25883 is a template for a broader attack class: foundational utility packages with high download counts are high-value targets. A new ReDoS or similar vulnerability in semver would have the same cascade effect today. Running npm list semver --all will show you every version in your tree.
Does pinning all dependencies to exact versions solve semver range exploits?
Pinning prevents automatic pull-in of new versions, including malicious ones. But it creates a maintenance burden: you will not automatically receive security patches either, and your lockfile can still be tampered with. Exact pinning is part of a defense strategy, not a complete solution. Combine it with npm ci, lockfile integrity checks, and CVE monitoring to cover the full attack surface.
What is a semver range exploit in supply chain attacks?
A semver range exploit occurs when an attacker publishes a new, malicious version of a package that falls within the version ranges declared in legitimate projects. Because npm install resolves to the latest matching version, projects automatically pull the malicious version on the next install run. The Axios 1.14.1 attack (March 2026) is the clearest recent example: a version jump from 1.7.x to 1.14.1 targeted all projects using "axios": "^1.0.0".
How does npm ci protect against semver range attacks?
npm ci installs dependencies exactly as specified in package-lock.json. It does not resolve semver ranges against the live registry — it uses the already-resolved versions in the lockfile. If the lockfile and package.json diverge, npm ci fails rather than silently updating. This makes it immune to range-exploitation attacks at install time, as long as your lockfile itself has not been tampered with.
How can I check if my project is affected by CVE-2022-25883?
Run npm list semver --all | grep semver to see all versions of the semver package in your dependency tree. Vulnerable versions are any 7.x before 7.5.2, any 6.x before 6.3.1, and any version before 5.7.2. To remediate, run npm install semver@latest for direct dependencies, and file issues with maintainers of transitive dependencies still using old semver versions.
Monitor your full dependency tree — including semver ranges
CVE OptiBot scans your lockfiles daily, tracks every transitive dependency, and alerts you the moment a package in your tree is flagged — before attackers exploit the 36-day NVD gap. No code access required.
Start free monitoringLa chaîne ^1.0.0 se trouve dans pratiquement chaque package.json de la planète. La plupart des développeurs la tapent sans y penser. Mais en 2026, avec 454 648 packages npm malveillants publiés en un an — soit une hausse de 75% d'une année sur l'autre — ce petit symbole représente l'une des décisions de sécurité les plus importantes que vous prenez en ajoutant une dépendance. Ce guide explique précisément ce que font les ranges semver, comment les attaquants les exploitent, les vraies CVE qu'ils ont rendues possibles, et les défenses qui fonctionnent en 2026.
Ce que promet réellement la gestion sémantique des versions (et ce qu'elle ne promet pas)
Le versionnage sémantique (semver) est un contrat : MAJEUR.MINEUR.CORRECTIF. La promesse est que les versions MAJEURES introduisent des changements cassants, les versions MINEURES ajoutent des fonctionnalités de façon rétrocompatible, et les CORRECTIFS corrigent des bugs. npm enveloppe ce contrat dans des opérateurs de range qui définissent quelles versions futures sont acceptables.
Voici ce que signifie concrètement chaque opérateur pour la sécurité :
# Caret (^) — autorise les mises à jour MINEURES et CORRECTIFS
"axios": "^1.6.0" → accepte 1.6.0 jusqu'à <2.0.0
# Tilde (~) — autorise uniquement les CORRECTIFS
"axios": "~1.6.0" → accepte 1.6.0 jusqu'à <1.7.0
# Exact — épinglé, aucune mise à jour automatique
"axios": "1.6.0" → accepte uniquement 1.6.0
# Wildcard (*) — accepte tout (à ne jamais utiliser)
"axios": "*" → accepte n'importe quelle version
L'implication sécuritaire du ^ : si un attaquant publie axios@1.99.0 avec une backdoor, tout projet utilisant "axios": "^1.6.0" qui exécute npm install (pas npm ci) le récupèrera automatiquement. Sans revue. Sans avertissement. C'est l'exploit de range semver.
CVE-2022-25883 : la vulnérabilité à l'intérieur du vérificateur de versions
Avant de couvrir la manière dont les ranges sont transformés en armes, il existe une CVE critique affectant le package semver lui-même — la bibliothèque qui évalue les ranges de versions dans tout l'écosystème npm.
CVE-2022-25883 (CVSS 5.3) est une vulnérabilité de déni de service par expression régulière (ReDoS) dans le package npm semver avant les versions 7.5.2, 6.3.1 et 5.7.2. La fonction new Range() utilisait des patterns regex sujets au backtracking pour analyser les chaînes de version. Avec une entrée forgée, le temps de traitement croissait exponentiellement — permettant à un attaquant distant de déclencher un déni de service en passant des données utilisateur non fiables comme range de version.
Ce qui a rendu CVE-2022-25883 grave en pratique n'était pas son score CVSS, mais sa cascade : le package semver reçoit 643 millions de téléchargements par semaine (Snyk, 2026). Chaque gestionnaire de packages, outil de build, linter et plateforme CI qui touchait à la résolution de versions npm l'utilisait. IBM, Red Hat, Ubuntu et Rocky Linux ont tous publié des bulletins de sécurité. La leçon réelle : une seule vulnérabilité dans un utilitaire fondamental se propage à des milliers de packages sans qu'aucun d'eux ne déclare de dépendance directe sur le code vulnérable.
Comment les attaquants exploitent les ranges semver en 2026
Comprendre CVE-2022-25883, c'est comprendre une vulnérabilité dans semver. Mais la surface d'attaque plus large concerne la façon dont les ranges semver sont utilisés comme vecteur. Il existe trois patterns principaux :
1. Publication d'une version malveillante dans les ranges existants
L'attaque la plus directe : publier une nouvelle version d'un package légitime qui tombe dans les ranges ^ ou ~ de milliers de projets. Quand les développeurs exécutent npm install, ils tirent automatiquement la version malveillante. C'est exactement ce qui s'est passé lors de l'attaque supply chain npm de septembre 2025, qui a compromis chalk, debug et 16 autres packages cumulant 2,6 milliards de téléchargements hebdomadaires (ArmorCode, 2025).
2. Saut de numéro de version pour déclencher les ranges
Dans la compromission d'Axios en mars 2026, les attaquants ont publié axios@1.14.1. Le vrai Axios était à la version 1.7.x — il n'existait pas de 1.14.1 légitime. Mais parce que les projets utilisant "axios": "^1.0.0" acceptent toute version sous 2.0.0, le 1.14.1 malveillant est devenu la cible de résolution. Étant numériquement supérieur à 1.7.x, l'algorithme de résolution de npm l'a traité comme la dernière version acceptable.
3. Compromission de dépendances transitives par range
La plupart des projets npm ne dépendent pas directement des packages qui les compromettent finalement. Le projet moyen a 79 dépendances transitives (Sonatype 2026). Si l'une de ces dépendances transitives utilise un range large (^ ou *) pour une dépendance compromise, la version malveillante se propage automatiquement. C'est pourquoi 52% des équipes de développement manquent les SLA de vulnérabilités (Snyk 2024).
Injection de lockfile : l'angle mort caché de semver
Les lockfiles (package-lock.json, yarn.lock) existent précisément pour figer le graphe de dépendances. Mais ils introduisent leur propre surface d'attaque. Le chercheur en sécurité Liran Tal a documenté les attaques par injection de lockfile : un attaquant ayant accès en écriture à une pull request peut modifier le lockfile pour pointer une dépendance vers une version malveillante, tandis que le range dans package.json reste visuellement inchangé.
Dans l'incident Axios, GitGuardian a constaté que 111 pull requests Dependabot ont propagé l'entrée de lockfile malveillante, 60% d'entre elles ayant été auto-mergées sans revue humaine. La largeur du range semver (^1.0.0) signifiait que Dependabot n'avait aucune raison de signaler la mise à jour de version comme anormale.
Le compromis sécurité vs flexibilité
L'approche pratique recommandée est une défense en couches :
- Dépendances de production directes : utilisez
~(tilde, patches uniquement) ou épinglage exact. Évitez^dans lesdependenciesde production. - Dépendances de développement :
^est acceptable — elles ne sont pas livrées aux utilisateurs. - Committez toujours les lockfiles : traitez-les comme un contrôle de sécurité.
- Utilisez
npm cien CI/CD : il applique le lockfile et échoue si package.json et le lockfile divergent. - Implémentez un cooldown de version : n'installez pas automatiquement les packages nouvellement publiés pendant 48-72h.
Ce que npm audit détecte (et ne détecte pas) sur semver
npm audit ne vérifie que les versions actuellement résolues dans votre lockfile. Il ne simule pas ce qu'un prochain npm install sans lockfile pourrait résoudre. Un projet peut passer npm audit aujourd'hui et être compromis demain si une version malveillante est publiée dans un range ^ existant — et que npm install est ensuite exécuté dans un environnement CI sans lockfile persistant.
Chronologie des exploits semver réels (2022-2026)
| Date | Incident | Vecteur semver | Impact |
|---|---|---|---|
| Juin 2023 | CVE-2022-25883 — semver ReDoS | Entrée de chaîne de version forgée dans new Range() |
643M téléchargements/sem. touchés, bulletins IBM/Red Hat |
| Sep 2025 | chalk, debug & 16 packages compromis | Ranges ^ auto-résolus vers versions mineures backdoorées |
2,6 milliards de téléchargements hebdo. à risque |
| Mar 2026 | Axios 1.14.1 injection RAT | Saut de version (1.7.x → 1.14.1) dans le range ^1.0.0 |
80% des environnements cloud, 111 PRs Dependabot, 60% auto-mergées |
| Avr 2026 | CanisterWorm / Bitwarden CLI | Token CI/CD compromis → nouvelle version semver-valide publiée | 93 min en ligne avant détection, groupe TeamPCP |
Questions fréquentes
Quelle est la différence entre ^ et ~ dans npm semver ?
L'opérateur caret (^) autorise les mises à jour jusqu'au premier chiffre non nul le plus à gauche — donc ^1.2.3 accepte les versions de 1.2.3 jusqu'à (mais non compris) 2.0.0. L'opérateur tilde (~) est plus restrictif : ~1.2.3 accepte uniquement les patches, de 1.2.3 à <1.3.0. Pour la sécurité, ~ est plus sûr que ^ pour les dépendances de production car il limite la surface d'attaque aux versions de patch.
CVE-2022-25883 est-elle encore pertinente en 2026 ?
Oui, pour deux raisons. Premièrement, de nombreux projets dépendent indirectement d'anciennes versions de semver via des chaînes transitives et n'ont peut-être pas mis à jour. Deuxièmement, CVE-2022-25883 est un modèle pour une classe d'attaque plus large : les packages utilitaires fondamentaux à fort taux de téléchargement sont des cibles de haute valeur. Exécutez npm list semver --all pour voir toutes les versions dans votre arbre.
Comment npm ci protège-t-il contre les attaques de range semver ?
npm ci installe les dépendances exactement telles que spécifiées dans package-lock.json. Il ne résout pas les ranges semver contre le registre en ligne — il utilise les versions déjà résolues dans le lockfile. Si le lockfile et package.json divergent, npm ci échoue plutôt que de mettre à jour silencieusement. Cela le rend immunisé contre les attaques d'exploitation de range au moment de l'installation.
Comment vérifier si mon projet est affecté par CVE-2022-25883 ?
Exécutez npm list semver --all | grep semver pour voir toutes les versions du package semver dans votre arbre de dépendances. Les versions vulnérables sont les 7.x avant 7.5.2, les 6.x avant 6.3.1, et toute version avant 5.7.2. Pour corriger, exécutez npm install semver@latest pour les dépendances directes, et ouvrez des issues chez les mainteneurs des dépendances transitives utilisant encore de vieilles versions.
Surveillez tout votre arbre de dépendances — y compris les ranges semver
CVE OptiBot scanne vos lockfiles quotidiennement, suit chaque dépendance transitive et vous alerte dès qu'un package de votre arbre est signalé — avant que les attaquants n'exploitent le délai de 36 jours entre la découverte d'une CVE et sa publication sur le NVD.
Démarrer le monitoring gratuit