Back to Blog
SecurityJuly 4, 20264 min read

Dependency management: Why Floating Versions Break Your Security

Dependency management is the silent killer of production stability. Learn how to stop floating versions from introducing supply chain vulnerabilities today.

securitynodejsphpsupply-chain-securitydependency-managementdevopsWebBackend

Last month, a junior dev pushed a change that caused our staging environment to crash on startup. We spent about four hours tracing the issue back to a minor patch update in a third-party library that introduced a breaking change. It wasn't a malicious attack, but it highlighted exactly why relying on floating versions is a ticking time bomb for your production systems.

If your package.json or composer.json files contain carets (^) or tildes (~), you’re telling your package manager to fetch the latest "compatible" version during installation. In theory, this keeps you updated. In practice, you’re letting external maintainers dictate the code running in your production environment without your explicit approval.

Why Dependency Management Matters for Security

When you allow floating versions, you lose control over your build's reproducibility. If your CI/CD pipeline runs npm install or composer install without a strictly enforced lockfile, you might be deploying a different dependency tree than the one you tested locally. This is a massive gap in your supply chain security.

An attacker who compromises a popular package can push a malicious "patch" version. Because your configuration allows floating updates, your next automated build could pull that compromised code automatically. We’ve seen this happen with abandoned packages that get taken over—the new maintainer pushes an update that looks minor but includes a backdoor.

The Lockfile Advantage

Lockfiles—package-lock.json in Node.js and composer.lock in PHP—are your first line of defense. They record the exact version and integrity hash of every dependency and sub-dependency.

FeatureFloating VersionPinned/Locked Version
Build ReproducibilityLowHigh
Security RiskHigh (Auto-updates)Low (Controlled)
Maintenance OverheadLow (Automatic)Medium (Manual Audits)
CI/CD SpeedFasterSlower (Hash checks)

To maximize your supply chain security, you must treat the lockfile as a source-of-truth file. Never ignore it, and always commit it to your repository.

Enforcing Version Pinning

You shouldn't trust your team to remember to pin versions manually. Automation is the only way to scale this.

Node.js Strategy

In Node.js, I prefer using npm ci instead of npm install in CI environments. The npm ci command forces a clean install, ignores package.json version ranges, and requires a package-lock.json file to be present. If the lockfile doesn't match your package.json, the build fails immediately. This is exactly what you want when preventing dependency confusion with scoped registries and lockfile security.

PHP Strategy

Composer handles this similarly with composer install --frozen-lockfile. This flag ensures that Composer doesn't attempt to resolve dependencies if the lockfile is out of sync with the composer.json file.

Bash
# In your CI pipeline, always use the frozen/ci command
# For Node.js:
npm ci

# For PHP:
composer install --no-interaction --frozen-lockfile

Software Composition Analysis

Even with locked versions, you can't manually audit every line of code in your node_modules or vendor folders. This is where software composition analysis (SCA) comes in. Tools like npm audit or composer audit check your locked dependency tree against known vulnerability databases.

We integrated these checks directly into our GitHub Actions workflow. If a vulnerability is found with a severity level above "Medium," the build blocks. It’s not perfect—it won't catch zero-day exploits—but it prevents us from deploying packages with publicly disclosed flaws.

A Note on "Patching"

When a vulnerability is found, don't just "update everything." I've seen teams blindly run npm update to clear a security warning, only to break their entire auth flow because a dependency changed its API. Always update one package at a time, check your tests, and verify the diff in your lockfile before merging.

Frequently Asked Questions

Q: Should I ever use floating versions? A: Only in personal projects or rapid prototyping. In production, predictability is more important than convenience. If you need the latest features, update dependencies intentionally as part of a sprint task.

Q: Does pinning versions stop all supply chain attacks? A: No. It stops accidental updates, but it doesn't stop you from installing a compromised version if you decide to update your lockfile. You still need to monitor your dependencies and use tools that perform software composition analysis.

Q: What if a vulnerability exists in a version I'm currently using? A: You have to upgrade. But do it with intent. Update the specific package, run your regression suite, and commit the updated lockfile.

I’m still not entirely convinced that automated dependency update tools like Dependabot are a net positive. While they keep dependencies fresh, they often encourage "merge-without-reading" behavior. I’d much rather have a human engineer review a PR that updates a core security dependency than have a bot force-push it into my main branch. Next time, I’d like to experiment with local proxy registries to mirror and scan every package before it reaches our build servers.

Similar Posts