# The Publisher-Change Verdict: Upgrade, Hold, or Block a Flagged Release

*Take one flagged release, pull its publisher and maintainer history, score five dimensions, and record an Upgrade, Hold, or Block verdict a second engineer would repeat.*

- Canonical URL: https://www.refolk.ai/guides/publisher-change-dependency-release-verdict
- Pillar: Engineering and open source
- Format: Framework
- Published: 2026-10-27
- Last reviewed: 2026-10-27
- Reading time: 22 min
- Keywords: how to tell if an npm package was hijacked, new maintainer added to dependency is it safe, package published without provenance what to do, verify open source maintainer identity before upgrading, dependency cooldown exception approval process, pypi project ownership transfer risk

## Key takeaways

- Valid provenance does not clear a release: 84 malicious TanStack versions shipped in under six minutes with valid SLSA Build Level 3 provenance, because the attacker controlled the source account and the real pipeline signed the result.
- A flagged release gets a score of 0, 1, or 2 on each of five dimensions: publish-path continuity, account identity continuity, handover trail, contribution tenure of the new identity, and change content.
- Block and pin whenever change content scores 0, or when publish path and account identity both score 0. Upgrade only when no dimension scores 0, change content scores 2, and the total is at least 8 of 10.
- In the documented axios and Tensorlake compromises, scanners flagged the malicious release 6 to 11 minutes after publish, while axios stayed live for about 3 hours, so a 1-day cooldown outlasts the install-time window in those cases.
- PyPI keeps no ownership history and no API names the uploader, so you can only detect a publisher change on PyPI by comparing against ownership.roles snapshots you stored yourself.
- Refolk's index holds only 2 US profiles with supply-chain or open-source security engineer titles, against 40 with maintainer or OSPO titles. The verdict usually lands on a generalist engineer, so it needs a written rubric.

This is the decision standard for one specific moment. A package you already depend on ships a release from a different account, or without the provenance its earlier versions carried, or with a changed maintainer list, and someone has to decide today whether to take it. It is for the engineering manager, technical founder, developer-relations lead, or sourcer who ends up holding that call. It gives you five scored dimensions, a rule that turns the scores into Upgrade, Hold, or Block and pin, and a record format a second engineer can check line by line.

## What does the publisher-change verdict decide?

The publisher-change verdict decides whether the person behind a flagged release is the legitimate maintainer. That answer determines whether you upgrade, hold the release behind a cooldown until named checks clear, or block it and pin the last good version. The verdict treats the release as a question about a person's identity, not about package hygiene.

A release is flagged when any of these is true compared with the last version you trusted:

- The publishing account (`_npmUser` on npm) is different.
- The previous version carried a provenance attestation and this one does not.
- The npm `maintainers[]` list or the PyPI `ownership.roles` list has changed.
- The PyPI `ownership.organization` has changed. This is how a project ownership transfer looks from outside.

The three verdicts mean specific things:

- **Upgrade.** Take the release on your normal schedule, including the standard cooldown.
- **Hold.** Keep the cooldown in force and deny any exemption until a named list of checks clears. Every Hold names its checks and the person who clears them.
- **Block and pin.** Pin the last good version in your lockfile and refuse the flagged one until the maintainers publish an explanation you can verify.

### Why provenance no longer settles it

Provenance is a signed attestation that binds a published tarball to the CI workflow that built it. It looked like the obvious shortcut: if the release came through the real pipeline, it came from the real maintainers. Worm-style compromises broke that shortcut. An attacker who controls a maintainer's source account can push to the branch, and the legitimate workflow then builds and signs the result.

**84 - Malicious TanStack versions published with valid SLSA Build Level 3 provenance in under six minutes**

The 84 versions covered 42 packages, and every signature checked out because the attacker pushed through the real pipeline.

#### What a provenance signature actually vouches for

1. **Person** - The human behind the account; provenance says nothing about this layer
2. **Source account and branch** - Whoever can push or merge to the default branch
3. **CI workflow** - The trusted publisher identity named in the attestation
4. **Tarball** - The published file the signature is bound to

