惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
L
LangChain Blog
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Engineering at Meta
Engineering at Meta
P
Privacy International News Feed
I
Intezer
NISL@THU
NISL@THU
Jina AI
Jina AI
G
GRAHAM CLULEY
C
CERT Recently Published Vulnerability Notes
S
Schneier on Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cisco Talos Blog
Cisco Talos Blog
Scott Helme
Scott Helme
MyScale Blog
MyScale Blog
IT之家
IT之家
Security Latest
Security Latest
C
Cisco Blogs
Cyberwarzone
Cyberwarzone
aimingoo的专栏
aimingoo的专栏
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
Recorded Future
Recorded Future
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
月光博客
月光博客
A
Arctic Wolf
云风的 BLOG
云风的 BLOG
N
Netflix TechBlog - Medium
K
Kaspersky official blog
S
Securelist
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
Simon Willison's Weblog
Simon Willison's Weblog
Know Your Adversary
Know Your Adversary
WordPress大学
WordPress大学
Project Zero
Project Zero
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
News and Events Feed by Topic
AWS News Blog
AWS News Blog
T
The Exploit Database - CXSecurity.com
T
The Blog of Author Tim Ferriss

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Malicious Release of elementary-data PyPI Package Steals Cloud Credentials from Data Engineers
SnykSec · 2026-04-29 · via DEV Community

A Python package on PyPI called elementary-data, with over 1 million downloads per month, has suffered a supply chain security attack sourced through a GitHub Actions attack vector.

TL;DR

Advisory SNYK-PYTHON-ELEMENTARYDATA-16316110
Severity Critical (CVSS v4.0: 9.3)
Affected package elementary-data==0.23.3
Clean versions All versions except 0.23.3; upgrade to 0.23.4
Attack type Supply chain (GitHub Actions CI/CD injection, then credential-stealing package)
Stolen credentials dbt profiles, Snowflake/BigQuery/Redshift creds, AWS/GCP/Azure keys, API tokens, SSH keys, .env files
Scope The PyPI CLI package and a Docker Image got compromised; Elementary Cloud and the Elementary dbt package were not affected
Detection marker $TMPDIR/.trinny-security-update (Linux/macOS), %TEMP%\.trinny-security-update (Windows)
Disclosure April 25–26, 2026

What is elementary-data?

elementary-data is a dbt-native data observability CLI tool used by data and analytics engineers to monitor pipeline health, detect anomalies, and track test failures across data warehouses like Snowflake, BigQuery, Redshift, and Databricks. The package sees roughly 280,000 downloads per week and over 1.1 million per month, placing it firmly in the tier of widely adopted data tooling.

The package provides integrations with most major cloud data platforms, which is precisely what made it an attractive target. A tool that routinely handles connections to Snowflake, BigQuery, and AWS at CI/CD runtime is one that sits next to a lot of valuable credentials.

How the attack unfolded

The compromise occurred in two stages: first, compromising the publication pipeline; second, publishing malicious content that steals further credentials. It’s worth noting that this security incident for the elementary-data package compromise is one of the most prominent vectors recently exploited by TeamPCP and other threat actors.

Stage 1: GitHub actions script injection

On April 24, 2026, at 22:10 UTC, an attacker using a two-day-old GitHub account (realtungtungtungsahur) posted a crafted comment on PR #2147 in the elementary-data repository. The comment exploited a script injection flaw in .github/workflows/update_pylon_issue.yml, a workflow that handled issue/PR comment events.

The vulnerable run: block directly interpolated ${{ github.event.comment.body }} into a shell script before bash parsing occurred. Because this expression is expanded at workflow-template time rather than sanitized as a string argument, injecting shell metacharacters or subcommands through the comment body allows arbitrary code execution in the runner. When the workflow triggered, the attacker's payload ran with the repository's GITHUB_TOKEN in scope.

Critically, the attacker never needed direct write access to the repository. The GITHUB_TOKEN available to the runner had sufficient permissions to create commits, push tags, and dispatch other workflows. The injected handle_comment job stayed active for two hours and forty-six minutes, giving the attacker an extended window to set up each subsequent stage.

