Outdated dependencies are one of the most common sources of security vulnerabilities, so updating them is essential. But updating carelessly is how you break a working app on a Friday afternoon. The good news is that safe updating is a repeatable process, not a gamble. This guide teaches beginners how to update dependencies deliberately: one change at a time, verified by tests, with an easy way to undo. The examples use npm, but the same discipline applies to pip, Maven, Go, and every other package manager.
What you will accomplish
You will update your dependencies to close known vulnerabilities while keeping your app working, and you will do it in a way you can confidently repeat every month.
Prerequisites
- A project with a committed lockfile, such as
package-lock.json. - A working test suite, or at least a way to run and manually check your app.
- Git, so you can create a branch and roll back.
- The Safeguard CLI, installed in Step 2.
Step 1: Start from a clean, committed state
Never update on top of uncommitted work. Create a dedicated branch so you can throw it away if things go wrong:
git checkout -b chore/update-deps
git status
git status should report a clean working tree. This is your safety net.
Step 2: See what is outdated and what is vulnerable
Two questions matter: what is out of date, and what is actually dangerous. Answer both:
npm outdated
sg scan
npm outdated lists newer versions available. The scan tells you which outdated packages carry known vulnerabilities. Prioritize the ones that appear on both lists, especially at high or critical severity.
Step 3: Understand version numbers before you change them
Package versions follow semantic versioning, written as major.minor.patch, for example 4.17.21. The rule of thumb:
- Patch updates fix bugs and are almost always safe.
- Minor updates add features in a backward-compatible way and are usually safe.
- Major updates can contain breaking changes and need care and testing.
Prefer the smallest bump that fixes your vulnerability. Jumping across a major version is sometimes necessary, but treat it as a deliberate task, not a routine update.
Step 4: Update one thing at a time
Resist the urge to update everything at once, because if something breaks you will not know which change caused it. Update a single package to the version that fixes your finding:
npm install lodash@4.17.21
For safe, in-range patch and minor updates across the board, npm update is fine, but still do it as its own commit so it is easy to isolate.
Step 5: Run your tests immediately
After each update, verify nothing broke:
npm test
If your tests pass, commit right away so this good state is captured:
git add -A && git commit -m "chore: update lodash to 4.17.21"
If they fail, you have a small, isolated change to investigate, not a tangle of twenty simultaneous updates.
Step 6: Handle transitive dependencies
Often the vulnerable package is not one you installed directly; it was pulled in by something else. You cannot upgrade it with a normal install, but you can force a resolved version using the overrides field in package.json, then reinstall:
npm install
sg scan --fail-on high
The scan at the end confirms the forced version actually cleared the finding.
How to know it worked
npm testpasses after each update.sg scan --fail-on highexits cleanly, meaning no high or critical findings remain.git logshows a series of small, single-purpose commits you could revert individually.- Your app still runs and behaves as before.
If your vulnerability count dropped and your tests are green, the update was safe and successful.
Next steps
- Make updating a routine, such as a monthly window, so you handle a few small changes often instead of a frightening pile once a year.
- Automate detection so you learn about a vulnerable dependency the day it is disclosed.
- Learn how versions and ranges work in more depth via the concepts library.
- Let tooling propose the fix when a transitive override gets complicated.
To see how updates get prioritized by real risk instead of raw counts, read about software composition analysis. The Safeguard CLI gives you identical results locally and in CI, and Griffin AI can compute the correct upgrade or override, run your tests, and open a pull request so a transitive fix becomes a quick review. Keep leveling up with the Safeguard Academy.
Frequently Asked Questions
Should I just run npm update and update everything at once?
For in-range patch and minor updates, a batched npm update is reasonable, but do it as its own isolated commit. For anything crossing a major version, update one package at a time and test after each, because a single breaking change hidden in a large batch is painful to track down.
What is the difference between a patch, minor, and major update? Under semantic versioning, patch releases fix bugs, minor releases add backward-compatible features, and major releases may break existing behavior. Patch and minor updates are usually safe to take quickly, while major updates deserve deliberate testing before you rely on them.
How do I update a dependency I did not install directly?
Transitive dependencies are pulled in by your direct ones. You can force a patched version using the overrides field in package.json, or resolutions in Yarn, then reinstall so the lockfile updates. This works even before the direct dependency releases its own fix.
What if an update breaks my app? That is exactly why you work on a branch and commit after each successful change. Roll back the single commit that caused the failure, then either pin to the last working version or investigate the breaking change on its own. Small, isolated updates make this a minor inconvenience rather than a crisis.
Want vulnerable dependencies fixed automatically with tested pull requests? Sign up at app.safeguard.sh/register, and learn the full workflow in the Safeguard Academy.