Skip to main content
  1. Posts/

PyPI Packages Keep Getting Compromised — Can Pixi and Rattler's Signing Help?

On March 24, 2026, LiteLLM — a package with 95 million monthly downloads — was backdoored on PyPI. The TeamPCP group compromised the maintainer’s credentials through a chain of CI/CD attacks, published versions 1.82.7 and 1.82.8 with a multi-stage credential stealer, and had three hours before detection. Three hours where every pip install litellm delivered malware that harvested SSH keys, cloud tokens, Kubernetes secrets, and crypto wallets.

This wasn’t an isolated incident. TeamPCP also hit Telnyx the same week. In 2025, 500+ typosquatting packages flooded PyPI in a single campaign. Domain expiration attacks compromised 1,800+ PyPI accounts. A cross-ecosystem worm exfiltrated 3,325 secrets from 817 GitHub repositories, including PyPI publishing tokens.

The question for anyone using pixi and the conda ecosystem: does package signing protect you here? The honest answer: partially, and not yet by default. This blueprint maps the attack surface, the available defenses, the gaps, and a reusable architecture for closing them.


Supply Chain Security Architecture
#

Before diving into specifics, here’s the reference architecture this blueprint proposes. It combines three industry frameworks — SLSA (producing), S2C2F (consuming), and Sigstore (signing) — into a single pipeline applicable to conda/pixi projects:

  ┌─────────────────────────────────────────────────────────────────┐
  │                    PRODUCE (SLSA L3)                             │
  │                                                                 │
  │  Source ──► Build (isolated CI) ──► Provenance ──► Sign         │
  │    │            │                      │              │         │
  │  git commit  rattler-build          in-toto        Sigstore     │
  │  signed      hermetic build         statement      attestation  │
  │  tag/branch  reproducible           SHA-256 hash   Rekor log    │
  └──────────────────────────────────────┬──────────────────────────┘
                                         │ .conda + .sigstore.json
  ┌─────────────────────────────────────────────────────────────────┐
  │                    CHANNEL / REGISTRY                            │
  │                                                                 │
  │  prefix.dev / conda-forge / private channel                     │
  │  Serves: package + attestation + repodata                       │
  └──────────────────────────────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────────────────────────────┐
  │               CONSUME (S2C2F Quarantine Pattern)                │
  │                                                                 │
  │  ┌───────────┐    ┌──────────────┐    ┌───────────────────┐     │
  │  │ INGEST    │───►│ QUARANTINE   │───►│ PROMOTE           │     │
  │  │           │    │              │    │                   │     │
  │  │ pixi add  │    │ Typosquat    │    │ Approved deps    │     │
  │  │ or update │    │ CVE scan     │    │ pinned in        │     │
  │  │           │    │ Attestation  │    │ pixi.lock        │     │
  │  │           │    │ SAST scan    │    │                   │     │
  │  │           │    │ Secret scan  │    │ SBOM generated   │     │
  │  └───────────┘    └──────────────┘    └───────────────────┘     │
  │                         │ FAIL                                  │
  │                         ▼                                       │
  │                   ┌──────────┐                                  │
  │                   │ REJECT   │                                  │
  │                   │ + alert  │                                  │
  │                   └──────────┘                                  │
  └─────────────────────────────────────────────────────────────────┘

  ┌─────────────────────────────────────────────────────────────────┐
  │                 SIGSTORE TRUST CHAIN                             │
  │                                                                 │
  │  CI Identity ──► Fulcio (CA) ──► Ephemeral Cert ──► Sign        │
  │  (GitHub OIDC)   issues cert     bound to OIDC     artifact     │
  │                                  identity                       │
  │                                       │                         │
  │                                       ▼                         │
  │                                  Rekor (Log)                    │
  │                                  transparency                   │
  │                                  immutable record               │
  └─────────────────────────────────────────────────────────────────┘

Framework Mapping
#

FrameworkFocusWhat It CoversPixi/Conda Mapping
SLSA L1-L3Producing artifactsSource → Build → Provenance → Signingrattler-build + Sigstore attestation
S2C2F L1-L4Consuming dependenciesIngest → Inventory → Scan → Enforce → Auditpixi.lock + CVE scan + quarantine
SigstoreCryptographic trustKeyless signing, transparency log, verificationCEP 27 attestations on prefix.dev
NIST SSDFSecure developmentPractices across the SDLCSAST, secret scanning, SBOM

SLSA levels in context: SLSA L1 requires documented provenance. L2 requires a hosted build service. L3 requires the build to be hardened (isolated, hermetic). rattler-build on GitHub Actions with --generate-attestation achieves SLSA L3 for conda packages published to prefix.dev.

S2C2F practices mapped to the pipeline: The eight S2C2F practices — Ingest, Inventory, Update, Enforce, Audit, Scan, Rebuild, Fix — map directly to the quarantine pattern above. pixi add is Ingest. pixi.lock is Inventory. CVE scanning is Scan. Lockfile pinning is Enforce. SBOM generation is Audit.


