Nothing is published to PyPI yet. The comment on the right is what the first release posts for the real lightning 2.6.2 compromise, taken from the replay below.
github-actionsbot commented
Garnet Guard: 1 update needs a look
⚠ Warning
lightning 2.6.1 → 2.6.2
GRN-C003 2.6.2 adds import-time calls to sensitive APIs: lightning/__init__.py:14 calls subprocess.Popen (in _run_runtime(), reached at import).
GRN-T004github.com/Lightning-AI/pytorch-lightning has a tag for 2.6.1 but none for 2.6.2.
Garnet Guard · what is this? · GRN-C003 · GRN-T004
Rendered the way GitHub shows it. A finding is amber because it is a reason to look, not a verdict.
Replay results
Replayed 2026 compromises flagged
3 of 3from content and tags alone
Routine updates that warned
17 of 9741.7% across the top 100 PyPI packages; 5 were wrong
Package code executed
NoneGarnet reads releases as bytes
Replayed on 2026-10-10 with uv run python -m backtest. The routine set is the last 11 releases of each of the top 100 PyPI packages. Of the 17 warnings, 13 pointed at a real change that turned out benign and 5 were wrong; every one is read and written up in the triage file. The 20 clean updates around the incidents all passed. Full incident replay output.
Replays
Each replay downloads the release a project trusted and the compromised release that followed, checks both against the hashes PyPI published, and diffs them. Nothing is run. This is the actual output.
Uploaded with a stolen token. A thread started in lightning/__init__.py launches a Bun-based credential stealer the moment anything imports the package. Install-script blocking does not help here, because the payload runs at import, not at install.
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: github.com/Lightning-AI/pytorch-lightning has a tag for 2.6.1 but none for 2.6.2
- 2.6.1 → tag 2.6.1 at 655a3a91828694b9bfc241177f474741e483aeaa
- 2.6.2: no tag named 2.6.2, v2.6.2, lightning-2.6.2 or lightning-v2.6.2
verdict: warn
What this replay could not see
GRN-T001 (lost provenance) cannot fire here: lightning did not publish attestations before the incident, so there was no provenance to lose. GRN-T004 is evaluated against the repository's tags as they exist today, not as they stood on April 30.
A second upload of the same payload minutes later, with no git tag and no workflow run. Garnet compares it with 2.6.1, the last release the project would have trusted.
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: github.com/Lightning-AI/pytorch-lightning has a tag for 2.6.1 but none for 2.6.3
- 2.6.1 → tag 2.6.1 at 655a3a91828694b9bfc241177f474741e483aeaa
- 2.6.3: no tag named 2.6.3, v2.6.3, lightning-2.6.3 or lightning-v2.6.3
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 tags as they exist today.
Published by the project's own CI after a GitHub Actions script injection, so the upload path looked normal. The release adds elementary.pth, which runs a base64 payload every time Python starts, for every program on the machine.
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"); …
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
- 0.23.3: no tag named 0.23.3, v0.23.3, elementary-data-0.23.3 or elementary-data-v0.23.3
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 signal is in the content: the new .pth file. GRN-T004 is evaluated against tags as they exist today.
20 clean updates pass
Routine releases of popular packages, including the clean releases on either side of each incident.
A reviewer that warns on everything gets ignored. These updates produced no warning from a detection rule.
The attacks that hurt in 2025 and 2026 were not new packages. They were new versions of packages people already trusted, and each one looked different from the last good release in ways a machine can check.
T
Trust downgrade
The new release has no provenance attestation when the old one did, was built by a different repository or workflow, or has no matching git tag.
Never runs the code it inspects. Releases are read as bytes and parsed with ast.
“Couldn't check” is never “pass”. A fetch or parse that fails is reported as a finding.
Quiet on clean updates. Passing packages collapse to one line in the comment.
Explainable. Every finding names a rule and its evidence, and garnet explain says what to do.
What the replayed rules say
From garnet explain. Every rule has a page at /rules/GRN-XXXX.
GRN-C001 new-pth-file
Python runs import lines in .pth files in site-packages every time the interpreter starts, for every program, before any of your code. Almost no package needs a new one. What to do: read the .pth file. If it is a legitimate path hook (editable installs, namespace helpers) the release notes usually say so; otherwise treat the release as compromised.
GRN-C003 new-import-time-exec
Module top-level code runs the moment anything imports the package, including your test suite and CI. What to do: read the flagged lines. Network or subprocess calls at import are rare in libraries; ask whether the release notes explain them.
GRN-T001 lost-provenance
The trusted version was published by CI with a Sigstore attestation proving which repository and workflow built it. A new file with no attestation was most likely uploaded some other way, for example with a stolen or leaked API token. What to do: don't merge yet. Check the project's repository for a release workflow run and a tag for this version.
GRN-T004 no-matching-tag
Projects that tag releases normally tag every release. A version on the registry with no tag was probably not built from the repository's history, which is typical of a token-based upload. What to do: check the repository's tags and releases page. A missing tag can be an oversight, so confirm with the maintainers before merging.
Try it on your own wheels planned for Nov 22
Drop in the release you trust and the one that just arrived. The content rules run in your browser. Nothing is uploaded and nothing is executed.
Loads the engine (about 6 MB) only when you click. Trust rules need GitHub and Sigstore, which a page like this cannot reach, so they report as “couldn't check here”, never as pass.
garnet diff requests 2.32.4 2.32.5 (in your browser)
requests 2.32.4 → 2.32.5: passok GRN-C001 new-pth-file: no new .pth files
ok GRN-C002 new-build-hook: no new build hooks
ok GRN-C003 new-import-time-exec: no new import-time calls to sensitive APIs
skip GRN-T001 lost-provenance: couldn't check here (needs PyPI attestations)skip GRN-T004 no-matching-tag: couldn't check here (needs GitHub)
verdict: pass(content rules only)
Get started
Garnet Guard is pre-alpha. You can run the prototype from source today; the PyPI package and the GitHub Action come with the first release.
1 Today, from source
recording
$ git clone https://github.com/orca-solutions/garnet-guard
$ cd garnet-guard && uv sync
$ uv run garnet diff lightning 2.6.1 2.6.2
$ uv run python -m backtest # replay every incident
This block becomes the terminal recording when it is ready.
2 First release: one command planned
uvx garnet-guard check --git-base origin/main
Compares your lockfile with the one on main. Works with uv.lock, poetry.lock, pylock.toml and pinned requirements.txt.
npm support follows Python. Agent hooks for Claude Code and Codex, which stop an agent from quietly adding a risky dependency, are on the roadmap.
Open, and staying that way
Apache-2.0. The whole engine and every rule.
No telemetry. No account, no API key, nothing phones home. This page loads nothing from third parties either.
Detection is never paywalled. Free for public and private repositories. See OPEN_CORE.md.
Complementary. Keep your cooldown, pip-audit and malware feeds. Garnet answers a different question: is this release built and shaped like the last one you trusted?
Garnet Guard: 1 update needs a look
⚠ Warning
lightning 2.6.1 → 2.6.2
GRN-C0032.6.2 adds import-time calls to sensitive APIs:lightning/__init__.py:14callssubprocess.Popen(in_run_runtime(), reached at import).GRN-T004github.com/Lightning-AI/pytorch-lightninghas a tag for 2.6.1 but none for 2.6.2.Garnet Guard · what is this? · GRN-C003 · GRN-T004