Pinning dependencies is good practice. Committing your lockfile is good practice. Using exact version specifiers rather than version ranges in production manifests is good practice. None of these things constitute a security strategy.
The confusion between pinning-as-reproducibility and pinning-as-security comes up repeatedly in security posture assessments. A team can answer "yes, all our dependencies are pinned" and simultaneously be running a dozen packages with known CVEs, including some with available fixes. Pinning froze the versions. Nobody checked whether those frozen versions were safe when they were pinned, and nobody checked whether CVEs were published against them in the months since.
What Pinning Actually Protects You From
To be precise about what pinning does: it prevents your CI/CD pipeline from resolving a different version of a package than the one you tested with. It prevents unexpected breaking changes from upstream version bumps. It gives you deterministic builds. These are legitimate and important properties.
Pinning does not protect you from:
- Vulnerabilities that were present in the version you pinned at the time you pinned it
- CVEs published after you pinned against the exact version you're running
- Vulnerabilities in transitive dependencies at any version depth
- Malicious packages that were the correct, expected version when pinned (supply chain compromises of the package source itself)
The last category is admittedly difficult to defend against with any dependency management approach. The first three are where most security work actually concentrates, and pinning provides zero protection there.
The Frozen Vulnerability Problem
Tight version pinning can actually make the security problem worse in one specific way: it prevents the kind of ambient version drift that sometimes, incidentally, picks up security fixes. A project that uses semver range specifiers (^2.1.0 instead of 2.1.3 exactly) will sometimes pull in a patched version during an unrelated dependency resolution. This isn't a security strategy either, and it creates its own problems with unexpected behavior, but it's worth acknowledging that pure pinning has a side effect of freezing vulnerability exposure as well as version behavior.
A team using Python in a Django application, pinned to specific versions across the board, found this out when a CVE was published against the version of cryptography they'd pinned 14 months earlier. Their CI was deterministic, their builds were reproducible, and they were running known-vulnerable TLS code in production for over a year. The pinning gave them false confidence that the dependency graph was stable and therefore safe. It was stable. It was not safe.
The Semver Assumption Underneath "Upgrade to Fix"
The standard security advice is: when a CVE affects a package version you're running, upgrade to the patched version. This works most of the time and is the right default. But it carries an implicit assumption that the patched version is semver-compatible with your existing code and that upgrading won't break anything.
Major version bumps break things by definition. Some minor version bumps break things despite the semver promise. Security patches are sometimes backported to multiple supported branches (so you can get the fix without a major bump), but not always. When the fix is only available in a new major version of a library with API changes, the upgrade path requires code changes, not just a manifest edit. That's not a two-minute fix.
This is where policy-based remediation becomes more useful than simple "upgrade to latest" advice. A good governance strategy distinguishes between patch-level fixes (low breaking risk, can often auto-merge), minor-level fixes (moderate risk, need CI validation), and major-level fixes (requires manual review, possibly significant engineering work). Treating all CVE fixes as equivalent produces both unnecessary friction (over-cautious on patches) and under-caution (under-estimating major version migration effort).
What a Real Dependency Security Strategy Requires
A dependency security strategy has three operational requirements that pinning doesn't address:
Continuous CVE monitoring. New vulnerabilities are published against packages you're already running. The only way to know about them is to monitor your dependency graph against CVE feeds (NVD, OSV, GitHub Advisory Database) on an ongoing basis. This isn't a one-time audit. It's a continuous process. A package you scanned as clean in January may have three CVEs against it by March.
Prioritized remediation workflow. Not all CVEs require the same response speed. A CVSS 9.8 with active exploit code in the wild against a package your authentication service depends on is a different urgency than a CVSS 4.2 in a dev-only testing utility. An effective strategy has explicit SLA tiers for different severity classes, with defined expectations for how quickly each category gets fixed.
Evidence of compliance. For security audits, vendor assessments, or internal governance review, "we checked once and nothing was flagged" is insufficient. You need a continuous record of: which CVEs were detected, when, what their severity was, and how long it took to remediate each one. That's the audit trail that turns a reactive security process into a defensible compliance posture.
The Freshness Trade-off
There's a real tension between keeping dependencies current and maintaining stability. Teams that update dependencies aggressively reduce the time they're running outdated versions (reducing exposure window) but introduce more change, more potential breaking changes, and more CI flakiness. Teams that update conservatively have more stability but accumulate technical debt and run old versions longer.
Pinning is one point on this spectrum, but the security trade-off doesn't resolve cleanly in favor of either extreme. The practice we've seen work best: pin for reproducibility (always commit lockfiles), but run continuous CVE monitoring against the pinned versions, and use severity-tiered policies to determine when a fix is required versus optional. That combines the stability benefits of pinning with active vulnerability management, rather than treating pinning as a substitute for it.
Starting From Where You Are
Most teams we work with are starting from a state of partial pinning, inconsistent monitoring, and no formal remediation SLA. That's not a failure state. It's the default. The goal isn't to immediately implement perfect continuous monitoring across a hundred repositories. It's to add the monitoring layer on top of existing pinning practices and establish the triage and remediation workflow incrementally.
Pinning is the foundation. It's just not the building. A lockfile without CVE monitoring is a building foundation with no structure on top of it: you've done necessary but not sufficient work. The security posture comes from what you add above the foundation, which is the ongoing evaluation of what's inside those pinned versions and what to do when something in them turns out to be vulnerable.