*Provenance binds the tarball to the workflow, so whoever controls the source branch inherits the signature.*

So the verdict has to look past the signature to the account that pushed the code, and past the account to the person.

## Which registry fields show a publisher change?

On npm, the registry document records three things for each version: who published it, the maintainer list at publication time, and any provenance attestation. On PyPI you get the current ownership roles and a separate provenance endpoint, but no uploader and no history. Read each field for what it proves and for how it can mislead you.

| Field | Registry | What it proves | How it lies |
| --- | --- | --- | --- |
| `_npmUser` | npm | Account that published the version | Under staged publishing, may name the approver |
| `_npmUser.trustedPublisher` | npm | Publish came through a configured CI identity | A hijacked source account still publishes through real CI |
| `maintainers[]` | npm | Maintainer list and emails at publish time | A changed email can sit on the same account name |
| `dist.attestations.provenance` | npm | An attestation exists | Also present on token publishes with provenance |
| `ownership.roles` | PyPI | Current owners and maintainers | Current state only; no history |
| Integrity API provenance | PyPI | Trusted Publisher kind, repo, workflow, environment | Uploader is never named |

On npm, fetch `https://registry.npmjs.org/<package>` and compare the `versions` entries for the previous and flagged releases. Practitioners have an open complaint that npm has no field equivalent to provenance for trusted or staged publishing. So read `trustedPublisher` and `_npmUser` together, and never rely on `dist.attestations` alone.

On PyPI, the JSON API gives you `ownership.roles` and `ownership.organization`. The Integrity API at `/integrity/{project}/{version}/{filename}/provenance` returns the Trusted Publisher identity behind the PEP 740 attestations. The JSON API does not expose the trusted-publisher configuration, which appears only on the project's Publishing page. One practitioner audit found that no API names the account that uploaded a file.

A missing attestation on PyPI means something only if earlier versions had one. A single-PR audit put attestation of the latest release at 44% of the top 500 projects. I treat that figure as unverified, but it means absence is common and is not a signal by itself.

I have not verified the equivalent fields for crates.io, RubyGems, or Packagist. Check each registry's own documentation before you apply this table to them.

> **Note:** PyPI makes publisher change a stateful check
>
> PyPI shows current ownership only and hides the uploader, so you cannot detect a change you did not record. Save a snapshot of ownership.roles and ownership.organization every time you adopt or upgrade a Python dependency.

## The five dimensions and how to score each

Score each dimension 0, 1, or 2. A 2 means the release is continuous with the trusted history. A 1 means something changed but evidence you can verify explains it. A 0 means something changed and nothing explains it, or the evidence contradicts it. Write one line of evidence beside every score so a reviewer can check it without redoing your work.

| Dimension | 2: continuous | 1: changed, explained | 0: changed, unexplained |
| --- | --- | --- | --- |
| Publish path | Same trusted publisher, tag, PR-merged commits | Path changed; tag and file list match | No tag, or commits bypassed PRs |
| Account identity | Same account and maintainer emails | New account tied to known maintainer or org | New account or email, no link |
| Handover trail | None needed, or posted by an existing maintainer | Trail exists, only from new accounts | None, or flooded by new accounts |
| New identity tenure | No new identity, or broad history and normal escalation | Thin history, no pressure pattern | Pressure patterns or sudden elevation |
| Change content | Matches tagged source; no new scripts | New dependency that source imports | Unimported dependency, lifecycle script, `.pth`, blobs |

### Publish-path continuity

This dimension proves the release took the same route from source to registry as the versions you trusted. That means the same trusted publisher, a tag that exists before the publish, and commits that reached the default branch through pull requests. Legitimate releases leave a gap you can see: the Playwright release was tagged 22 minutes before the npm publish.

**When it lies:** valid provenance gives a false clear when a hijacked source account pushes through the real CI. In the Tensorlake case, malicious commits landed under a maintainer's name and none went through a pull request. A clean `trustedPublisher` with direct commits to the default branch scores 0, not 2.