Using the stolen token, the attacker forged a release commit with hash b1e4b1f3aad0d489ab0e9208031c67402bbb8480. The commit was structured to look automated and official: it was an orphan commit (unreachable from any branch), authored as github-actions[bot], carried a forged "Verified" PGP signature, and used the message release/v0.23.2 (#2188) — copied verbatim from a legitimate PR merged nine days earlier. The attacker tagged this orphan v0.23.3 and then dispatched the repository's own Release package workflow with tag=v0.23.3 as input. That workflow's checkout step used ref: ${{ inputs.tag || github.ref }}, so it was built directly from the malicious orphan commit without touching master. The legitimate CI/CD pipeline packaged and published the malicious code. By 22:20 UTC, elementary-data==0.23.3 was live on PyPI. A compromised Docker image (ghcr.io/elementary-data/elementary:0.23.3 and :latest, digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255) followed four minutes later.

This attack vector has appeared repeatedly in the PyPI ecosystem. The Ultralytics supply chain attack in December 2024 used the same pull_request_target injection pattern to steal credentials and publish four malicious versions. The LiteLLM compromise in early 2026 took a slightly different path (a poisoned third-party GitHub Action), but the destination was identical: stolen PyPI tokens used to publish a credential-stealing package.

Stage 2: The malicious package

The attacker embedded the malicious payload in a file named elementary.pth, included in the package's site-packages directory.

.pth files are Python path configuration files that site.py, Python's startup module, processes automatically when the interpreter launches. Any line in a .pth file that begins with import is executed as Python code at interpreter startup, before your own code runs. This means the malware activates any time Python starts on the affected system, including during pip install operations, not just when a user explicitly imports elementary.

This technique was also used in the LiteLLM v1.82.8 compromise. It is more persistent and harder to detect than embedding malicious code in __init__.py because it does not require the victim to import the poisoned package. Installing it is sufficient.

Inside the payload: What the malware did

The embedded code in elementary.pth was a credential stealer with three stages of encryption: a base64 outer wrapper, then XOR encryption keyed from an MD5 keystream (seed: swabag), then a second XOR decryption layer. The obfuscation is not sophisticated by modern malware standards, but it is deliberate — it prevents trivial string-based detection of the payload and increases the time required to analyze what the package is actually doing.

Once Python started on an affected machine, the decoded payload:

  1. Harvested credentials and secrets across the filesystem, targeting a broad set of material:
  • dbt profiles (~/.dbt/profiles.yml) and data warehouse credentials (Snowflake, BigQuery, Redshift, Databricks).
  • Cloud provider credentials: AWS ~/.aws/credentials plus live role credentials fetched from the IMDSv2 metadata endpoint, with direct SigV4-signed calls to AWS Secrets Manager and SSM Parameter Store; GCP application_default_credentials.json; Azure ~/.azure/ directories.
  • SSH private keys (id_rsa, id_ed25519, ~/.git-credentials).
  • Container and orchestration secrets: ~/.docker/config.json, ~/.kube/config, all /etc/kubernetes/*.conf files, Kubernetes ServiceAccount tokens.
  • Package manager credentials: ~/.npmrc, ~/.pypirc, ~/.cargo/credentials.toml.
  • Other secrets at rest: .env* files (scanning up to six directory levels deep), ~/.vault-token, ~/.netrc, ~/.pgpass, ~/.my.cnf, API tokens in environment variables.
  • Cryptocurrency wallet files (Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple, Ethereum, Cardano, Solana validator keypairs).
  • System files: /etc/passwd, /etc/shadow, shell history files, /var/log/auth.log.
  1. Packed all collected material into an archive named trin.tar.gz, then exfiltrated it via curl --data-binary to the C2 server at igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud, using the HTTP header X-Rise-To-The-Trinny: agree.

  2. Left a marker file at $TMPDIR/.trinny-security-update (Linux/macOS) or %TEMP%\.trinny-security-update (Windows), indicating the malware executed at least once.

The scope of credentials goes well beyond dbt and data warehouses. The payload is broadly written to sweep whatever secrets are accessible on the machine, including Kubernetes clusters, infrastructure secrets managers, and cryptocurrency keys. The dbt and warehouse targeting make it relevant to the tool's user base, but anyone running this on a developer machine or CI runner stands to lose considerably more.

The credential profile is well-matched to the tool's typical users. Data engineers running the elementary-data CLI are almost certainly using it against a connected data warehouse, with cloud provider credentials, often in a CI/CD environment where those credentials are stored as secrets or environment variables. This is a targeted attack, not a generic spray.

Impact and scope

The attack window ran from April 24, 22:20 UTC (when the package appeared on PyPI) until the package was removed on April 25, between 8:51 and 11:51 UTC, after community members flagged the issue at 6:18 UTC. That is roughly eight to ten hours of exposure.

Anyone to whom the following applies should assume the malware has executed and that their credentials have been exfiltrated:

  • ran pip install elementary-data or upgraded during that window,
  • used a Docker image pulled from the elementary-data registry between April 24, 22:24 UTC and removal,
  • or had a CI/CD pipeline that automatically pulled the latest version

Elementary Cloud and the Elementary dbt package were not affected, and no other CLI versions contained the malicious code.

Detection: Are You Affected?

Step 1: Check your installed version

pip show elementary-data

Enter fullscreen mode Exit fullscreen mode

If the output shows Version: 0.23.3, your environment was exposed.

Step 2: Check for the execution marker

The malware writes a marker file on execution:

# Linux / macOS (checks $TMPDIR, which defaults to /tmp if unset)
ls -la "${TMPDIR:-/tmp}/.trinny-security-update"

# Windows (PowerShell)
Test-Path "$env:TEMP\.trinny-security-update"

Enter fullscreen mode Exit fullscreen mode

The presence of this file means the credential-stealing code ran in that environment. Its absence does not guarantee safety; the malware may not have written the marker in all execution paths, or the temp directory may have been cleared.

Step 3: Review with Snyk

To check your Python dependencies for this and other known malicious or vulnerable packages:

# Scan your project dependencies
snyk test --file=requirements.txt

# Or for pip-based environments
pip freeze > requirements_check.txt && snyk test --file=requirements_check.txt

Enter fullscreen mode Exit fullscreen mode

Snyk's vulnerability database includes SNYK-PYTHON-ELEMENTARYDATA-16316110 and will flag any environment still pinned to 0.23.3.

Note: We recommend you review our Python security best practices cheat sheet and write-up on Best practices for containerizing Python applications with Docker to practice secure development guidelines.

Remediation

1. Upgrade immediately

pip install --upgrade elementary-data

Enter fullscreen mode Exit fullscreen mode

Version 0.23.4 was published on April 25, 2026, and contains no malicious code.

If you are using a requirements.txt or pyproject.toml, update the pin:

elementary-data>=0.23.4

Enter fullscreen mode Exit fullscreen mode

2. Rotate all credentials that may have been exposed

Treat any credentials accessible to Python processes on affected machines as compromised. Specifically:

  • dbt profiles: Rotate warehouse passwords and OAuth tokens in ~/.dbt/profiles.yml.
  • Cloud provider keys: Rotate or revoke AWS IAM keys (and check Secrets Manager and SSM Parameter Store for accessed values), GCP service account keys, Azure service principals.
  • Kubernetes: Rotate ServiceAccount tokens, and audit any /etc/kubernetes/*.conf files that were accessible.
  • Container registries: Rotate credentials stored in ~/.docker/config.json.
  • Package manager tokens: Rotate ~/.npmrc, ~/.pypirc, ~/.cargo/credentials.toml tokens.
  • Secrets managers: Rotate HashiCorp Vault tokens (~/.vault-token) and any .netrc, .pgpass, or .my.cnf credentials.
  • SSH keys: If private keys were present on the machine, consider them exposed and rotate them.
  • CI/CD secrets: If the affected machine was a CI runner, rotate all secrets stored in that environment.

Rotation alone is not sufficient. Review access logs for the exfiltration domain igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud and for any of your services to detect unauthorized access that may have already occurred.

3. Clear Python caches

# Remove any cached .pth files or bytecode
pip cache purge

# Find and remove any elementary.pth artifacts
find / -name "elementary.pth" 2>/dev/null

Enter fullscreen mode Exit fullscreen mode

4. Pull clean Docker images

The compromised image (ghcr.io/elementary-data/elementary:0.23.3 and :latest) carried digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255. The last known-clean image is 0.23.2 at digest sha256:b3bbfafde1a0db3a4d47e70eb0eb2ca19daef4a19410154a71abee567b35d3d9. Pull a clean image built after April 25, 2026:

docker pull ghcr.io/elementary-data/elementary:latest

Enter fullscreen mode Exit fullscreen mode

Verify you are not running a cached copy of the compromised image:

docker images ghcr.io/elementary-data/elementary --digests

Enter fullscreen mode Exit fullscreen mode

5. Audit your GitHub Actions workflows

If you maintain Python packages, this incident is a prompt to audit any workflow that processes issue or PR comment events. The specific pattern to search for is unquoted context expressions interpolated directly into run: blocks:

# Vulnerable: ${{ github.event.comment.body }} expands before bash parses the command
- run: echo "Comment: ${{ github.event.comment.body }}"

# Safe: pass untrusted input through an environment variable
- run: echo "Comment: $COMMENT_BODY"
  env:
    COMMENT_BODY: ${{ github.event.comment.body }}

Enter fullscreen mode Exit fullscreen mode

Snyk's post on GitHub Actions vulnerabilities and the TJ Actions compromise analysis covers the broader patterns to look for.

Beyond sanitizing inputs, the more durable fix is to remove long-lived PyPI API tokens from your workflow secrets entirely. PyPI supports Trusted Publishers, which use short-lived OIDC tokens scoped to a specific workflow on a specific repository — tokens that cannot be exfiltrated and reused. The elementary-data attacker needed a long-lived secret to publish; Trusted Publishers eliminate that attack surface. Also, keep privileged release workflows behind manual approval gates that require human confirmation before the publish step runs.

The repeating pattern

This attack follows a now-familiar playbook: find a gap in a project's GitHub Actions configuration, inject code that steals the PyPI publishing token, use that token to publish a malicious version, embed a .pth file or similar startup hook to maximize reach.

The same pattern appeared in the Ultralytics attack (December 2024, pull_request_target branch injection, cryptocurrency miner), the LiteLLM attack (early 2026, poisoned Trivy action, credential stealer with persistent backdoor), and the Cline/Clinejection incident (AI-assisted prompt injection into Actions, stolen tokens).

The pattern is not novel. The tooling to exploit it is well-understood and appears to be in active, repeated use. For package maintainers, the priority is workflow hardening: restrict the use of pull_request_target, require manual approval for release workflows, use short-lived OIDC tokens for PyPI publishing instead of long-lived API tokens, and implement branch protection rules to prevent unauthorized release triggers.

For elementary-data users, the Elementary team responded quickly: from community report to initial remediation was under four hours, and the team published a full incident report. That response tempo is worth noting alongside the incident itself.