How Packages Get Compromised
#

Four attack vectors dominate, in order of impact:

1. Account takeover — the dominant vector. Attackers compromise maintainer credentials through phishing (the July 2025 noreply@pypj.org campaign), expired email domain registration (1,800 accounts), or CI/CD credential theft (TeamPCP’s path through Trivy → GitHub Actions → PyPI tokens). Once in, they publish a malicious version of a legitimate, trusted package.

2. Typosquatting and slopsquatting. Register requ3sts or colorinal — close enough to a popular package that someone installs it by mistake. LLMs have made this worse: attackers now use model-hallucinated package names as typosquat targets, a technique called slopsquatting.

3. Dependency confusion. Publish a public package with the same name as a private internal package. If the package manager checks the public registry first, it installs the attacker’s version. 5,000+ copycats flooded PyPI and npm simultaneously in 2025.

4. Malicious updates. Once credentials are compromised (via any of the above), the attacker publishes backdoored versions of a package that thousands of projects already depend on. The trust is inherited.


What Signing Solves — And What It Doesn’t
#

Package signing proves provenance: this artifact was produced by this identity, at this time, through this build pipeline. It’s logged to a transparency log so the chain of custody is auditable.

Signing protects against:

  • Mirror tampering — a compromised CDN or mirror can’t substitute a different artifact
  • Man-in-the-middle — intercepted downloads are detected
  • Build tampering — if Trusted Publishing is used, the package must come from the registered CI pipeline

Signing does not protect against:

  • Compromised maintainer credentials — a signed package is still signed even if the maintainer’s account was hijacked. The signature proves it came from that account, which is exactly the problem
  • Malicious code in legitimate updates — if the maintainer publishes a backdoored version through the normal pipeline, the signature is valid
  • Typosquatting — the attacker signs their own malicious package with their own identity

This nuance matters. Signing is necessary but not sufficient. It needs to be combined with other defenses.


The State of Signing in Conda and Pixi
#

What Exists Today
#

CEP 27 (accepted, July 2025) standardizes the conda attestation format using in-toto Statement v1 with Sigstore bundles. The predicate ties a package filename and SHA-256 hash to a Sigstore-verified identity. This is a real standard, backed by prefix.dev and Trail of Bits.

rattler-build supports Sigstore attestation generation:

rattler-build publish ./recipe.yaml \
  --to https://prefix.dev/my-channel \
  --generate-attestation

This creates a Sigstore bundle using the CI environment’s OIDC identity (GitHub Actions, GitLab, Buildkite, CircleCI, Codefresh), uploads both the package and attestation to prefix.dev. No key management — Sigstore’s keyless signing uses ephemeral keys bound to the CI identity.