### Account identity continuity

This dimension proves the same account, with the same contact identity, published the release. In the axios compromise the maintainer email changed at the same time as the publish path. Also watch for several release lines published from a new path in quick succession. Axios 1.14.1 and 0.30.4 went out 39 minutes apart, and LiteLLM 1.82.7 and 1.82.8 about 13 minutes apart.

**When it lies:** under staged publishing, `_npmUser` may name the npm user who approved the release, not the one who built it. On PyPI a change of uploader is invisible. One practitioner reports buying a sole author's expired email domain. In that case the account looks unchanged even though someone else now controls it.

### Handover trail

This dimension proves that someone who was already trusted announced a legitimate change. Documented precedents:

- **Corporate release-infrastructure change:** @playwright/cli 0.1.19 dropped provenance and `_npmUser` moved to "microsoft1es". A GitHub issue in the Playwright repository documents the change.
- **Identity rotation after an incident:** LiteLLM moved to new maintainer accounts and documented it in pinned issue #24518.
- **Temporary token publishing while OIDC is set up:** one project recorded this in a repository issue.

**When it lies:** attackers can manufacture a trail. During the LiteLLM incident, attackers posted 88 bot comments from 73 unique accounts in a 102-second window. A trail only counts toward a 2 if a maintainer account that existed before the change posted it. For foundation transfers and acquisitions I found no publicly established pattern of documenting artifact. In those cases I would look for the announcement on the foundation's or acquirer's existing channels and score 1 until I found it.

### Contribution tenure of the new identity

This dimension proves the new identity has a real history beyond this one package. Record account age, contributions across other projects, and how the person gained permissions. Then check for the patterns in the OpenSSF/OpenJS alert on social-engineering takeovers:

- Friendly yet aggressive and persistent pursuit of the maintainer by unknown members
- Requests for elevated rights from new people
- Sock-puppet endorsements
- Binary blobs in pull requests

**When it lies:** tenure can make an attacker look trustworthy. The xz attacker's work represented 2.6 years of sustained, patient engagement. No numeric tenure threshold such as "six months" is publicly established, so do not invent one. Tenure can lower a verdict but never raise it above what path and content allow.

Building the cross-project footprint takes the longest in the whole procedure, because the evidence is scattered across commits, profiles, and talks. [Refolk](/) lets you ask for the person in plain English and returns their footprint across GitHub, LinkedIn, and the open web. It also shows who currently holds merge rights on the repository, which tells you whose confirmation to seek.

