Safeguard
Developer Tools•

How do you diff a suspicious package release against the last one?

Download both versions without installing them, unpack them in a throwaway directory and compare files. Steps for npm and PyPI, plus what to look for.

Safeguard Security Team
7 min read

To diff a suspicious release, download the old and new versions without installing them, unpack each in a throwaway directory, and compare the files. Look for new install scripts, new network calls and files that your last review never saw. Do this in a disposable environment.

Set up a safe place first

You are about to handle code someone says is bad. Use a disposable container or virtual machine with no credentials, no mounted home directory and no access to internal networks. Do not unpack it in a directory you also use for real work.

Fetching is one thing and installing is another. Installing can trigger scripts and builds. The steps below only fetch and unpack.

npm: fetch two tarballs

The npm documentation says npm pack takes a package spec such as a name and version, fetches it to the cache, and copies a tarball named name-version.tgz into the current directory.

mkdir old new
cd old && npm pack some-package@1.2.3 && tar -xzf some-package-1.2.3.tgz && cd ..
cd new && npm pack some-package@1.2.4 && tar -xzf some-package-1.2.4.tgz && cd ..
diff -ru old/package new/package

To find the previous version, npm view some-package versions lists the history, as the npm docs show.

PyPI: download without installing

The pip documentation includes a pip download command. Prefer wheels, since a source distribution may need a build step to produce metadata, and keep dependencies out of the way.

pip download some-project==1.2.3 --no-deps --only-binary=:all: -d old
pip download some-project==1.2.4 --no-deps --only-binary=:all: -d new

A wheel is a zip archive, so you can unzip it and compare the folders the same way. If only a source distribution exists, unpack it and read it, but do not build it on a machine you care about.

What to look for in the diff

ChangeWhy it matters
New or changed install scriptsThey run on the installing machine
New network calls or hard coded hostsPossible data exfiltration or a downloader
Obfuscated or minified blobs in a package that was readable beforeHides intent
New files with no matching source repository commitSuggests the release was not built from the public code
Changed dependency listA new dependency can carry the payload

A large diff is not proof of anything. A normal release changes many files. Focus on the kind of change, not the count.

Compare against the repository

Check whether the new release matches a tagged commit in the public source. A release with files that exist nowhere in the repository deserves a closer look. Our post on whether a published package can differ from its source repository covers why that happens and how to read it.

Write down what you found

If the diff looks clean, record that you compared the two versions and what you checked, but do not call the package safe. A diff shows differences, not intent. If it looks wrong, stop installing the version, tell your team, and follow your incident runbook.

A worked example

Imagine a story says version 2.6.0 of a small utility package was altered. Your service uses it. You set up a disposable container with no secrets and run the npm steps from above.

The diff output shows three things.

  1. The package.json file has a new scripts entry that runs a file named setup.js after install.
  2. setup.js is new, about 4 KB, and mostly one long line of encoded text.
  3. The list of dependencies gained one package you have never heard of.

None of these proves harm on its own. Put together, they form the pattern you were warned about: a new install hook, an unreadable blob, and a new dependency. That is enough to stop using 2.6.0, tell your team, and pin the previous version while you wait for the maintainer or registry to say more.

Now a calmer outcome. The diff shows a changed version number, an updated changelog, three edited source files with readable changes, and a new test. You compare against the tagged commit in the public repository and the source files match. You record "compared 2.5.9 and 2.6.0, no install scripts, no new dependencies, source matches tag abc123" in the ticket. You have not proven the package is safe forever, but you have a documented, specific basis for carrying on.

Reading a diff without being an expert

You do not need to understand every line. Three questions get you most of the way.

  • Does it run something at install time? Look at the scripts section of the manifest. For a Python wheel, look for anything unusual in the metadata and for files that run on import.
  • Does it talk to the network? Search the new or changed files for hosts, URLs and words like fetch, request, socket, or exec. Add any you find to your notes.
  • Is it readable? A package that was plain before and is a block of encoded text now has changed in a way worth asking about.

If you are unsure, ask someone with more experience and show them the diff. A second pair of eyes is cheap compared with a wrong call.

Tools that save time

NeedApproach
Compare two foldersdiff -ru old new, or a visual diff tool
See only file names that changeddiff -rq old new
Compare to repository sourceClone the repository at the tagged commit, then diff the folders
Search for hosts or URLsgrep -rE "https?://" new
Compare file hashesshasum -a 256 on both trees and compare

Run these inside the disposable environment. Copy out only your notes, not the package files.

What not to conclude

A clean diff does not make a release safe, because a malicious change might be subtle or live in a dependency. A noisy diff does not make a release malicious, because big releases are normal. Report what you saw and let the owner or the wider community weigh in. If you believe you have found something, report it to the registry's security contact and the maintainer through their documented channels.

Document what you did

Record the two versions, the commands, the date and time in UTC, who ran them, and a one paragraph summary. If you decide to block the version, add the reason. This is the evidence trail that lets you defend the decision later.

Try it yourself

Click through it at your own pace, or open it full screen. Look at how every component and version gets an entry, which tells you which release you would be comparing.

Quick check

Does npm pack run the package's code?

The npm docs describe it as fetching a tarball and copying it to your directory. Still, handle the contents in a disposable environment and never run them.

Why use the no-deps flag with pip download?

It keeps the download to the one package you want to inspect instead of fetching its whole dependency tree.

Do I need to be a malware analyst to do this?

No. Checking for install scripts, new dependencies, network calls and unreadable blobs catches many obvious cases. For anything unclear, ask a colleague with more experience.

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.