Safeguard
Security Guides•

What do caret and tilde mean in a package version range?

The little `^` and `~` signs before a version decide which updates your app accepts. Learn how they work and how they change your security exposure.

Safeguard Security Team
9 min read

A caret (^) before a version number allows updates that should not break your code, usually new minor and patch releases. A tilde (~) allows a narrower set, usually patch releases only. These signs tell your package manager which versions are acceptable, which affects what gets installed and how fast fixes arrive.

TL;DR

  • A version range is a rule for which versions your app will accept.
  • Caret is the wider rule. Tilde is the narrower rule. An exact version is the narrowest.
  • Wide ranges pick up fixes automatically, but can also pull in surprises.
  • The lockfile records what was actually installed, which is what scanners check.
  • Know your ranges so you know how your security fixes will arrive.

What is a version range?

Definition: A version range is a rule written next to a package name that says which versions of that package your project will accept. Your package manager picks a specific version that fits the rule.

When you add a package to a project, you do not always say "give me exactly version 4.17.21." Often you say "give me something close to 4.17.21." The first approach is predictable but you have to update by hand. The second approach lets small updates arrive without any work, but it trades some control for convenience.

Think of ordering coffee. "A large latte with oat milk" is exact. "Any large coffee" is a range. With the second order, you get a good result most days, but you do not control the details.

If you want a refresher on version numbers themselves, our guide on whether to upgrade to the latest version explains the three part scheme: major, minor, and patch.

What do the common signs mean?

The npm documentation for the manifest file lists the forms a version range can take. It says dependencies are specified as an object that maps a package name to a version range, and the range is a string of one or more descriptors. Here are the ones you will see most.

What you seeIn plain wordsHow wide it is
1.2.3Exactly this versionNarrowest
~1.2.3Roughly this version, patch updates onlyNarrow
^1.2.3Compatible with this version, minor and patch updatesWider
1.2.xAny patch of 1.2Narrow
1.xAny minor of 1Wider
*Any version at allWidest
latestWhatever is tagged latestWidest, and moves

The detailed rules come from the node-semver documentation, which npm uses. Here is what it says, translated into simple terms.

  • A tilde range allows patch level changes if a minor version is given. So the tilde form of 1.2.3 accepts anything from 1.2.3 up to, but not including, 1.3.0.
  • A caret range allows changes that do not modify the left most non zero number. For versions 1.0.0 and above, that means minor and patch updates. So the caret form of 1.2.3 accepts anything from 1.2.3 up to, but not including, 2.0.0.

Here are the rules written as ranges, for readers who like exact notation:

~1.2.3  means  >=1.2.3 and below 1.3.0
^1.2.3  means  >=1.2.3 and below 2.0.0
~1      means  >=1.0.0 and below 2.0.0

What changes for versions that start with zero?

Careful here. The node-semver documentation says that for versions numbered 0.x, the caret allows patch updates for 0.1.0 and above, and no updates at all for 0.0.x. In its words, many authors treat a 0.x version as if the x were the major breaking change indicator.

So a caret on version 0.2.5 accepts 0.2.6 but not 0.3.0. And a caret on 0.0.4 accepts only 0.0.4. The same sign behaves differently depending on the number, which surprises many people.

The SemVer specification itself says that version zero is for initial development and that anything may change at any time. For these packages, even a patch can behave unexpectedly, so read release notes.

Why does this matter for security?

Because your range decides how fixes reach you. There are two sides.

The good side. If your range accepts new patch and minor versions, a fix released in a patch will be picked up the next time someone installs without a lockfile in the way, or when you refresh the lockfile. You get security fixes with less effort.

The risky side. A wide range means you may get a new version you did not test. A broken or even a harmful release could be pulled in automatically. And a range of "any version" or "latest" gives you no control at all.

The lockfile is what ties this together. The npm documentation describes the lockfile as recording the exact tree that was generated, so that later installs are identical, whatever updates happened in between. It also says the audit command normally requires a lockfile, and that bypassing it can give different results each run because the tree is rebuilt each time.

So there are two layers:

  1. The range in your manifest says what is allowed.
  2. The lockfile says what you actually have.

Scanners check what you actually have. That means a scan result can differ from what you expect if you only read the manifest. For a deeper look at the lockfile side, see best practices for npm lockfile security.

Which range should I choose?

There is no single right answer. Use this as a guide.

SituationReasonable choiceWhy
Normal library in a team app with testsCaret, with a committed lockfileGets fixes, stays predictable
Very sensitive package, such as one that handles loginExact version, updated deliberatelyMaximum control
Version zero packageExact version, or tilde with careSmall changes can break things
Anything with star or latestAvoidYou do not control what arrives
Packages taken straight from a web address or repositoryPin to a specific commitThe npm docs support a commit reference for exactly this

Whatever you pick, keep the lockfile in version control and have an update routine, so fixes do not depend on luck.

A worked example with invented numbers

The package and numbers below are invented to show how ranges play out.

Your manifest says that package "pdfmaker" has the range caret 3.1.0. Your lockfile, last refreshed in March, installed 3.1.4. In June, the authors publish 3.1.5 with a fix for a high severity flaw, and also 3.2.0 with new features.

  • The caret range accepts 3.1.5 and 3.2.0, but not 4.0.0.
  • Your lockfile still says 3.1.4, so your app still runs 3.1.4 and a scan flags the flaw.
  • You refresh the lockfile. The package manager picks the newest version that fits, which is 3.2.0, not just the fixed 3.1.5. The flaw is gone, but you also got new features you did not test.
  • If your range had been the tilde form of 3.1.0, you would have received 3.1.5, the smallest change that includes the fix.

Same fix, different amount of change. This is why people choose ranges on purpose. A narrower range means fewer surprises, and a wider one means fewer manual updates.

How do I check what I really have?

  1. Open the manifest and read the ranges for your most important packages.
  2. Open the lockfile and find the exact version installed for each.
  3. Compare that to the fixed versions in your scan report.
  4. If they differ, refresh the lockfile on a branch, run tests, and merge.
  5. Write down any exact pins you chose on purpose and why.

Try it yourself

The demo below shows an SCA dependency inventory in a sample workspace. Click through and look at how each component is listed with the exact version in use, which is the number that matters for security even when your manifest only shows a range.

Click through it at your own pace, or open it full screen.

Quick check

What is the difference between caret and tilde?

Tilde is narrower: it usually allows only patch updates. Caret is wider: for versions 1.0.0 and above it allows minor and patch updates, but not a new major version.

Why does a caret behave differently on version 0.x?

For 0.x versions, the caret allows only patch updates, and for 0.0.x it allows none. Many authors treat the 0.x number as the breaking change indicator.

Does my range or my lockfile decide what a scanner sees?

The lockfile. It records the exact versions installed, so scanners use it to see what you actually run. The range only says what is allowed.

How can Safeguard help with this?

Safeguard's SBOM and dependency scanning records the exact versions your project resolves to, not only the ranges you wrote, and checks them against known vulnerabilities. Policies and gates can then enforce rules on pull requests that change those versions. With autonomous remediation turned on, which is off by default with every category on Manual, fix pull requests are proposed for a person to review.

Run a free scan at gold.safeguard.sh/scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.