I ran this search: `Engineers with merge rights on axios or TanStack repositories active in the last 12 months` - [see the full result list](https://www.refolk.ai/s/85bswhk9ec).

*Returns named people with their GitHub activity and professional profiles, so you can see who legitimately holds publish and merge power before you trust a new name.*

### Change content

This dimension proves what will actually execute on your machines. Diff the published files against the tagged source and look for:

- New dependencies, and whether source actually imports them
- Lifecycle scripts such as `postinstall`
- `.pth` files in Python packages
- Binary blobs and unexplained changes to the file list

**When it lies:** attackers pre-stage dependencies so they do not look new. A clean decoy, plain-crypto-js 4.2.0, was published about 18 hours before the malicious 4.2.1 that axios pulled in. The test is whether anything imports the dependency, not how old it is.

> **Rule:** Change content is never skipped
>
> Even when the publish path and identity checks pass, review the change content before any Upgrade. Provenance can earn the publish path a 2, but it can never clear a release on its own.

## How do the scores become Upgrade, Hold, or Block?

Block and pin when change content scores 0, or when publish path and account identity both score 0. Upgrade only when no dimension scores 0, change content scores 2, and the total is at least 8 of 10. Everything else is a Hold, and every Hold lists the checks that would move each low score to 2.

The rule in order:

1. **Block and pin** if change content = 0.
2. **Block and pin** if publish path = 0 and account identity = 0.
3. **Upgrade** if no dimension is 0, change content = 2, and the total is 8 or more.
4. **Hold** in every other case, with named checks.

These thresholds are my rubric, not a published standard. Their job is to make two engineers reach the same answer. Tenure is deliberately weak: a high tenure score cannot pull a release out of Block.

#### Path continuity against change content

Horizontal axis runs from Path and identity broken to Path and identity continuous. Vertical axis runs from Content risky to Content clean.

| Quadrant | What it means |
| --- | --- |
| Explained infra change | Hold; find the tag, a matching file list, and a trail posted by an existing maintainer |
| Normal release | Upgrade after the standard cooldown |
| Token hijack | Block and pin; this is the axios pattern |
| Source-account worm | Block and pin despite valid provenance; this is the TanStack pattern |

*Clean content can earn a broken publish path a Hold while checks run, but continuous provenance never excuses risky content.*

The documented incidents land cleanly on this grid:

| Incident | Publish path | Provenance | Content signal |
| --- | --- | --- | --- |
| axios 1.14.1 | OIDC replaced by CLI token; email changed | Lost | Phantom dependency with postinstall |
| LiteLLM 1.82.7 | Stolen token, direct upload | Unverified | `.pth` file (in 1.82.8) |
| TanStack | Real pipeline | Valid SLSA L3 | Worm |
| Tensorlake 0.5.144 | Real workflow; direct commits, no PR | Valid | Worm |

Axios scores 0 on path, identity, and content, so it is a Block under either rule. Before its compromise, the legitimate 1.14.0 had been published through OIDC trusted publishing. LiteLLM's last legitimate CI-published version came eleven days before the malicious upload, so its path scores 0 and its content scores 0. TanStack and Tensorlake would pass an automated provenance check, but both score 0 on content, and Tensorlake also scores 0 on path because the commits bypassed review. The Playwright handover is the opposite case. It scores 1 on path and identity and 2 on content once you confirm the file list matches. That makes it a Hold that turns into an Upgrade when the issue is confirmed.

## Step-by-step: scoring one flagged release

Work through seven checks in order, writing each result into the verdict record as you go, then have a second engineer review it. The time estimates below are rough and unsourced, but the full pass is roughly an hour and a half of focused work.

#### Scoring one flagged release

1. **Diff the registry snapshot** - Compare the previous and flagged versions on _npmUser, trustedPublisher, dist.attestations, and maintainer emails, or PyPI roles against your stored snapshot. About 10 minutes; done when the diff table is in the record.
2. **Check the publish path against the repo** - Confirm a matching tag exists and note the gap between tag and publish. About 10 minutes.
3. **Check the commit path** - Confirm every commit reached the default branch through a pull request and look for a new committer. About 15 minutes.
4. **Look for a handover trail** - Find an announcement, pinned issue, org transfer, or PyPI ownership.organization change, and note who posted it. About 15 minutes.
5. **Check the new identity's tenure and footprint** - Record account age, cross-project contributions, permission timeline, and any OpenSSF pressure patterns. About 30 minutes.
6. **Review the change content** - Check new dependencies and whether they are imported, lifecycle scripts, .pth files, blobs, and file-list changes. About 20 minutes; never skipped.
7. **Record the verdict and get a second review** - Apply the verdict rule, name the checks for any Hold, and have a second engineer confirm the same verdict.

Sources disagree on ordering. pnpm's `trustPolicy: no-downgrade` runs the automated provenance check first and fails closed. That is a sound gate, because a downgrade stops the install before a human looks. Some security researchers argue that provenance must never clear a release on its own. Both positions fit this procedure: let the automated gate run first, and still do step 6 when steps 1 and 2 pass.

**Publisher-change verdict record**

```
Package:
Flagged version:              Previous trusted version:
Trigger: publisher changed / provenance lost / maintainers changed / PyPI ownership changed

1. Registry diff
Field                          | Previous | Flagged
_npmUser                       |          |
_npmUser.trustedPublisher      |          |
maintainers (emails)           |          |
dist.attestations.provenance   |          |
PyPI ownership.roles / org     |          |   (from stored snapshot)

2. Publish path   Tag exists: Y/N   Tag-to-publish gap:
3. Commit path    All commits via PR: Y/N   New committer on default branch: Y/N
   Publish path score (0/1/2):          Evidence:
   Account identity score (0/1/2):      Evidence:
4. Handover trail link:
   Posted by pre-existing maintainer account: Y/N
   Handover score (0/1/2):              Evidence:
5. New identity   Account age:   Cross-project footprint:   OpenSSF patterns seen:
   Tenure score (0/1/2):                Evidence:
6. Change content   New deps (imported in source? Y/N):   Lifecycle scripts:   .pth files:   File-list diff:
   Content score (0/1/2):               Evidence:

Total: __/10    Any zero: Y/N
Verdict: Upgrade / Hold / Block and pin
If Hold, named checks and owner:
-
Cooldown exemption: denied / granted by:
Scored by:                Reviewed by:
```

*Write one record per flagged release. Paste the registry diff verbatim and score every dimension, even when the verdict is obvious.*

## How does the publisher-change verdict go wrong?

Most wrong verdicts come from trusting one signal for something it cannot prove. Provenance gets read as identity, a new dependency gets read as a fresh dependency, and issue chatter gets read as a handover. Each failure below names the signal, how it misleads, and the check that catches it.

1. **Valid provenance gives a false clear.** A hijacked source account publishes through the real CI, as happened with TanStack and keyv. *Check:* were all commits merged through pull requests, and is there a new committer on the default branch?
2. **The `_npmUser` read misleads.** Staged publishing records the approver, and token publishes can still carry attestations. *Check:* read `trustedPublisher`, not `dist.attestations` alone.
3. **Pre-staging defeats the "new dependency" check.** plain-crypto-js had existed for about 18 hours before the malicious version. *Check:* is the dependency actually imported in source? A dependency nothing imports, especially one that carries a lifecycle script, scores 0.
4. **A trust downgrade is a false positive.** Playwright's move to microsoft1es was legitimate even though provenance disappeared. *Check:* look for a matching tag and an identical file list before you Block. Hold instead.
5. **A PyPI uploader change is invisible.** No API names the uploader. *Check:* look for attestation presence and diff against your stored ownership snapshot.
6. **Tenure clears an attacker.** The xz attacker had 2.6 years of tenure. *Check:* look for sock-puppet pressure and read the timeline of permission escalation, not just the account's age.
7. **The handover trail is manufactured.** LiteLLM issue threads were flooded with bot comments. *Check:* treat issue chatter as unverified unless a maintainer account that existed before the change posted it.
8. **The cooldown silently fails.** npm older than 11.10 ignores `min-release-age`. A pnpm 10.x bug ignores the cooldown when `shared-workspace-lockfile=false`. *Check:* run the package manager's `config get` for the setting on the version your CI actually runs.

> **Watch out:** Do not confirm through the new channel
>
> Confirm a handover only through a maintainer account or channel that existed before the flagged release. A contact listed in the new release or its issue thread proves nothing.

> Provenance tells you which workflow built the tarball. It does not tell you who pushed to the branch.

## How do you run a Hold: cooldowns and exemption approval

A Hold works because of the cooldown. A cooldown is a minimum release age your package manager enforces before it will install a new version. The Hold verdict amounts to refusing the per-package exemption until named checks clear, so you need to know your tool's setting, its unit, and its exemption key.

| Tool | Setting and unit | Default | Per-package exemption |
| --- | --- | --- | --- |
| npm | `min-release-age`, days, CLI 11.10.0+ | Off | Disputed |
| pnpm | `minimumReleaseAge`, minutes, 10.16+ | 1440 from v11 | `minimumReleaseAgeExclude` |
| Yarn | `npmMinimalAgeGate`, minutes or duration, Berry 4.10.0+ | 1 day; version disputed | `npmPreapprovedPackages` |
| Bun | `minimumReleaseAge`, seconds | Unverified | `minimumReleaseAgeExcludes` |

Sources conflict on several cells. For npm, one source gives `min-release-age-exclude` and `--min-release-age=0`, and another says npm has no exclusion yet and points to CLI issue #8994. For Yarn, sources disagree on which version introduced the 1-day default and on the unit, and one reports that suffixed duration strings are ignored on some versions. Renovate 42's `config:best-practices` preset reportedly sets a 3-day default. I did not verify uv `exclude-newer`, pip `uploaded-prior-to`, or Dependabot `cooldown`. Before you rely on any of these, confirm the value on your installed version.

#### Where a 1-day cooldown sits against documented timelines

1. **Publish** - The malicious version goes live on the registry
2. **First flag** - 6 to 11 minutes in the axios and Tensorlake cases
3. **Removal** - About 3 hours live for axios; about 4h50m to deletion for LiteLLM
4. **Cooldown ends** - 1 day after publish, after removal in these cases
5. **Not covered** - Upstream CI credential theft that began days earlier

*In documented compromises, scanners flagged releases within minutes and registries removed them within hours, so a 1-day cooldown outlasts the install-time window.*

The cooldown has limits. It covers the install-time window in these cases. It does not cover theft of upstream CI credentials, and in the LiteLLM case credential collection started five days before the malicious publish. A cooldown buys time for scanners and registries to act. It does not replace the verdict.

The exemption approval process I use:

- Grant an exemption only for a release that has scored Upgrade in a reviewed record.
- Record the approver's name in the record, next to the exemption key used.
- For a Hold, deny the exemption and list the checks and their owner.
- Remove the exemption once the release has aged past the cooldown, so it does not quietly cover later versions.

When a Hold turns on a handover question, ask a maintainer you already know to confirm it.

**Confirmation request to a pre-existing maintainer**

```
Hi <name>,

I work on a team that depends on <package>, and my tooling flagged <flagged version>. It was published by <account or path>, while <previous version> came through <previous publisher or workflow>.

Before I take it, can you confirm two things?
1. Did you, or a maintainer you added, publish this release?
2. Is the change in publish path or maintainers expected, and is it documented anywhere?

I am holding the upgrade until I hear back. If this was not you, the release may come from a compromised account, and you may want to check your tokens and CI credentials.

Thanks,
<your name>, <team>
```

*Send this through a channel that existed before the flagged release, never to a contact listed in the new release or its issue thread.*

## Who should own the verdict?

The verdict should belong to a named engineer working from the written rubric, with a second engineer reviewing. Few organisations have a dedicated supply-chain security person, so the owner will usually be a generalist.

**2 - US profiles in Refolk's index with supply-chain or open-source security engineer titles**

The two are at Google and AWS. The same title search in Germany returns zero.

| Segment | Country | Profiles |
| --- | --- | --- |
| Supply-chain or OSS security engineer titles | US | 2 |
| Same titles | Germany | 0 |
| Open source maintainer or OSPO titles | US | 40 |

In Refolk's index of professional profiles, US maintainer and OSPO titles outnumber supply-chain security titles 20 to 1. The top employer among the 40 is Akuity, with 2, and the SF Bay Area accounts for 6 of 25 sampled profiles. These are exact-title matches, so they are a floor, not the size of the market. Plenty of people do this work under other titles. Still, the inference is that few people hold this as a titled job. Expect the verdict to land on whoever is on call, and design for that: the rubric, the record, and the second review exist so that a generalist and a specialist reach the same answer.

If you want a specialist to review your rubric, or you are hiring for the role, the titled pool is too small to wait for. Searching on the work itself, such as contributions to Sigstore, pnpm, or the npm CLI, finds people that title searches miss. Refolk runs that kind of search in plain English across GitHub and professional profiles.

## How do you keep the verdict current?

Keep the verdict current by storing snapshots, re-checking the registry protections whose behaviour changes, and re-running the checklist on every flagged release. The registry metadata stays stable, but the protections around it change, and so do the tools' defaults.

What to re-check, and how:

- **npm's account-change hold.** npm places high-impact accounts in read-only mode for 72 hours after an email change or use of a 2FA recovery code. npm has not restated the "high-impact" threshold. One analysis infers it matches npm's 2FA policy: more than 1M weekly downloads or 500 dependents. Long-lived tokens never trigger the hold, and the axios versions were published with one. Check npm's current documentation for the threshold before you count on it.
- **Staged publishing.** It is opt-in. When a package uses it, read `_npmUser` as a possible approver.
- **Tool defaults and exemption syntax.** Re-run `config get` after every package manager upgrade, and watch npm CLI issue #8994 for the exclusion syntax.
- **Ownership snapshots.** Save PyPI `ownership.roles` and `ownership.organization`, and the npm `maintainers[]` list, at every adoption and upgrade. Without them, a PyPI ownership transfer is invisible.
- **Precedents.** When a legitimate handover is documented, link it in your records. When a new compromise pattern appears, add its signal to the failure-mode list.

#### Before you call the verdict done

- [ ] The registry diff for the previous and flagged versions is pasted into the record.
- [ ] Each of the five dimensions has a 0, 1, or 2 with one line of checkable evidence.
- [ ] Change content was reviewed even though provenance was present.
- [ ] Every new dependency was checked for an actual import in source.
- [ ] Any handover evidence cited was posted by an account that existed before the flagged release.
- [ ] For PyPI, ownership roles were compared against a stored snapshot, not memory.
- [ ] The cooldown setting was confirmed with config get on the package manager version CI runs.
- [ ] Any Hold lists named checks, an owner, and an exemption decision.
- [ ] A second engineer read the record and reached the same verdict.

## Frequently asked questions

### How do I tell if an npm package was hijacked?

Compare the flagged version with the last trusted version in the registry document. Check whether _npmUser changed, whether trusted publishing gave way to a direct token publish, whether maintainer emails changed, and whether a new dependency with a postinstall script appeared. In the axios compromise all four happened together. Then confirm the release has a matching tag and that its commits went through pull requests. Worm-style hijacks publish through the real pipeline and keep valid provenance.

### A new maintainer was added to a dependency. Is it safe to upgrade?

Not until you have scored the new identity and the change content. Look for a handover note posted by a maintainer account that existed before the change. Check the new account's age and its contributions to other projects, and look for the OpenSSF/OpenJS warning signs: persistent pressure on the maintainer, sudden requests for elevated rights, and sock-puppet endorsements. Tenure alone proves nothing, because the xz attacker spent 2.6 years building trust. Hold until the content review is clean.

### What should I do when a package is published without provenance?

Hold it and find out why the publish path changed. Missing provenance is only a signal if earlier versions had it. Then look for a matching repository tag published before the release, a file list identical to the source, and an explanation from an existing maintainer. Playwright's move to a corporate release account dropped provenance legitimately, so a downgrade alone is not grounds to Block.

### How should a dependency cooldown exception approval process work?

Grant an exemption only for a release that has scored Upgrade under the written rubric, and record who approved it. Use the package manager's per-package exemption key: minimumReleaseAgeExclude in pnpm, npmPreapprovedPackages in Yarn. npm's exclusion syntax is disputed, so confirm it on your installed version. If the release scored Hold, deny the exemption until the named checks clear. Remove the exemption once the release has aged past the cooldown.

### Is a PyPI project ownership transfer a risk?

It can be, and you cannot see it unless you saved the earlier state. PyPI only exposes current ownership.roles and ownership.organization, keeps no history, and no API names the account that uploaded a file. Store a snapshot of the roles each time you adopt or upgrade a project, diff against it, and check whether the new release carries a Trusted Publisher attestation from the same repository and workflow as before.

### Does valid provenance mean a release is safe?

No. Provenance proves which CI workflow built the tarball, not who pushed the code that workflow built. In the TanStack compromise, 84 malicious versions carried valid SLSA Build Level 3 provenance because the attacker controlled a source account. Valid provenance can earn the publish path a 2, but the change content review still has to happen.

---

*From the Refolk guide library. I revise these guides rather than replacing them, so the current version is always at https://www.refolk.ai/guides/publisher-change-dependency-release-verdict*
