Safeguard
Security Guides•

How do you build the exposure window for a package incident?

To scope a bad package release, find when it was published, when it was removed, and which of your builds installed it in between. Steps for npm and PyPI.

Safeguard Security Team
7 min read

To build the exposure window for a package incident, find when the bad version was published, when it was removed or replaced, and which of your builds or machines installed it in between. Only installs inside that window can have pulled the bad code. Everything else is a name match, not an exposure.

Why the window matters

A news item says a package was compromised. Your lockfile shows the package. That does not yet say you are affected, because many incidents involve one or a few specific versions that were live for a limited time.

The window turns a vague worry into a bounded question: did anything we run fetch that version, between these two timestamps? If the answer is no for every build, laptop and image, you can say so with evidence.

Step 1: get the publish time of the bad version

For npm, the npm view command prints registry data about a package. The npm documentation shows how to read a single field and how to list the version history.

npm view some-package versions
npm view some-package time --json

The time field holds timestamps keyed by version. Run it first on a package you know, so you recognize the shape of the output before you rely on it.

For PyPI, the JSON API documents a route per project, and each file entry carries upload time fields.

curl -s https://pypi.org/pypi/some-project/json

Look at the upload_time_iso_8601 value on the files for the version in question. PyPI's docs also note that releases include hash digests, which helps later when you compare files.

Step 2: get the end of the window

The end is not always when the news broke. It is when the bad version stopped being installable, or when your own pin or mirror stopped serving it. Three different clocks apply:

  • The registry removal or replacement time, from the maintainer or registry notice.
  • The time your internal mirror or cache stopped serving it, which may be later.
  • The time your build pipeline last ran a fresh install without a cached copy.

Use the latest of these as your conservative end. See the earlier post on what to do after the registry removes the package.

Step 3: match builds to the window

Now list every place something could have installed the package:

PlaceEvidence to check
CI runsJob logs and the install step timestamp
Container imagesBuild date and the layer that ran the install
Developer laptopsShell history and the lockfile modification time
Deployed artifactsRelease date and the lockfile commit it was built from

A lockfile that predates the window and was installed with a frozen command normally holds the older version. A build that resolved fresh inside the window may not.

Step 4: write the result down

Record the bad version, the window start and end with time zone, the places you checked, and the verdict for each. Use UTC throughout so timestamps compare cleanly. If a build falls inside the window, move to your incident runbook for credential rotation and clean rebuilds.

A worked example

Say a maintainer notice states that version 5.0.2 of a package was published without authorization at 08:12 UTC on a Wednesday and removed at 11:30 UTC the same day. You run two services and a mobile build.

You collect what you need, one place at a time.

  1. Registry facts. You run the version and time commands and confirm that 5.0.2 appears with a publish timestamp near the notice. You save the output to the ticket.
  2. Mirror behavior. Your internal mirror caches packages for 24 hours. So the mirror could have served 5.0.2 until about 11:30 UTC on Thursday, or later if a cache refresh was delayed. Your conservative end is Thursday 11:30 UTC, not Wednesday.
  3. CI history. The web service ran seven builds between those times. Six restored a cached dependency folder keyed to the lockfile hash. One, at 09:05 UTC Wednesday, ran a fresh install because the lockfile changed.
  4. Lockfile check. The fresh install resolved the package through a range. The lockfile in that commit shows 5.0.1 because the lockfile pinned it, so the build installed 5.0.1 even though 5.0.2 was live.
  5. Mobile build. The mobile build has no lockfile for that package and resolves the newest version in range on every run. It ran at 10:40 UTC Wednesday. That one is inside the window and needs a closer look.

The verdict is not "we are affected" or "we are fine." It is a list: web service not exposed, evidence attached, mobile build possibly exposed, investigating. That is a calm, specific answer.

Common mistakes when building a window

MistakeWhy it hurtsBetter habit
Using the news time as the startThe version may have been live earlierUse the registry publish time
Ending at the removal timeMirrors and caches can keep serving itEnd at the last time your own systems could serve it
Mixing time zonesA three hour error can move a build inside or outside the windowConvert everything to UTC first
Checking only the lockfile in mainA branch build may have used another lockfileCheck the commit each build actually used
Ignoring laptopsDevelopers install packages locallyAsk developers to run the version check and report

What to do with builds inside the window

For each build or machine that falls inside the window and actually installed the bad version, treat it as exposed. That usually means: stop using the artifact, rebuild from a clean state with a known good version, rotate any credentials the build environment could read, and review what the build produced and shipped. The exact steps depend on what the bad version was known to do, which the primary source should describe. If the source does not say, assume the broader case until you learn otherwise.

For builds outside the window, note the evidence and close that line. Do not rotate credentials just because the package name matched. Over-reacting wastes time and trains people to ignore the next alert.

Keep the window in the ticket

A short block at the top of the ticket works well: version, publish time, end time, the clock you used, and one line per environment. Update it when you learn more. When a reviewer later asks how you scoped the incident, the answer is already written.

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, since that is the list you search first.

Safeguard's SBOM and dependency scanning keeps that inventory of packages and versions per project, so the lookup does not start from a blank page when news arrives.

Quick check

Is the window the same as the time the news was published?

No. The bad version may have been live well before the news, and may remain in your mirrors or caches after the registry removed it.

What if I cannot find the exact removal time?

Use the later of the registry notice and the last time your own systems could serve the version, and say the end is conservative.

Do I need the window if the package is only a transitive dependency?

Yes. A transitive dependency installs the same way as a direct one. Your lockfile or build logs show which version was resolved and when.

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.