Garnet Guard / Results

Results

Every malicious release we have replayed, with Garnet Guard's unedited output: 51 releases of 40 compromised packages (47 on PyPI, 4 on npm), December 2024 to September 2026, each compared with the release the project trusted before it. 51 of the 51 were flagged for a look. None was missed.

The PyPI files are fetched from files.pythonhosted.org and checked against the hashes PyPI published. npm deletes the files of a removed version, so those four replays use the registry facts the public write-ups recorded and read the tags. Nothing is run. The known-bad feed is off, so each result shows only what the release diff found by itself, and git tags are read as they stood on the day of each upload.

On this page: malicious releases · limits and what we're doing about them · everyday updates · files and sources

Malicious releases, replayed

PackageReleaseWhereUploadedFlagged by
lightning2.6.2PyPIApril 30, 2026GRN-C003, GRN-T004
lightning2.6.3PyPIApril 30, 2026GRN-C003, GRN-T004
elementary-data0.23.3PyPIApril 24, 2026GRN-C001 (with today's tags: GRN-C001, GRN-T004)
litellm1.82.7PyPIMarch 24, 2026GRN-C003
litellm1.82.8PyPIMarch 24, 2026GRN-C001, GRN-C003
telnyx4.87.1PyPIMarch 27, 2026GRN-C003, GRN-T004 (with today's tags: GRN-C003, GRN-C007)
telnyx4.87.2PyPIMarch 27, 2026GRN-C003, GRN-T004
ultralytics8.3.41PyPIDecember 4, 2024GRN-T004
ultralytics8.3.42PyPIDecember 5, 2024GRN-C007 (with today's tags: GRN-T004)
ultralytics8.3.45PyPIDecember 7, 2024GRN-C003, GRN-T001, GRN-T004
ultralytics8.3.46PyPIDecember 7, 2024GRN-C003, GRN-T001, GRN-T004
num2words0.5.15PyPIJuly 28, 2025GRN-C003, GRN-T004
num2words0.5.16PyPIJuly 28, 2025GRN-C003, GRN-T004
xinference2.6.0PyPIApril 21, 2026GRN-C003, GRN-T004
xinference2.6.1PyPIApril 21, 2026GRN-C003, GRN-T004
xinference2.6.2PyPIApril 21, 2026GRN-C003, GRN-T004
mistralai2.4.6PyPIMay 12, 2026GRN-C003, GRN-T004
guardrails-ai0.10.1PyPIMay 12, 2026GRN-C003
durabletask1.4.1PyPIMay 19, 2026GRN-C003, GRN-T004
durabletask1.4.2PyPIMay 19, 2026GRN-C003, GRN-T004
durabletask1.4.3PyPIMay 19, 2026GRN-C003, GRN-T004
memoryos2.0.34PyPISeptember 23, 2026GRN-C002, GRN-C003
embiggen0.11.97PyPIJune 8, 2026GRN-C001
gpsea0.9.14PyPIJune 8, 2026GRN-C001, GRN-T004 (with today's tags: GRN-C001)
pyphetools0.9.120PyPIJune 8, 2026GRN-C001
ppkt2synergy0.1.1PyPIJune 8, 2026GRN-C001
bramin0.0.3PyPIJune 5, 2026GRN-C001
cmd2func0.2.2PyPIJune 5, 2026GRN-C001, GRN-T004
coolbox0.4.1PyPIJune 5, 2026GRN-C001, GRN-T004
executor-engine0.3.4PyPIJune 5, 2026GRN-C001, GRN-T004
executor-http0.1.3PyPIJune 5, 2026GRN-C001, GRN-T004
funcdesc0.2.2PyPIJune 5, 2026GRN-C001, GRN-T004
magique0.6.9PyPIJune 5, 2026GRN-C001
magique-ai0.4.5PyPIJune 5, 2026GRN-C001
mrbios0.1.1PyPIJune 5, 2026GRN-C001, GRN-T004
napari-ufish0.0.3PyPIJune 5, 2026GRN-C001
nucbox0.1.2PyPIJune 5, 2026GRN-C001
okite0.0.7PyPIJune 5, 2026GRN-C001, GRN-T004
pantheon-agents0.6.1PyPIJune 5, 2026GRN-C001
pantheon-toolsets0.5.5PyPIJune 5, 2026GRN-C001
synago0.1.1PyPIJune 5, 2026GRN-C001
uprobe0.1.3PyPIJune 5, 2026GRN-C001, GRN-T004 (with today's tags: GRN-C001)
dreamgen1.8.1PyPIJune 7, 2026GRN-C001
mem86.0.1PyPIJune 7, 2026GRN-C001, GRN-T004
ensmallen0.8.101PyPIJune 8, 2026GRN-C001
phenopacket-store-toolkit0.1.7PyPIJune 8, 2026GRN-C001, GRN-T004
mrmustard0.7.4PyPIJuly 23, 2026GRN-C003
chalk5.6.1npmSeptember 8, 2025GRN-T004
debug4.4.2npmSeptember 8, 2025GRN-T004
nx21.5.0npmAugust 26, 2025GRN-C002, GRN-T001, GRN-T004
@ctrl/tinycolor4.1.1npmSeptember 15, 2025GRN-C002, GRN-T004

lightning

lightning 2.6.1 → 2.6.2 warn

PyPI · April 30, 2026 · MAL-2026-3201

Uploaded with a stolen token; a thread started in lightning/__init__.py launches a Bun-based credential stealer at import.

garnet diff lightning 2.6.1 2.6.2
lightning 2.6.1 → 2.6.2: WARN
  WARN  GRN-C003 new-import-time-exec: 2.6.2 adds import-time calls to sensitive APIs in 1 module(s)
           - lightning/__init__.py:14 calls subprocess.Popen (in _run_runtime(), reached at import)
  WARN  GRN-T004 no-matching-tag: 2.6.1 has a tag in github.com/Lightning-AI/lightning, but github.com/Lightning-AI/pytorch-lightning has none for 2.6.2
           - 2.6.1 → tag 2.6.1 at 655a3a91828694b9bfc241177f474741e483aeaa <https://github.com/Lightning-AI/lightning/tree/2.6.1>
           - 2.6.2: no tag named 2.6.2, v2.6.2, lightning-2.6.2 or lightning-v2.6.2 in github.com/Lightning-AI/pytorch-lightning <https://github.com/Lightning-AI/pytorch-lightning/tags>

verdict: warn

What this replay could not see

GRN-T001 (lost provenance) cannot fire: lightning did not publish attestations before the incident, so there was no provenance to lose. The tags match today's: there is no tag for 2.6.2, and we found no record of one pushed and later removed.

lightning 2.6.1 → 2.6.3 warn

PyPI · April 30, 2026 · MAL-2026-3201

Second upload of the same payload minutes later, with no git tag or workflow run.

garnet diff lightning 2.6.1 2.6.3
lightning 2.6.1 → 2.6.3: WARN
  WARN  GRN-C003 new-import-time-exec: 2.6.3 adds import-time calls to sensitive APIs in 1 module(s)
           - lightning/__init__.py:14 calls subprocess.Popen (in _run_runtime(), reached at import)
  WARN  GRN-T004 no-matching-tag: 2.6.1 has a tag in github.com/Lightning-AI/lightning, but github.com/Lightning-AI/pytorch-lightning has none for 2.6.3
           - 2.6.1 → tag 2.6.1 at 655a3a91828694b9bfc241177f474741e483aeaa <https://github.com/Lightning-AI/lightning/tree/2.6.1>
           - 2.6.3: no tag named 2.6.3, v2.6.3, lightning-2.6.3 or lightning-v2.6.3 in github.com/Lightning-AI/pytorch-lightning <https://github.com/Lightning-AI/pytorch-lightning/tags>

verdict: warn

What this replay could not see

The same limits as 2.6.2: no earlier attestations for GRN-T001 to compare against, and no record of a 2.6.3 tag on the day or since.

elementary-data

elementary-data 0.23.2 → 0.23.3 warn

PyPI · April 24, 2026 · MAL-2026-3083

Published by the project's own CI after a GitHub Actions script injection; a new elementary.pth runs a base64 payload at every Python start.

garnet diff elementary-data 0.23.2 0.23.3
elementary-data 0.23.2 → 0.23.3: WARN
  WARN  GRN-C001 new-pth-file: 0.23.3 adds code that runs at every Python startup (.pth)
           - new elementary.pth (246,691 bytes, 1 executable line(s)): import os, subprocess, tempfile, base64; x = os.path.join(tempfile.gettempdir(), ".trinny-security-update"); result = "ignore" if os.path.exists(x) else exec(b…

verdict: warn
Tags as of today
garnet diff elementary-data 0.23.2 0.23.3
elementary-data 0.23.2 → 0.23.3: WARN
  WARN  GRN-C001 new-pth-file: 0.23.3 adds code that runs at every Python startup (.pth)
           - new elementary.pth (246,691 bytes, 1 executable line(s)): import os, subprocess, tempfile, base64; x = os.path.join(tempfile.gettempdir(), ".trinny-security-update"); result = "ignore" if os.path.exists(x) else exec(b…
  WARN  GRN-T004 no-matching-tag: github.com/elementary-data/elementary has a tag for 0.23.2 but none for 0.23.3
           - 0.23.2 → tag v0.23.2 at c642a369fbe1d1be2ae9eb5e981b8863ece21bed <https://github.com/elementary-data/elementary/tree/v0.23.2>
           - 0.23.3: no tag named 0.23.3, v0.23.3, elementary-data-0.23.3 or elementary-data-v0.23.3 in github.com/elementary-data/elementary <https://github.com/elementary-data/elementary/tags>

verdict: warn

What this replay could not see

Because the project's own pipeline published the release, provenance checks alone would have passed it. The tag check stays quiet too: the attacker's workflow run pushed a v0.23.3 tag of its own, on a commit no branch contains (StepSecurity's analysis), so a matching tag existed when the release went out. That tag has since been deleted, so against today's tags GRN-T004 reports it missing. The finding that counts is in the content: the new .pth file.

litellm

litellm 1.82.6 → 1.82.7 warn

PyPI · March 24, 2026 · PYSEC-2026-2

Uploaded with a PyPI token stolen through a compromised Trivy step in CI; a payload added to litellm/proxy/proxy_server.py runs when the proxy module is imported.

garnet diff litellm 1.82.6 1.82.7
litellm 1.82.6 → 1.82.7: WARN
  WARN  GRN-C003 new-import-time-exec: 1.82.7 adds import-time calls to sensitive APIs in 1 module(s)
           - litellm/proxy/proxy_server.py:139 calls subprocess.run

verdict: warn

What this replay could not see

GRN-T001 cannot fire: litellm did not publish attestations before the attack. GRN-T004 cannot fire either: litellm tags its builds v1.82.6-nightly, v1.82.6.rc.1 and so on, never a plain version, so the trusted release had no tag to compare with. Only the wheel is replayed, because no public source gives the source archive's address, so GRN-C002, which reads source archives for new build hooks, could not run.

litellm 1.82.6 → 1.82.8 warn

PyPI · March 24, 2026 · PYSEC-2026-2

Second upload 13 minutes later: the same proxy_server.py payload, plus a new litellm_init.pth that runs it at every Python start.

garnet diff litellm 1.82.6 1.82.8
litellm 1.82.6 → 1.82.8: WARN
  WARN  GRN-C001 new-pth-file: 1.82.8 adds code that runs at every Python startup (.pth)
           - new litellm_init.pth (34,628 bytes, 1 executable line(s)): import os, subprocess, sys; subprocess.Popen([sys.executable, "-c", "import base64; exec(base64.b64decode('aW1wb3J0IHN1YnByb2Nlc3MKaW1wb3J0IHRlbXBmaWxlCmltcG9y…
  WARN  GRN-C003 new-import-time-exec: 1.82.8 adds import-time calls to sensitive APIs in 1 module(s)
           - litellm/proxy/proxy_server.py:139 calls subprocess.run

verdict: warn

What this replay could not see

The same limits as 1.82.7 for GRN-T001 and GRN-T004: no earlier attestations, and no plain version tags to compare with. Both the wheel and the source archive are replayed here.

telnyx

telnyx 4.87.0 → 4.87.1 warn

PyPI · March 27, 2026 · PYSEC-2026-3

Uploaded with a stolen PyPI token; code appended to telnyx/_client.py fetches a second stage at import. A typo stopped it from running in this version.

garnet diff telnyx 4.87.0 4.87.1
telnyx 4.87.0 → 4.87.1: WARN
  WARN  GRN-C003 new-import-time-exec: 4.87.1 adds import-time calls to sensitive APIs in 1 module(s)
           - telnyx/_client.py:7810 calls subprocess.Popen (in FetchAudio(), reached at import)
  WARN  GRN-T004 no-matching-tag: github.com/team-telnyx/telnyx-python has a tag for 4.87.0 but none for 4.87.1
           - 4.87.0 → tag v4.87.0 at fa704347b541de2884fa34021b805d0bda544ae8 <https://github.com/team-telnyx/telnyx-python/tree/v4.87.0>
           - 4.87.1: no tag named 4.87.1, v4.87.1, telnyx-4.87.1 or telnyx-v4.87.1 in github.com/team-telnyx/telnyx-python <https://github.com/team-telnyx/telnyx-python/tags>

verdict: warn
Tags as of today
garnet diff telnyx 4.87.0 4.87.1
telnyx 4.87.0 → 4.87.1: WARN
  WARN  GRN-C003 new-import-time-exec: 4.87.1 adds import-time calls to sensitive APIs in 1 module(s)
           - telnyx/_client.py:7810 calls subprocess.Popen (in FetchAudio(), reached at import)
  WARN  GRN-C007 code-not-in-source: 4.87.1 contains code that is not in github.com/team-telnyx/telnyx-python at tag v4.87.1 (1 file(s))
           - 4.87.1 → tag v4.87.1 at 8bbd1654fa63 in github.com/team-telnyx/telnyx-python; 2485 Python file(s) match the source, 6 don't <https://github.com/team-telnyx/telnyx-python/tree/v4.87.1>
           - telnyx/_client.py:7782 calls subprocess.run (in setup()); the source differs from src/telnyx/_client.py at v4.87.1 <https://github.com/team-telnyx/telnyx-python/blob/8bbd1654fa6392a210a277b13a1d35dd19c70243/src/telnyx/_client.py>
           - telnyx/_client.py:7786 calls urllib.request.Request (in setup()); the source differs from src/telnyx/_client.py at v4.87.1 <https://github.com/team-telnyx/telnyx-python/blob/8bbd1654fa6392a210a277b13a1d35dd19c70243/src/telnyx/_client.py>
           - telnyx/_client.py:7787 calls urllib.request.urlopen (in setup()); the source differs from src/telnyx/_client.py at v4.87.1 <https://github.com/team-telnyx/telnyx-python/blob/8bbd1654fa6392a210a277b13a1d35dd19c70243/src/telnyx/_client.py>
           - telnyx/_client.py:7801 calls subprocess.Popen (in setup()); the source differs from src/telnyx/_client.py at v4.87.1 <https://github.com/team-telnyx/telnyx-python/blob/8bbd1654fa6392a210a277b13a1d35dd19c70243/src/telnyx/_client.py>
           - telnyx/_client.py:7810 calls subprocess.Popen (in FetchAudio()); the source differs from src/telnyx/_client.py at v4.87.1 <https://github.com/team-telnyx/telnyx-python/blob/8bbd1654fa6392a210a277b13a1d35dd19c70243/src/telnyx/_client.py>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: telnyx did not publish attestations before the attack. The tag finding holds for the day only: Telnyx published its own v4.87.1 tag that evening, 16 hours after the upload (SafeDep found none on the day), so against today's tags GRN-T004 stays quiet. A flag is not proof the payload works: a typo stopped this one, and GRN-C003 reports the call at import, not whether it would succeed.

telnyx 4.87.0 → 4.87.2 warn

PyPI · March 27, 2026 · PYSEC-2026-3

Second upload 16 minutes later with the typo fixed, so the payload runs when telnyx is imported.

garnet diff telnyx 4.87.0 4.87.2
telnyx 4.87.0 → 4.87.2: WARN
  WARN  GRN-C003 new-import-time-exec: 4.87.2 adds import-time calls to sensitive APIs in 1 module(s)
           - telnyx/_client.py:7782 calls subprocess.run (in setup(), reached at import)
           - telnyx/_client.py:7786 calls urllib.request.Request (in setup(), reached at import)
           - telnyx/_client.py:7787 calls urllib.request.urlopen (in setup(), reached at import)
           - telnyx/_client.py:7801 calls subprocess.Popen (in setup(), reached at import)
           - telnyx/_client.py:7810 calls subprocess.Popen (in FetchAudio(), reached at import)
  WARN  GRN-T004 no-matching-tag: github.com/team-telnyx/telnyx-python has a tag for 4.87.0 but none for 4.87.2
           - 4.87.0 → tag v4.87.0 at fa704347b541de2884fa34021b805d0bda544ae8 <https://github.com/team-telnyx/telnyx-python/tree/v4.87.0>
           - 4.87.2: no tag named 4.87.2, v4.87.2, telnyx-4.87.2 or telnyx-v4.87.2 in github.com/team-telnyx/telnyx-python <https://github.com/team-telnyx/telnyx-python/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire, as for 4.87.1. The tags read the same today as on the day: the repository has no tag for 4.87.2 now, and we found no record of one pushed and later removed.

ultralytics

ultralytics 8.3.40 → 8.3.41 warn

PyPI · December 4, 2024 · PYSEC-2024-154

Built and published by the project's own release workflow, through Trusted Publishing, after a GitHub Actions cache-poisoning attack; a crypto-miner downloader was injected into the build. The repository's main branch never had version 8.3.41.

garnet diff ultralytics 8.3.40 8.3.41
ultralytics 8.3.40 → 8.3.41: WARN
  WARN  GRN-E001 fetch-failed: Couldn't evaluate GRN-T001: provenance of 8.3.41 is unknown
           - ultralytics-8.3.41-py3-none-any.whl: provenance not available
           - ultralytics-8.3.41.tar.gz: provenance not available
  WARN  GRN-T004 no-matching-tag: github.com/ultralytics/ultralytics has a tag for 8.3.40 but none for 8.3.41
           - 8.3.40 → tag v8.3.40 at dbdb451512ad4b3c636e6d61ad7af31e07301ece <https://github.com/ultralytics/ultralytics/tree/v8.3.40>
           - 8.3.41: no tag named 8.3.41, v8.3.41, ultralytics-8.3.41 or ultralytics-v8.3.41 in github.com/ultralytics/ultralytics <https://github.com/ultralytics/ultralytics/tags>

verdict: warn

What this replay could not see

The project's own workflow built and published this release through Trusted Publishing, so the upload path looked normal. GRN-E001 says PyPI no longer serves the provenance for the replay to check GRN-T001; on the day the attestation was valid. The repository's main branch never had version 8.3.41, so no tag existed and GRN-T004 fired.

ultralytics 8.3.40 → 8.3.42 warn

PyPI · December 5, 2024 · PYSEC-2024-154

Second poisoned build by the same workflow, from the real 8.3.42 commit, so it was tagged and released on GitHub like any other release.

garnet diff ultralytics 8.3.40 8.3.42
ultralytics 8.3.40 → 8.3.42: WARN
  WARN  GRN-C007 code-not-in-source: 8.3.42 contains code that is not in github.com/ultralytics/ultralytics at tag v8.3.42 (1 file(s))
           - 8.3.42 → tag v8.3.42 at 950d9f73fc3e in github.com/ultralytics/ultralytics; 150 Python file(s) match the source, 2 don't <https://github.com/ultralytics/ultralytics/tree/v8.3.42>
           - ultralytics/utils/downloads.py:292 calls subprocess.Popen (in safe_run()); the source differs from ultralytics/utils/downloads.py at v8.3.42 <https://github.com/ultralytics/ultralytics/blob/950d9f73fc3e12c87e45f4f792281ee8e3cfc75f/ultralytics/utils/downloads.py>
  WARN  GRN-E001 fetch-failed: Couldn't evaluate GRN-T001: provenance of 8.3.42 is unknown
           - ultralytics-8.3.42-py3-none-any.whl: provenance not available
           - ultralytics-8.3.42.tar.gz: provenance not available

verdict: warn
Tags as of today
garnet diff ultralytics 8.3.40 8.3.42
ultralytics 8.3.40 → 8.3.42: WARN
  WARN  GRN-E001 fetch-failed: Couldn't evaluate GRN-T001: provenance of 8.3.42 is unknown
           - ultralytics-8.3.42-py3-none-any.whl: provenance not available
           - ultralytics-8.3.42.tar.gz: provenance not available
  WARN  GRN-T004 no-matching-tag: github.com/ultralytics/ultralytics has a tag for 8.3.40 but none for 8.3.42
           - 8.3.40 → tag v8.3.40 at dbdb451512ad4b3c636e6d61ad7af31e07301ece <https://github.com/ultralytics/ultralytics/tree/v8.3.40>
           - 8.3.42: no tag named 8.3.42, v8.3.42, ultralytics-8.3.42 or ultralytics-v8.3.42 in github.com/ultralytics/ultralytics <https://github.com/ultralytics/ultralytics/tags>

verdict: warn

What this replay could not see

The project's own workflow built and published this release through Trusted Publishing, from the real 8.3.42 commit and with its usual tag, so every trust signal was true. GRN-E001 is a couldn't-check notice: PyPI no longer serves provenance for removed releases, so the replay cannot evaluate GRN-T001; on the day the attestation was valid. GRN-T004 stayed quiet because the workflow tagged v8.3.42 before publishing; that tag has since been deleted, so against today's tags it would report the release for the wrong reason. The injected downloader runs when a model is fetched, not at import, so GRN-C003 stays quiet too. The finding that counts is GRN-C007: the wheel's ultralytics/utils/downloads.py is not the file at the tag, and the extra code calls subprocess.Popen. The rules as first written missed this release; GRN-C007 is the fix it prompted.

ultralytics 8.3.44 → 8.3.45 warn

PyPI · December 7, 2024 · PYSEC-2024-154

Uploaded with a PyPI API token left in the CI environment from before Trusted Publishing, with no attestations and no repository activity.

garnet diff ultralytics 8.3.44 8.3.45
ultralytics 8.3.44 → 8.3.45: WARN
  WARN  GRN-C003 new-import-time-exec: 8.3.45 adds import-time calls to sensitive APIs in 1 module(s)
           - ultralytics/__init__.py:13 calls os.system
           - ultralytics/__init__.py:14 calls os.system
           - ultralytics/__init__.py:16 calls os.system
           - ultralytics/__init__.py:17 calls os.system
  WARN  GRN-T001 lost-provenance: 2 file(s) in 8.3.45 have no attestation; every file of 8.3.44 had one
           - ultralytics-8.3.45-py3-none-any.whl: no attestation
           - ultralytics-8.3.45.tar.gz: no attestation
           - 8.3.44 was attested by github.com/ultralytics/ultralytics .github/workflows/publish.yml
  WARN  GRN-T004 no-matching-tag: github.com/ultralytics/ultralytics has a tag for 8.3.44 but none for 8.3.45
           - 8.3.44 → tag v8.3.44 at c284a9e9744afb867c1acb6518d65cb21b7a1e75 <https://github.com/ultralytics/ultralytics/tree/v8.3.44>
           - 8.3.45: no tag named 8.3.45, v8.3.45, ultralytics-8.3.45 or ultralytics-v8.3.45 in github.com/ultralytics/ultralytics <https://github.com/ultralytics/ultralytics/tags>

verdict: warn

What this replay could not see

The first real GRN-T001 in the replay: 8.3.44 was attested by the project's publish workflow and 8.3.45 was not. PyPI no longer serves provenance for removed releases, so the replay records the missing attestations from PyPI's analysis.

ultralytics 8.3.44 → 8.3.46 warn

PyPI · December 7, 2024 · PYSEC-2024-154

Second token upload 46 minutes later.

garnet diff ultralytics 8.3.44 8.3.46
ultralytics 8.3.44 → 8.3.46: WARN
  WARN  GRN-C003 new-import-time-exec: 8.3.46 adds import-time calls to sensitive APIs in 1 module(s)
           - ultralytics/__init__.py:18 calls os.system
  WARN  GRN-T001 lost-provenance: 2 file(s) in 8.3.46 have no attestation; every file of 8.3.44 had one
           - ultralytics-8.3.46-py3-none-any.whl: no attestation
           - ultralytics-8.3.46.tar.gz: no attestation
           - 8.3.44 was attested by github.com/ultralytics/ultralytics .github/workflows/publish.yml
  WARN  GRN-T004 no-matching-tag: github.com/ultralytics/ultralytics has a tag for 8.3.44 but none for 8.3.46
           - 8.3.44 → tag v8.3.44 at c284a9e9744afb867c1acb6518d65cb21b7a1e75 <https://github.com/ultralytics/ultralytics/tree/v8.3.44>
           - 8.3.46: no tag named 8.3.46, v8.3.46, ultralytics-8.3.46 or ultralytics-v8.3.46 in github.com/ultralytics/ultralytics <https://github.com/ultralytics/ultralytics/tags>

verdict: warn

What this replay could not see

As 8.3.45: no attestations where 8.3.44 had them, recorded from PyPI's analysis.

num2words

num2words 0.5.14 → 0.5.15 warn

PyPI · July 28, 2025 · PYSEC-2025-72

Uploaded after a maintainer was phished through a fake PyPI login page, with no matching git tag.

garnet diff num2words 0.5.14 0.5.15
num2words 0.5.14 → 0.5.15: WARN
  WARN  GRN-C003 new-import-time-exec: 0.5.15 adds import-time calls to sensitive APIs in 1 module(s)
           - num2words/__init__.py:121 calls ctypes.CDLL
  WARN  GRN-E002 parse-failed: 1 new or changed Python file(s) in 0.5.15 couldn't be parsed
           - num2words/_build.py: not valid Python 3 syntax
  WARN  GRN-T004 no-matching-tag: github.com/savoirfairelinux/num2words has a tag for 0.5.14 but none for 0.5.15
           - 0.5.14 → tag v0.5.14 at 6abe2c64714a38e41de23fe20fa319d0ad2fd845 <https://github.com/savoirfairelinux/num2words/tree/v0.5.14>
           - 0.5.15: no tag named 0.5.15, v0.5.15, num2words-0.5.15 or num2words-v0.5.15 in github.com/savoirfairelinux/num2words <https://github.com/savoirfairelinux/num2words/tags>

verdict: warn

What this replay could not see

GRN-E002 reports num2words/_build.py, a Windows DLL named as a Python file, as something it could not parse rather than passing it. GRN-T001 cannot fire: no earlier attestations.

num2words 0.5.14 → 0.5.16 warn

PyPI · July 28, 2025 · PYSEC-2025-72

Second phished upload three hours later.

garnet diff num2words 0.5.14 0.5.16
num2words 0.5.14 → 0.5.16: WARN
  WARN  GRN-C003 new-import-time-exec: 0.5.16 adds import-time calls to sensitive APIs in 1 module(s)
           - num2words/__init__.py:121 calls ctypes.CDLL
  WARN  GRN-E002 parse-failed: 1 new or changed Python file(s) in 0.5.16 couldn't be parsed
           - num2words/_build.py: not valid Python 3 syntax
  WARN  GRN-T004 no-matching-tag: github.com/savoirfairelinux/num2words has a tag for 0.5.14 but none for 0.5.16
           - 0.5.14 → tag v0.5.14 at 6abe2c64714a38e41de23fe20fa319d0ad2fd845 <https://github.com/savoirfairelinux/num2words/tree/v0.5.14>
           - 0.5.16: no tag named 0.5.16, v0.5.16, num2words-0.5.16 or num2words-v0.5.16 in github.com/savoirfairelinux/num2words <https://github.com/savoirfairelinux/num2words/tags>

verdict: warn

What this replay could not see

As 0.5.15.

xinference

xinference 2.5.0 → 2.6.0 warn

PyPI · April 21, 2026 · MAL-2026-3000

A version the project never released, published by TeamPCP; a credential stealer in xinference/__init__.py runs at import.

garnet diff xinference 2.5.0 2.6.0
xinference 2.5.0 → 2.6.0: WARN
  WARN  GRN-C003 new-import-time-exec: 2.6.0 adds import-time calls to sensitive APIs in 1 module(s)
           - xinference/__init__.py:37 calls subprocess.Popen
  WARN  GRN-T004 no-matching-tag: github.com/xorbitsai/inference has a tag for 2.5.0 but none for 2.6.0
           - 2.5.0 → tag v2.5.0 at c9b597ea6b5335a6116f26ad62279aa51872aa50 <https://github.com/xorbitsai/inference/tree/v2.5.0>
           - 2.6.0: no tag named 2.6.0, v2.6.0, xinference-2.6.0 or xinference-v2.6.0 in github.com/xorbitsai/inference <https://github.com/xorbitsai/inference/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The version never existed in the project's repository, so GRN-T004 fired.

xinference 2.5.0 → 2.6.1 warn

PyPI · April 21, 2026 · MAL-2026-3000

Second upload the same evening, with the payload moved into an _install() function that runs it with exec().

garnet diff xinference 2.5.0 2.6.1
xinference 2.5.0 → 2.6.1: WARN
  WARN  GRN-C003 new-import-time-exec: 2.6.1 adds import-time calls to sensitive APIs in 1 module(s)
           - xinference/__init__.py:42 calls base64.b64decode → exec (in _install(), reached at import)
           - xinference/__init__.py:42 calls exec (in _install(), reached at import)
  WARN  GRN-T004 no-matching-tag: github.com/xorbitsai/inference has a tag for 2.5.0 but none for 2.6.1
           - 2.5.0 → tag v2.5.0 at c9b597ea6b5335a6116f26ad62279aa51872aa50 <https://github.com/xorbitsai/inference/tree/v2.5.0>
           - 2.6.1: no tag named 2.6.1, v2.6.1, xinference-2.6.1 or xinference-v2.6.1 in github.com/xorbitsai/inference <https://github.com/xorbitsai/inference/tags>

verdict: warn

What this replay could not see

The payload is base64 passed to exec inside _install(), which runs at import; GRN-C003 follows the call and reports both the decode and the exec. GRN-T001 cannot fire: no earlier attestations.

xinference 2.5.0 → 2.6.2 warn

PyPI · April 21, 2026 · MAL-2026-3000

Third upload seven minutes later, starting the payload as a detached subprocess.

garnet diff xinference 2.5.0 2.6.2
xinference 2.5.0 → 2.6.2: WARN
  WARN  GRN-C003 new-import-time-exec: 2.6.2 adds import-time calls to sensitive APIs in 1 module(s)
           - xinference/__init__.py:42 calls subprocess.Popen (in _install(), reached at import)
  WARN  GRN-T004 no-matching-tag: github.com/xorbitsai/inference has a tag for 2.5.0 but none for 2.6.2
           - 2.5.0 → tag v2.5.0 at c9b597ea6b5335a6116f26ad62279aa51872aa50 <https://github.com/xorbitsai/inference/tree/v2.5.0>
           - 2.6.2: no tag named 2.6.2, v2.6.2, xinference-2.6.2 or xinference-v2.6.2 in github.com/xorbitsai/inference <https://github.com/xorbitsai/inference/tags>

verdict: warn

What this replay could not see

As 2.6.0.

mistralai

mistralai 2.4.5 → 2.4.6 warn

PyPI · May 12, 2026 · MAL-2026-3608

A version Mistral never released, published by TeamPCP; code in mistralai/client/__init__.py downloads and starts a second stage at import on Linux.

garnet diff mistralai 2.4.5 2.4.6
mistralai 2.4.5 → 2.4.6: WARN
  WARN  GRN-C003 new-import-time-exec: 2.4.6 adds import-time calls to sensitive APIs in 1 module(s)
           - mistralai/client/__init__.py:34 calls subprocess.run (in _run_background_task(), reached at import)
           - mistralai/client/__init__.py:37 calls subprocess.Popen (in _run_background_task(), reached at import)
  WARN  GRN-T004 no-matching-tag: github.com/mistralai/client-python has a tag for 2.4.5 but none for 2.4.6
           - 2.4.5 → tag v2.4.5 at 7ebe84b5a9eee0714f81c687c8cf636eaa12476f <https://github.com/mistralai/client-python/tree/v2.4.5>
           - 2.4.6: no tag named 2.4.6, v2.4.6, mistralai-2.4.6 or mistralai-v2.4.6 in github.com/mistralai/client-python <https://github.com/mistralai/client-python/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. Mistral never released a 2.4.6, so GRN-T004 fired.

guardrails-ai

guardrails-ai 0.10.0 → 0.10.1 warn

PyPI · May 12, 2026 · MAL-2026-3607

Malicious release published the same night as mistralai 2.4.6 (CVE-2026-45758).

garnet diff guardrails-ai 0.10.0 0.10.1
guardrails-ai 0.10.0 → 0.10.1: WARN
  WARN  GRN-C003 new-import-time-exec: 0.10.1 adds import-time calls to sensitive APIs in 1 module(s)
           - guardrails/__init__.py:44 calls urllib.request.Request
           - guardrails/__init__.py:45 calls urllib.request.urlopen
           - guardrails/__init__.py:48 calls subprocess.run

verdict: warn

What this replay could not see

The package links no source repository, so GRN-T004 has nothing to check. GRN-T001 cannot fire: no earlier attestations.

durabletask

durabletask 1.4.0 → 1.4.1 warn

PyPI · May 19, 2026 · MAL-2026-4174

Microsoft's Durable Task SDK: code added to durabletask/__init__.py and durabletask/task.py downloads and runs a second stage at import.

garnet diff durabletask 1.4.0 1.4.1
durabletask 1.4.0 → 1.4.1: WARN
  WARN  GRN-C003 new-import-time-exec: 1.4.1 adds import-time calls to sensitive APIs in 1 module(s)
           - durabletask/__init__.py:9 calls urllib.request.urlretrieve
           - durabletask/__init__.py:11 calls subprocess.Popen
  WARN  GRN-T004 no-matching-tag: github.com/microsoft/durabletask-python has a tag for 1.4.0 but none for 1.4.1
           - 1.4.0 → tag v1.4.0 at e613e571117debcaa66a8b45ed73a1f19583ab60 <https://github.com/microsoft/durabletask-python/tree/v1.4.0>
           - 1.4.1: no tag named 1.4.1, v1.4.1, durabletask-1.4.1 or durabletask-v1.4.1 in github.com/microsoft/durabletask-python <https://github.com/microsoft/durabletask-python/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations.

durabletask 1.4.0 → 1.4.2 warn

PyPI · May 19, 2026 · MAL-2026-4174

Second upload 30 minutes later.

garnet diff durabletask 1.4.0 1.4.2
durabletask 1.4.0 → 1.4.2: WARN
  WARN  GRN-C003 new-import-time-exec: 1.4.2 adds import-time calls to sensitive APIs in 2 module(s)
           - durabletask/__init__.py:9 calls urllib.request.urlretrieve
           - durabletask/__init__.py:11 calls subprocess.Popen
           - durabletask/task.py:24 calls urllib.request.urlretrieve
           - durabletask/task.py:26 calls subprocess.Popen
  WARN  GRN-T004 no-matching-tag: github.com/microsoft/durabletask-python has a tag for 1.4.0 but none for 1.4.2
           - 1.4.0 → tag v1.4.0 at e613e571117debcaa66a8b45ed73a1f19583ab60 <https://github.com/microsoft/durabletask-python/tree/v1.4.0>
           - 1.4.2: no tag named 1.4.2, v1.4.2, durabletask-1.4.2 or durabletask-v1.4.2 in github.com/microsoft/durabletask-python <https://github.com/microsoft/durabletask-python/tags>

verdict: warn

What this replay could not see

As 1.4.1.

durabletask 1.4.0 → 1.4.3 warn

PyPI · May 19, 2026 · MAL-2026-4174

Third upload five minutes later.

garnet diff durabletask 1.4.0 1.4.3
durabletask 1.4.0 → 1.4.3: WARN
  WARN  GRN-C003 new-import-time-exec: 1.4.3 adds import-time calls to sensitive APIs in 5 module(s)
           - durabletask/__init__.py:9 calls urllib.request.urlretrieve
           - durabletask/__init__.py:11 calls subprocess.Popen
           - durabletask/entities/__init__.py:26 calls urllib.request.urlretrieve
           - durabletask/entities/__init__.py:28 calls subprocess.Popen
           - durabletask/extensions/__init__.py:14 calls urllib.request.urlretrieve
           - durabletask/extensions/__init__.py:16 calls subprocess.Popen
           - durabletask/payload/__init__.py:39 calls urllib.request.urlretrieve
           - durabletask/payload/__init__.py:41 calls subprocess.Popen
           - durabletask/task.py:24 calls urllib.request.urlretrieve
           - durabletask/task.py:26 calls subprocess.Popen
  WARN  GRN-T004 no-matching-tag: github.com/microsoft/durabletask-python has a tag for 1.4.0 but none for 1.4.3
           - 1.4.0 → tag v1.4.0 at e613e571117debcaa66a8b45ed73a1f19583ab60 <https://github.com/microsoft/durabletask-python/tree/v1.4.0>
           - 1.4.3: no tag named 1.4.3, v1.4.3, durabletask-1.4.3 or durabletask-v1.4.3 in github.com/microsoft/durabletask-python <https://github.com/microsoft/durabletask-python/tags>

verdict: warn

What this replay could not see

As 1.4.1.

memoryos

memoryos 2.0.33 → 2.0.34 warn

PyPI · September 23, 2026 · MAL-2026-16475

An attacker with write access pushed release commits and a v2.0.34 tag, and the project's own release workflow published them; importing the package runs memos/_stage0.py, which starts a bundled binary.

garnet diff memoryos 2.0.33 2.0.34
memoryos 2.0.33 → 2.0.34: WARN
  WARN  GRN-C002 new-build-hook: 2.0.34 adds code that runs when the sdist is built
           - build backend poetry.core.masonry.api → in-tree:sckit_poetry_build
  WARN  GRN-C003 new-import-time-exec: 2.0.34 adds import-time calls to sensitive APIs in 1 module(s)
           - new module memos/_stage0.py:22 calls subprocess.Popen (in trigger(), reached at import from memos/api/client.py)

verdict: warn

What this replay could not see

The attacker had write access, pushed a v2.0.34 tag, and let the project's own workflow publish, so the tag and the publish path looked normal. The content carries it: a build backend changed to an in-tree module, and a new module that starts a bundled binary, reached at import from memos/api/client.py.

embiggen

embiggen 0.11.96 → 0.11.97 warn

PyPI · June 8, 2026 · MAL-2026-5314

Hades campaign: a new embiggen-setup.pth downloads Bun and runs a bundled _index.js at every Python start. 0.11.96 shipped only an sdist and 0.11.97 only a wheel.

garnet diff embiggen 0.11.96 0.11.97
embiggen 0.11.96 → 0.11.97: WARN
  WARN  GRN-C001 new-pth-file: 0.11.97 adds code that runs at every Python startup (.pth)
           - new embiggen-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
           - compared embiggen-0.11.97-py3-none-any.whl with embiggen-0.11.96.tar.gz: the releases ship different kinds of files

verdict: warn

What this replay could not see

0.11.96 shipped only a source archive and 0.11.97 only a wheel; Garnet compares the two kinds of file and says so on the finding. embiggen doesn't tag releases, so GRN-T004 could not fire. GRN-T001 cannot fire: no earlier attestations.

gpsea

gpsea 0.9.13 → 0.9.14 warn

PyPI · June 8, 2026 · MAL-2026-5316

Hades campaign: the same <name>-setup.pth payload.

garnet diff gpsea 0.9.13 0.9.14
gpsea 0.9.13 → 0.9.14: WARN
  WARN  GRN-C001 new-pth-file: 0.9.14 adds code that runs at every Python startup (.pth)
           - new gpsea-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/P2GX/gpsea has a tag for 0.9.13 but none for 0.9.14
           - 0.9.13 → tag v0.9.13 at 086e88e90928a81f24b2b94268323af5b7e9e108 <https://github.com/P2GX/gpsea/tree/v0.9.13>
           - 0.9.14: no tag named 0.9.14, v0.9.14, gpsea-0.9.14 or gpsea-v0.9.14 in github.com/P2GX/gpsea <https://github.com/P2GX/gpsea/tags>

verdict: warn
Tags as of today
garnet diff gpsea 0.9.13 0.9.14
gpsea 0.9.13 → 0.9.14: WARN
  WARN  GRN-C001 new-pth-file: 0.9.14 adds code that runs at every Python startup (.pth)
           - new gpsea-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The project tagged its own real 0.9.14 three months later, so against today's tags GRN-T004 would miss this release; the replay reads the tags as of June 8 and reports it.

pyphetools

pyphetools 0.9.119 → 0.9.120 warn

PyPI · June 8, 2026 · MAL-2026-5324

Hades campaign: the same <name>-setup.pth payload.

garnet diff pyphetools 0.9.119 0.9.120
pyphetools 0.9.119 → 0.9.120: WARN
  WARN  GRN-C001 new-pth-file: 0.9.120 adds code that runs at every Python startup (.pth)
           - new pyphetools-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-E001 fetch-failed: Couldn't fully check 0.9.120: tag lookup: tag lookup for github.com/monarch-initiative/pyphetools returned HTTP 401
           - tag lookup: tag lookup for github.com/monarch-initiative/pyphetools returned HTTP 401
           - not evaluated: GRN-T004
  WARN  GRN-E001 fetch-failed [in 0.9.119]: Couldn't fully check 0.9.119: tag lookup: tag lookup for github.com/monarch-initiative/pyphetools returned HTTP 401
           - tag lookup: tag lookup for github.com/monarch-initiative/pyphetools returned HTTP 401
           - not evaluated: GRN-T004

verdict: warn

What this replay could not see

The repository is no longer public, so the tag lookup fails and GRN-E001 says so instead of passing.

ppkt2synergy

ppkt2synergy 0.1.0 → 0.1.1 warn

PyPI · June 8, 2026 · MAL-2026-5323

Hades campaign: the same <name>-setup.pth payload.

garnet diff ppkt2synergy 0.1.0 0.1.1
ppkt2synergy 0.1.0 → 0.1.1: WARN
  WARN  GRN-C001 new-pth-file: 0.1.1 adds code that runs at every Python startup (.pth)
           - new ppkt2synergy-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The repository has no tags, so GRN-T004 could not fire.

bramin

bramin 0.0.1 → 0.0.3 warn

PyPI · June 5, 2026 · MAL-2026-5280

Hades campaign: the same <name>-setup.pth payload. 0.0.2, 0.0.3 and 0.0.4 were all malicious; the one trusted release, 0.0.1, dates from 2020 and ships an egg, a wheel and an sdist.

garnet diff bramin 0.0.1 0.0.3
bramin 0.0.1 → 0.0.3: WARN
  WARN  GRN-C001 new-pth-file: 0.0.3 adds code that runs at every Python startup (.pth)
           - new bramin-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The repository tags nothing, so GRN-T004 has no trusted tag to compare with. The one trusted release, 0.0.1, is from 2020. GRN-T001 cannot fire: no earlier attestations.

cmd2func

cmd2func 0.2.1 → 0.2.2 warn

PyPI · June 5, 2026 · MAL-2026-5290

Hades campaign: the same <name>-setup.pth payload.

garnet diff cmd2func 0.2.1 0.2.2
cmd2func 0.2.1 → 0.2.2: WARN
  WARN  GRN-C001 new-pth-file: 0.2.2 adds code that runs at every Python startup (.pth)
           - new cmd2func-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/Nanguage/cmd2func has a tag for 0.2.1 but none for 0.2.2
           - 0.2.1 → tag 0.2.1 at ba69eab4b3aeccd143e41d4fa4527a553b7373d5 <https://github.com/Nanguage/cmd2func/tree/0.2.1>
           - 0.2.2: no tag named 0.2.2, v0.2.2, cmd2func-0.2.2 or cmd2func-v0.2.2 in github.com/Nanguage/cmd2func <https://github.com/Nanguage/cmd2func/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

coolbox

coolbox 0.4.0 → 0.4.1 warn

PyPI · June 5, 2026 · MAL-2026-5295

Hades campaign: the same <name>-setup.pth payload.

garnet diff coolbox 0.4.0 0.4.1
coolbox 0.4.0 → 0.4.1: WARN
  WARN  GRN-C001 new-pth-file: 0.4.1 adds code that runs at every Python startup (.pth)
           - new coolbox-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/GangCaoLab/CoolBox has a tag for 0.4.0 but none for 0.4.1
           - 0.4.0 → tag 0.4.0 at 651b930dbc59e3aa732f7ecc98e0af09e19e2719 <https://github.com/GangCaoLab/CoolBox/tree/0.4.0>
           - 0.4.1: no tag named 0.4.1, v0.4.1, coolbox-0.4.1 or coolbox-v0.4.1 in github.com/GangCaoLab/CoolBox <https://github.com/GangCaoLab/CoolBox/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

executor-engine

executor-engine 0.3.3 → 0.3.4 warn

PyPI · June 5, 2026 · MAL-2026-5298

Hades campaign: the same <name>-setup.pth payload.

garnet diff executor-engine 0.3.3 0.3.4
executor-engine 0.3.3 → 0.3.4: WARN
  WARN  GRN-C001 new-pth-file: 0.3.4 adds code that runs at every Python startup (.pth)
           - new executor_engine-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/Nanguage/executor-engine has a tag for 0.3.3 but none for 0.3.4
           - 0.3.3 → tag 0.3.3 at 1281610c07f546be87c2270c3eddcd557be71eaa <https://github.com/Nanguage/executor-engine/tree/0.3.3>
           - 0.3.4: no tag named 0.3.4, v0.3.4, executor-engine-0.3.4 or executor-engine-v0.3.4 in github.com/Nanguage/executor-engine <https://github.com/Nanguage/executor-engine/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

executor-http

executor-http 0.1.2 → 0.1.3 warn

PyPI · June 5, 2026 · MAL-2026-5281

Hades campaign: the same <name>-setup.pth payload.

garnet diff executor-http 0.1.2 0.1.3
executor-http 0.1.2 → 0.1.3: WARN
  WARN  GRN-C001 new-pth-file: 0.1.3 adds code that runs at every Python startup (.pth)
           - new executor_http-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/Nanguage/executor-http has a tag for 0.1.2 but none for 0.1.3
           - 0.1.2 → tag 0.1.2 at 6d1f6c2174af00fb9433ccf78571f176d3ee92d9 <https://github.com/Nanguage/executor-http/tree/0.1.2>
           - 0.1.3: no tag named 0.1.3, v0.1.3, executor-http-0.1.3 or executor-http-v0.1.3 in github.com/Nanguage/executor-http <https://github.com/Nanguage/executor-http/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

funcdesc

funcdesc 0.2.1 → 0.2.2 warn

PyPI · June 5, 2026 · MAL-2026-5300

Hades campaign: the same <name>-setup.pth payload.

garnet diff funcdesc 0.2.1 0.2.2
funcdesc 0.2.1 → 0.2.2: WARN
  WARN  GRN-C001 new-pth-file: 0.2.2 adds code that runs at every Python startup (.pth)
           - new funcdesc-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/Nanguage/funcdesc has a tag for 0.2.1 but none for 0.2.2
           - 0.2.1 → tag 0.2.1 at 60df2dc72433567d2c7e9aaa2de846f7a4ae1868 <https://github.com/Nanguage/funcdesc/tree/0.2.1>
           - 0.2.2: no tag named 0.2.2, v0.2.2, funcdesc-0.2.2 or funcdesc-v0.2.2 in github.com/Nanguage/funcdesc <https://github.com/Nanguage/funcdesc/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

magique

magique 0.6.7 → 0.6.9 warn

PyPI · June 5, 2026 · MAL-2026-5296

Hades campaign: the same <name>-setup.pth payload. 0.6.8 was malicious too and is not replayed (no file hash published). The package metadata links no repository, so GRN-T004 cannot look for a tag.

garnet diff magique 0.6.7 0.6.9
magique 0.6.7 → 0.6.9: WARN
  WARN  GRN-C001 new-pth-file: 0.6.9 adds code that runs at every Python startup (.pth)
           - new magique-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own.

magique-ai

magique-ai 0.4.3 → 0.4.5 warn

PyPI · June 5, 2026 · MAL-2026-5294

Hades campaign: the same <name>-setup.pth payload. 0.4.4 was malicious too and is not replayed (no file hash published). The package metadata links no repository.

garnet diff magique-ai 0.4.3 0.4.5
magique-ai 0.4.3 → 0.4.5: WARN
  WARN  GRN-C001 new-pth-file: 0.4.5 adds code that runs at every Python startup (.pth)
           - new magique_ai-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own.

mrbios

mrbios 0.1.0 → 0.1.1 warn

PyPI · June 5, 2026 · MAL-2026-5282

Hades campaign: the same <name>-setup.pth payload.

garnet diff mrbios 0.1.0 0.1.1
mrbios 0.1.0 → 0.1.1: WARN
  WARN  GRN-C001 new-pth-file: 0.1.1 adds code that runs at every Python startup (.pth)
           - new mrbios-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/Nanguage/mrbios has a tag for 0.1.0 but none for 0.1.1
           - 0.1.0 → tag 0.1.0 at 3a1631f503b569e722eb06774d20500b5b881033 <https://github.com/Nanguage/mrbios/tree/0.1.0>
           - 0.1.1: no tag named 0.1.1, v0.1.1, mrbios-0.1.1 or mrbios-v0.1.1 in github.com/Nanguage/mrbios <https://github.com/Nanguage/mrbios/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

napari-ufish

napari-ufish 0.0.1 → 0.0.3 warn

PyPI · June 5, 2026 · MAL-2026-5275

Hades campaign: the same <name>-setup.pth payload. 0.0.2 was malicious too and is not replayed (no file hash published). The package metadata links no repository.

garnet diff napari-ufish 0.0.1 0.0.3
napari-ufish 0.0.1 → 0.0.3: WARN
  WARN  GRN-C001 new-pth-file: 0.0.3 adds code that runs at every Python startup (.pth)
           - new napari_ufish-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own.

nucbox

nucbox 0.1.1 → 0.1.2 warn

PyPI · June 5, 2026 · MAL-2026-5276

Hades campaign: the same <name>-setup.pth payload. The package metadata links no repository.

garnet diff nucbox 0.1.1 0.1.2
nucbox 0.1.1 → 0.1.2: WARN
  WARN  GRN-C001 new-pth-file: 0.1.2 adds code that runs at every Python startup (.pth)
           - new nucbox-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own.

okite

okite 0.0.6 → 0.0.7 warn

PyPI · June 5, 2026 · MAL-2026-5283

Hades campaign: the same <name>-setup.pth payload.

garnet diff okite 0.0.6 0.0.7
okite 0.0.6 → 0.0.7: WARN
  WARN  GRN-C001 new-pth-file: 0.0.7 adds code that runs at every Python startup (.pth)
           - new okite-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/Nanguage/okite has a tag for 0.0.6 but none for 0.0.7
           - 0.0.6 → tag 0.0.6 at 783add3e81771e58865bc1c026baa6cbf946cdfc <https://github.com/Nanguage/okite/tree/0.0.6>
           - 0.0.7: no tag named 0.0.7, v0.0.7, okite-0.0.7 or okite-v0.0.7 in github.com/Nanguage/okite <https://github.com/Nanguage/okite/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

pantheon-agents

pantheon-agents 0.6.0 → 0.6.1 warn

PyPI · June 5, 2026 · MAL-2026-5299

Hades campaign: the same <name>-setup.pth payload. A tag v0.6.1 exists today and points at a commit dated 2026-05-25, before the upload; a lightweight tag carries no date of its own, so the replay assumes it was already there and GRN-T004 stays quiet.

garnet diff pantheon-agents 0.6.0 0.6.1
pantheon-agents 0.6.0 → 0.6.1: WARN
  WARN  GRN-C001 new-pth-file: 0.6.1 adds code that runs at every Python startup (.pth)
           - new pantheon_agents-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

A v0.6.1 tag exists and points at a commit from May 25, 2026, eleven days before the upload. A lightweight tag carries no date of its own, so the replay assumes it was already there and GRN-T004 stays quiet. GRN-T001 cannot fire: no earlier attestations.

pantheon-toolsets

pantheon-toolsets 0.5.4 → 0.5.5 warn

PyPI · June 5, 2026 · MAL-2026-5277

Hades campaign: the same <name>-setup.pth payload. The package metadata links no repository.

garnet diff pantheon-toolsets 0.5.4 0.5.5
pantheon-toolsets 0.5.4 → 0.5.5: WARN
  WARN  GRN-C001 new-pth-file: 0.5.5 adds code that runs at every Python startup (.pth)
           - new pantheon_toolsets-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own.

synago

synago 0.1.0 → 0.1.1 warn

PyPI · June 5, 2026 · MAL-2026-5284

Hades campaign: the same <name>-setup.pth payload. The package metadata links no repository.

garnet diff synago 0.1.0 0.1.1
synago 0.1.0 → 0.1.1: WARN
  WARN  GRN-C001 new-pth-file: 0.1.1 adds code that runs at every Python startup (.pth)
           - new synago-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own.

uprobe

uprobe 0.1.2 → 0.1.3 warn

PyPI · June 5, 2026 · MAL-2026-5279

Hades campaign: the same <name>-setup.pth payload. The project later tagged its own v0.1.3 and v0.1.4 and published 0.1.5 to PyPI (2026-09-30).

garnet diff uprobe 0.1.2 0.1.3
uprobe 0.1.2 → 0.1.3: WARN
  WARN  GRN-C001 new-pth-file: 0.1.3 adds code that runs at every Python startup (.pth)
           - new uprobe-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/UFISH-Team/U-Probe has a tag for 0.1.2 but none for 0.1.3
           - 0.1.2 → tag v0.1.2 at f302dffcd148f73a40494831f08ba62997104386 <https://github.com/UFISH-Team/U-Probe/tree/v0.1.2>
           - 0.1.3: no tag named 0.1.3, v0.1.3, uprobe-0.1.3 or uprobe-v0.1.3 in github.com/UFISH-Team/U-Probe <https://github.com/UFISH-Team/U-Probe/tags>

verdict: warn
Tags as of today
garnet diff uprobe 0.1.2 0.1.3
uprobe 0.1.2 → 0.1.3: WARN
  WARN  GRN-C001 new-pth-file: 0.1.3 adds code that runs at every Python startup (.pth)
           - new uprobe-setup.pth (881 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…

verdict: warn

What this replay could not see

The project tagged its own v0.1.3 three months later, on a commit from September 2026, so against today's tags GRN-T004 would miss this release; the replay reads the tags as of June 2026 and reports it. GRN-T001 cannot fire: no earlier attestations.

dreamgen

dreamgen 1.8.0 → 1.8.1 warn

PyPI · June 7, 2026 · MAL-2026-5313

Hades campaign: the same <name>-setup.pth payload. The linked repository is no longer public, so GRN-T004 reports GRN-E001.

garnet diff dreamgen 1.8.0 1.8.1
dreamgen 1.8.0 → 1.8.1: WARN
  WARN  GRN-C001 new-pth-file: 1.8.1 adds code that runs at every Python startup (.pth)
           - new dreamgen-setup.pth (1,315 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-E001 fetch-failed: Couldn't fully check 1.8.1: tag lookup: tag lookup for github.com/Agentic-Insights/dreamgen returned HTTP 401
           - tag lookup: tag lookup for github.com/Agentic-Insights/dreamgen returned HTTP 401
           - not evaluated: GRN-T004
  WARN  GRN-E001 fetch-failed [in 1.8.0]: Couldn't fully check 1.8.0: tag lookup: tag lookup for github.com/Agentic-Insights/dreamgen returned HTTP 401
           - tag lookup: tag lookup for github.com/Agentic-Insights/dreamgen returned HTTP 401
           - not evaluated: GRN-T004

verdict: warn

What this replay could not see

The linked repository is no longer public, so the tag lookup fails and GRN-E001 says so instead of passing. GRN-T001 cannot fire: no earlier attestations.

mem8

mem8 6.0.0 → 6.0.1 warn

PyPI · June 7, 2026 · MAL-2026-5319

Hades campaign: the same <name>-setup.pth payload.

garnet diff mem8 6.0.0 6.0.1
mem8 6.0.0 → 6.0.1: WARN
  WARN  GRN-C001 new-pth-file: 6.0.1 adds code that runs at every Python startup (.pth)
           - new mem8-setup.pth (1,315 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/killerapp/mem8 has a tag for 6.0.0 but none for 6.0.1
           - 6.0.0 → tag v6.0.0 at 6eea0644569cfdeaf3773e785c34647fa67d911d <https://github.com/killerapp/mem8/tree/v6.0.0>
           - 6.0.1: no tag named 6.0.1, v6.0.1, mem8-6.0.1 or mem8-v6.0.1 in github.com/killerapp/mem8 <https://github.com/killerapp/mem8/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

ensmallen

ensmallen 0.8.100 → 0.8.101 warn

PyPI · June 8, 2026 · MAL-2026-5315

Hades campaign: the same <name>-setup.pth payload. The package metadata links no repository. 0.8.100 ships platform wheels only; 0.8.101 is a pure-Python wheel.

garnet diff ensmallen 0.8.100 0.8.101
ensmallen 0.8.100 → 0.8.101: WARN
  WARN  GRN-C001 new-pth-file: 0.8.101 adds code that runs at every Python startup (.pth)
           - new ensmallen-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
           - compared wheels with different tags: cp37-abi3-manylinux2014_x86_64.cp37-abi3-manylinux_2_17_x86_64 vs py3-none-any
  WARN  GRN-E003 budget-exceeded: Couldn't fully check 0.8.101: ensmallen-0.8.101-py3-none-any.whl: ensmallen/datasets/string.py is larger than the parse limit
           - ensmallen-0.8.101-py3-none-any.whl: ensmallen/datasets/string.py is larger than the parse limit
           - not evaluated: GRN-C003
  WARN  GRN-E003 budget-exceeded: Couldn't fully check 0.8.101: ensmallen-0.8.101-py3-none-any.whl: entry 'ensmallen/datasets/wikipedia.py' exceeds compression ratio limit
           - ensmallen-0.8.101-py3-none-any.whl: entry 'ensmallen/datasets/wikipedia.py' exceeds compression ratio limit
           - not evaluated: GRN-C001, GRN-C003
  WARN  GRN-E003 budget-exceeded [in 0.8.100]: Couldn't fully check 0.8.100: ensmallen-0.8.100-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl: ensmallen/datasets/string.py is larger than the parse limit
           - ensmallen-0.8.100-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl: ensmallen/datasets/string.py is larger than the parse limit
           - not evaluated: GRN-C003
  WARN  GRN-E003 budget-exceeded [in 0.8.100]: Couldn't fully check 0.8.100: ensmallen-0.8.100-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl: entry 'ensmallen/datasets/wikipedia.py' exceeds compression ratio limit
           - ensmallen-0.8.100-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl: entry 'ensmallen/datasets/wikipedia.py' exceeds compression ratio limit
           - not evaluated: GRN-C001, GRN-C003

verdict: warn

What this replay could not see

The package metadata links no repository, so GRN-T004 had nowhere to look. GRN-T001 cannot fire: no earlier attestations. The new .pth file carries the result on its own. GRN-E003 reports ensmallen/datasets/string.py as larger than the parser's limit, as something it could not read rather than a pass. 0.8.100 shipped platform wheels only and 0.8.101 a pure-Python wheel.

phenopacket-store-toolkit

phenopacket-store-toolkit 0.1.6 → 0.1.7 warn

PyPI · June 8, 2026 · MAL-2026-5322

Hades campaign: the same <name>-setup.pth payload.

garnet diff phenopacket-store-toolkit 0.1.6 0.1.7
phenopacket-store-toolkit 0.1.6 → 0.1.7: WARN
  WARN  GRN-C001 new-pth-file: 0.1.7 adds code that runs at every Python startup (.pth)
           - new phenopacket_store_toolkit-setup.pth (1,407 bytes, 1 executable line(s)): import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u,pla…
  WARN  GRN-T004 no-matching-tag: github.com/monarch-initiative/phenopacket-store-toolkit has a tag for 0.1.6 but none for 0.1.7
           - 0.1.6 → tag v0.1.6 at 7024331c149c5d4fd1aa69c313294b50fe2322fb <https://github.com/monarch-initiative/phenopacket-store-toolkit/tree/v0.1.6>
           - 0.1.7: no tag named 0.1.7, v0.1.7, phenopacket-store-toolkit-0.1.7 or phenopacket-store-toolkit-v0.1.7 in github.com/monarch-initiative/phenopacket-store-toolkit <https://github.com/monarch-initiative/phenopacket-store-toolkit/tags>

verdict: warn

What this replay could not see

GRN-T001 cannot fire: no earlier attestations. The repository has no tag for this version today, and we found no record of one on the day, so GRN-T004 reports it as well as the .pth file.

mrmustard

mrmustard 0.7.3 → 0.7.4 warn

PyPI · July 23, 2026 · MAL-2026-11049

An attacker took over a maintainer's GitHub account on 2026-07-23, had the project's CI leak its PyPI token, and uploaded 0.7.4; found 2026-07-24. mrmustard/__init__.py starts a thread at import that reads SSH keys and cloud credentials and installs a .pth, a cron job and a shell hook. No v0.7.4 tag exists (StepSecurity), and the package metadata links no repository, so GRN-T004 cannot look for one.

garnet diff mrmustard 0.7.3 0.7.4
mrmustard 0.7.3 → 0.7.4: WARN
  WARN  GRN-C003 new-import-time-exec: 0.7.4 adds import-time calls to sensitive APIs in 1 module(s)
           - mrmustard/__init__.py:88 calls subprocess.check_output (in _c(), reached at import)
           - mrmustard/__init__.py:121 calls urllib.request.urlopen (in _eip(), reached at import)
           - mrmustard/__init__.py:122 calls urllib.request.Request (in _eip(), reached at import)
           - mrmustard/__init__.py:160 calls urllib.request.Request (in _run(), reached at import)
           - mrmustard/__init__.py:165 calls urllib.request.urlopen (in _run(), reached at import)
           - mrmustard/__init__.py:227 calls subprocess.check_output (in _run(), reached at import)
           - mrmustard/__init__.py:235 calls subprocess.Popen (in _run(), reached at import)

verdict: warn

What this replay could not see

No v0.7.4 tag exists, but the package metadata links no repository, so GRN-T004 cannot help. GRN-T001 cannot fire: no earlier attestations. The payload is a thread started at import, two calls deep: __init__.py calls a new function, which hands a nested function to threading.Thread.

chalk

chalk 5.6.0 → 5.6.1 warn

npm · September 8, 2025 · MAL-2025-47141

Published from the maintainer's phished npm account on 2025-09-08; index.js gained an obfuscated line that hijacks crypto transactions in browsers. The package had never used provenance.

garnet diff npm:chalk 5.6.0 5.6.1
chalk 5.6.0 → 5.6.1: WARN
  WARN  GRN-T004 no-matching-tag: github.com/chalk/chalk has a tag for 5.6.0 but none for 5.6.1
           - 5.6.0 → tag v5.6.0 at 5c91505e184aff4609c0a7fb235770c2f71db4a3 <https://github.com/chalk/chalk/tree/v5.6.0>
           - 5.6.1: no tag named 5.6.1, v5.6.1, chalk-5.6.1 or chalk-v5.6.1 in github.com/chalk/chalk <https://github.com/chalk/chalk/tags>

verdict: warn

What this replay could not see

npm removes the files of a malicious version along with its registry record, so this replay checks the registry facts the write-ups recorded (no provenance before or after, no install scripts) and reads the repository's tags; the tarball itself is not inspected. The content change, an obfuscated line in index.js, is outside what the replay can see. GRN-T001 cannot fire: chalk never published provenance.

debug

debug 4.4.1 → 4.4.2 warn

npm · September 8, 2025 · MAL-2025-47142

Same account takeover as chalk, minutes earlier; a maintainer noticed that the version on npm was not in the repository.

garnet diff npm:debug 4.4.1 4.4.2
debug 4.4.1 → 4.4.2: WARN
  WARN  GRN-T004 no-matching-tag: github.com/debug-js/debug has a tag for 4.4.1 but none for 4.4.2
           - 4.4.1 → tag 4.4.1 at 33330fa8616b9b33f29f7674747be77266878ba6 <https://github.com/debug-js/debug/tree/4.4.1>
           - 4.4.2: no tag named 4.4.2, v4.4.2, debug-4.4.2 or debug-v4.4.2 in github.com/debug-js/debug <https://github.com/debug-js/debug/tags>

verdict: warn

What this replay could not see

As chalk: registry facts and tags only, and no provenance to lose.

nx

nx 21.4.1 → 21.5.0 warn

npm · August 26, 2025 · MAL-2025-41439

Published with an npm token stolen through a GitHub Actions injection; package.json's postinstall became `node telemetry.js`, a credential stealer that drives local AI CLIs. Earlier nx releases carried provenance; this one had none.

garnet diff npm:nx 21.4.1 21.5.0
nx 21.4.1 → 21.5.0: WARN
  WARN  GRN-C002 new-build-hook: 21.5.0 adds or changes a script that runs when the package is installed
           - package.json: postinstall script changed to node telemetry.js (was node ./bin/post-install || exit 0)
  WARN  GRN-T001 lost-provenance: 1 file(s) in 21.5.0 have no attestation; every file of 21.4.1 had one
           - nx-21.5.0.tgz: no attestation
           - 21.4.1 was attested by github.com/nrwl/nx .github/workflows/publish.yml
  WARN  GRN-T004 no-matching-tag: github.com/nrwl/nx has a tag for 21.4.1 but none for 21.5.0
           - 21.4.1 → tag 21.4.1 at 0f3070161c15cfb0dfbf7f730c257d6322a2235a <https://github.com/nrwl/nx/tree/21.4.1>
           - 21.5.0: no tag named 21.5.0, v21.5.0, nx-21.5.0 or nx-v21.5.0 in github.com/nrwl/nx <https://github.com/nrwl/nx/tags>

verdict: warn

What this replay could not see

Registry facts and tags only; the tarball is not inspected. nx's earlier releases carried provenance from its publish workflow, which is why GRN-T001 fires here. The postinstall change is read from the package.json the registry served.

@ctrl/tinycolor

@ctrl/tinycolor 4.1.0 → 4.1.1 warn

npm · September 15, 2025 · MAL-2025-47138

First package of the Shai-Hulud worm: package.json gained a postinstall running a 3.6 MB bundle.js that steals tokens and republishes itself into every package the token can reach.

garnet diff npm:@ctrl/tinycolor 4.1.0 4.1.1
@ctrl/tinycolor 4.1.0 → 4.1.1: WARN
  WARN  GRN-C002 new-build-hook: 4.1.1 adds or changes a script that runs when the package is installed
           - package.json: new prepare script: npm run build
           - package.json: new postinstall script: node bundle.js
  WARN  GRN-T004 no-matching-tag: github.com/scttcper/tinycolor has a tag for 4.1.0 but none for 4.1.1
           - 4.1.0 → tag v4.1.0 at 2927a9d2aa03e037486a79a295542a7848621691 <https://github.com/scttcper/tinycolor/tree/v4.1.0>
           - 4.1.1: no tag named 4.1.1, v4.1.1, @ctrl/tinycolor-4.1.1 or @ctrl/tinycolor-v4.1.1 in github.com/scttcper/tinycolor <https://github.com/scttcper/tinycolor/tags>

verdict: warn

What this replay could not see

Registry facts and tags only. GRN-T001 cannot fire: the package never published provenance. The new postinstall is the finding; the 3.6 MB bundle.js it runs is not inspected.

Limits, and what we're doing about them

Every limit we know about, with its status. Fixed means a rule on the tool's main branch flags it now and the replay above shows it. Being built means a check is in progress as a draft pull request, measured on the replays and the everyday corpus before it lands. Stays as it is means a limit of the method or of this sample, stated so you can weigh the numbers. The list mirrors the limits in the tool's README and is updated when each change lands.

Fixed

The first run, on the rules as first written, missed or under-reported these. The replay above shows each one flagged, by a rule that was checked against the everyday corpus before it landed.

Being built

One draft pull request per check in the tool repository (pull requests), in the order below; the first, GRN-C008 new-dependency, is open as #68. Each is measured on the 51 replays and the 18,181 everyday updates before it lands; a noisy one ships at info. Until a check lands, the gap it closes is open.

Stays as it is

Limits of the method or of this sample. None changes a result above; each says what a number does and doesn't show.

Everyday updates

The fairer test is how often the rules fire on ordinary releases. The everyday corpus is the last 11 releases of each of the 1,000 most downloaded PyPI packages and the 1,000 most downloaded npm packages, each compared with the release before it, the way a Dependabot or Renovate update would be: 18,181 updates in all, run on 2026-10-30 with the known-bad feed off and tags as they exist today.

366 of 18,181 updates were flagged for a look (2.0%): 220 of 9,439 on PyPI and 146 of 8,742 on npm. Each flag was checked on its own against the registry, GitHub and the release files, and none made a false claim.

"None made a false claim" means every finding states something true about the release. It does not mean no warning was a false alarm: none of these updates is known to be malicious, so from a user's point of view every flag is a true observation about a legitimate release that a reviewer has to look at and dismiss. The numbers to judge are the rate, about 1 in 50, and whether each flag earns its look.

Rule PyPI updates npm updates
GRN-T004 no matching tag 49 89
GRN-T001 lost provenance 18 48
GRN-C003 new import-time exec 65 –
GRN-C002 new build hook 57 3
GRN-T003 workflow changed 16 2
GRN-T002 publisher changed 14 3
GRN-C001 new .pth file 3 –
GRN-T005 outside trusted publishing – 1

Two PyPI updates carry two rules, so that column adds up to 222. What the flags found is ordinary: a maintainer who published by hand after releases with provenance, a monorepo that tags by folder, a library that loads native code with ctypes at import, a postinstall that downloads browsers. Every one is listed with its check in the reports: PyPI, top 1,000 and npm, top 1,000.

The first pass found 30 false claims, all fixed before the final run. Nine PyPI and seven npm releases were tagged in forms the tag check didn't read (release-v1.0.0, prebuilt==1.0.11, utils/v2.8.1), the import-time rule misread a local name as a module and a module's own function as a builtin, and one npm finding said "no attestation" where the registry listed one it could not serve. Each fix is described in the reports, with the re-run that confirmed nothing else moved.

Separately, 291 PyPI updates and 30 npm updates carried a couldn't-check notice (a file too large to read, an archive with a link in it, a repository that answers 401, Python 2 code), which names what wasn't checked and never counts as a pass. The earlier, smaller runs (12 of 974 on the top 100 PyPI packages, 33 of 1,809 on the top 200 npm packages) are superseded by these.

Files and sources

Malicious files are not hosted here. The PyPI replay fetches them from files.pythonhosted.org by URL and SHA-256, listed in backtest/incidents.toml, with hashes from the PyPI snapshots in DataDog's malicious-software-packages-dataset and from the package_integrity records in the OSV advisories. For the 21 releases with no PyPI snapshot, the upload day comes from the advisory and the wheel's own timestamps. The npm replays use the registry facts recorded in backtest/npm-incidents.toml from the public write-ups cited there. Advisories are from OSV. The everyday-update reports and their triage files are in the tool repository under backtest/. Reproduce the replay with uv run python -m backtest incidents and the everyday run with uv run python -m backtest corpus pypi 1000 and corpus npm 1000.

Replay output: 2026-10-30.txt (uv run python -m backtest incidents, garnet-guard main at 42547f6, cloud container, 2026-10-30)