pixi publish also supports --generate-attestation (merged in PR #5678).

Verification works today via standard tools:

# cosign
cosign verify-blob \
  --certificate-identity-regexp "https://github.com/my-org/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  my-package-0.1.0-h123_0.conda

# sigstore-python
sigstore verify identity \
  --cert-identity "https://github.com/my-org/my-repo/.github/workflows/build.yml@refs/heads/main" \
  --cert-oidc-issuer "https://token.actions.githubusercontent.com" \
  my-package-0.1.0-h123_0.conda

Source attestation verification (experimental) in rattler-build can verify Sigstore attestations of upstream source tarballs during build:

source:
  url: https://files.pythonhosted.org/packages/.../flask-3.1.1.tar.gz
  sha256: "6489f1..."
  attestation:
    publishers:
      - github:pallets/flask

How PyPI Compares
#

PyPI is further along. PEP 740 (GA since November 2024) standardizes Sigstore-based attestations. Over 20,000 attestations have been published. Trusted Publishing is well-adopted. The gap: pip doesn’t verify attestations by default either — it’s still a manual step, with a plugin architecture in development.


What’s Actually Missing
#

Here’s the honest gap analysis. None of these are criticisms — the ecosystem is actively developing — but they’re the reality today:

1. pixi cannot reject unsigned packages. There is no require-attestation = true config. No pixi setting enforces attestation verification at install time. You can sign packages, but the consumer has no way to require signatures.

2. rattler doesn’t verify at install time. The rattler library (which powers pixi) has no install-time attestation verification. Verification is a separate, manual step after downloading.

3. conda-forge doesn’t sign packages. Despite running builds on GitHub Actions (which has OIDC plumbing for Sigstore), conda-forge has no attestation infrastructure. This is the largest conda channel by far.

4. OSV.dev doesn’t track conda. Zero conda ecosystem support. To check a conda package for CVEs, you must map it to its PyPI or upstream equivalent and query that. Trivy and Grype also lack conda-specific vulnerability databases.

5. CEP #142 (draft) is still open. This CEP would standardize how channels serve .v0.sigs files alongside packages — a prerequisite for automatic verification.

6. No policy engine exists. Nothing combines “is this package signed?” with “does this package have known CVEs?” into a single install-time gate.


Defense in Depth — What You Can Do Today
#

While the toolchain catches up, these measures provide real protection:

Lockfiles are your best friend. pixi.lock pins every transitive dependency to an exact version and hash. If a malicious version is published after you lock, you don’t get it. This is the single most effective defense against supply chain attacks today.

Audit before you update. Don’t blindly pixi update. Review what changed. Check the diff. Look at the changelog.

Use Trusted Publishing for your own packages. If you publish to prefix.dev, use --generate-attestation. Your consumers can’t enforce verification yet, but the attestation is logged to Sigstore’s transparency log — it’s auditable retroactively.

Scan dependencies for CVEs before building. Trivy can scan filesystem paths. OSV.dev has an API. Neither tracks conda natively, but most conda packages have PyPI equivalents you can query.

Run SAST and secret scanning. Semgrep (OWASP rules), Bandit (Python AST), and Gitleaks catch hardcoded credentials and common vulnerability patterns before they ship.

Generate SBOMs. Syft produces CycloneDX SBOMs from images and directories. Required for DORA, NIS2, and EU AI Act compliance. More importantly, it gives you a manifest to audit.


Closing the Gap — An Agentic CVE-Free Builder
#

The architecture above shows what a secure pipeline should look like. The problem: no single tool runs all the steps, and the quarantine gate doesn’t exist natively in pixi or conda. An AI agent can fill that gap — orchestrating the checks as a build-time gate until the package managers add native enforcement.

This is the concept behind cvebuild — a single-file agentic builder that implements the full SLSA + S2C2F + Sigstore pipeline:

  ┌──────────────────────────────────────────────────────────────┐
  │                  cvebuild — Agentic Pipeline                  │
  │                                                              │
  │  ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐  │
  │  │TYPOSQUAT │──►│ THREAT   │──►│ RESOLVE  │──►│ CVE      │  │
  │  │ CHECK    │   │ INTEL    │   │ DEPS     │   │ AUDIT    │  │
  │  │          │   │          │   │          │   │          │  │
  │  │ name     │   │ CISA KEV │   │ conda    │   │ OSV.dev  │  │
  │  │ distance │   │ GHSA     │   │ solve    │   │ pin safe │  │
  │  │ analysis │   │ OSV.dev  │   │ tree     │   │ versions │  │
  │  └──────────┘   └──────────┘   └──────────┘   └──────────┘  │
  │       │              │              │              │         │
  │       ▼ FAIL=HALT    ▼ ALERT        ▼              ▼ PATCH   │
  │                                                              │
  │  ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐  │
  │  │ SAST     │──►│ SECRET   │──►│ BUILD    │──►│POST-BUILD│  │
  │  │ SCAN     │   │ SCAN     │   │          │   │ CVE SCAN │  │
  │  │          │   │          │   │          │   │          │  │
  │  │ Semgrep  │   │ Gitleaks │   │ rattler  │   │ Trivy    │  │
  │  │ OWASP    │   │ regex    │   │ -build   │   │ image/fs │  │
  │  │ Bandit   │   │ fallback │   │          │   │          │  │
  │  └──────────┘   └──────────┘   └──────────┘   └──────────┘  │
  │       │              │              │              │         │
  │       ▼ SURFACE      ▼ HALT         ▼              ▼ ITERATE │
  │                                                              │
  │  ┌──────────┐   ┌──────────┐                                 │
  │  │ SIGN     │──►│ SBOM     │──► Final Report                 │
  │  │          │   │          │    CVEs: 0 CRITICAL, 0 HIGH     │
  │  │ Sigstore │   │ Syft     │    Signed: ✓  SBOM: ✓           │
  │  │ keyless  │   │ CycloneDX│    SLSA L3  S2C2F L3            │
  │  └──────────┘   └──────────┘                                 │
  └──────────────────────────────────────────────────────────────┘

The agent exposes 17 tools to an LLM — from typosquat_check and threat_intel to rattler_build, sign_artifact, and sbom_generate. Each tool is a focused function: CVE scan wraps Trivy, advisory lookup queries OSV.dev, safe version pinning finds the highest patched version, SAST combines Semgrep and Bandit with OWASP category tagging.

The key insight: the agent doesn’t auto-fix code. SAST findings and secret detections are surfaced to the user. The agent handles the security pipeline — the orchestration of checks, the gating of builds, the signing of artifacts — while keeping humans in the loop for code-level decisions.

This approach works because the individual tools (Trivy, Semgrep, Gitleaks, rattler-build, Sigstore) are mature. What’s missing is the orchestration layer that runs them in the right order, feeds findings between steps, and gates the build on security outcomes. An agentic loop fills that gap until the package managers add native enforcement.


Related#

Kevin Keller
Author
Kevin Keller
Personal blog about AI, Observability & Data Sovereignty. Snowflake-related articles explore the art of the possible and are not official Snowflake solutions or endorsed by Snowflake unless explicitly stated. Opinions are my own. Content is meant as educational inspiration, not production guidance.
Share this article

Related