TL;DR

On 2026-04-24, attackers published a malicious elementary-data==0.23.3 to PyPI (uploaded 22:20:47 UTC) and poisoned the matching Docker images on GHCR (ghcr.io/elementary-data/elementary). The package — a popular dbt data-observability tool with ~1M+ monthly downloads — shipped a single weaponized addition: a top-level elementary.pth file. Python auto-execs .pth lines that start with import at every interpreter startup, so the three-stage infostealer fired on any Python invocation in an environment where it was installed. The attacker got in via a GitHub Actions script-injection flaw in the project's own workflow, then used the workflow's GITHUB_TOKEN to forge a signed release commit and dispatch the legitimate publishing pipeline against it. Fixed in 0.23.4; :latest GHCR tag now resolves clean.

What happened

The root cause was a script-injection vulnerability in one of Elementary's own GitHub Actions workflows. The attacker injected commands that ran with the workflow's GITHUB_TOKEN, used it to forge a signed release commit, and then dispatched the project's real publishing pipeline against that commit — so the malicious 0.23.3 was shipped by the legitimate release automation (the same "hijack the real pipeline" shape behind the TanStack/Mini Shai-Hulud provenance abuse, here applied to PyPI + GHCR rather than npm).

Both the wheel and the sdist contained one malicious file: elementary.pth. .pth files in site-packages are a Python packaging mechanism — but Python executes any line beginning with import when the interpreter starts. That turns a "data file" into an auto-run primitive: the payload executes on every python, dbt, pytest, etc., run, with no import of the package required.

The payload is a three-stage information stealer that hunts developer secrets: - Cloud access tokens (AWS / GCP / Azure), SSH private keys, Kubernetes credentials, and cryptocurrency wallets.

The GHCR Docker images were poisoned in lockstep: every unpinned docker pull ghcr.io/elementary-data/elementary and every FROM ghcr.io/elementary-data/elementary without a pinned digest/tag pulled the trojaned image from 2026-04-24 until cleanup. The Elementary team removed 0.23.3 from PyPI and the malicious GHCR image, then published a clean 0.23.4 (and re-pointed :latest).

Am I affected?

You are affected if you installed elementary-data==0.23.3 from PyPI or pulled an unpinned ghcr.io/elementary-data/elementary image after 2026-04-24.

# PyPI side: is the bad version present?
pip show elementary-data 2>/dev/null | grep -i version
pip freeze 2>/dev/null | grep -i '^elementary-data==0.23.3'

# The smoking gun: a .pth file that runs code at interpreter startup
find "$(python -c 'import site; print(site.getsitepackages()[0])')" -name 'elementary.pth' 2>/dev/null -exec cat {} \;

# Docker side: did you pull/build on an unpinned tag after 2026-04-24?
docker images 'ghcr.io/elementary-data/elementary' --format '{{.Repository}}:{{.Tag}} {{.CreatedAt}} {{.Digest}}'
grep -rn 'ghcr.io/elementary-data/elementary' Dockerfile* docker-compose*.yml 2>/dev/null

If elementary.pth exists (or you ran an unpinned image after the window): assume cloud tokens, SSH keys, K8s creds, and any wallet material on that host/CI runner are compromised. Rotate everything and rebuild from a clean, pinned image.

IOCs

Type Value
Malicious PyPI version elementary-data==0.23.3 (uploaded 2026-04-24 22:20:47 UTC)
Malicious container ghcr.io/elementary-data/elementary (unpinned tags, from 2026-04-24)
Execution primitive top-level elementary.pth auto-exec at Python startup
Initial access GitHub Actions script injectionGITHUB_TOKEN → forged signed release → real publish pipeline
Payload 3-stage infostealer: cloud tokens, SSH keys, K8s creds, crypto wallets
Fixed version 0.23.4 (PyPI); clean :latest re-published on GHCR

If you are affected

playbooks/rotating-cloud-credentials.md — cloud tokens, SSH keys, K8s creds first. → playbooks/if-your-github-pat-leaked.md — especially for CI runners that built/ran the image. → Rebuild any image derived from ghcr.io/elementary-data/elementary from a pinned, post-fix digest; redeploy.

Prevention

prevention/package-vetting-checklist.md — flag .pth files in dependencies; they're a known auto-exec vector. → prevention/credential-hygiene.mdPin container images by digest (@sha256:...), not floating tags — a poisoned :latest reaches everyone who pulls. → Harden your own GitHub Actions against script injection: never interpolate untrusted ${{ }} context into run: steps; scan workflows with zizmor; restrict GITHUB_TOKEN to least privilege.

Sources