Supply chain integrity
Attestd monitors selected PyPI and npm packages for malicious publishes alongside CVE-based risk. The supply_chain object in a /v1/check response is independent from risk_state (which reflects NVD-derived vulnerability data only).
Supply chain compromise is deterministic: a package version either has a known malicious publish or it does not. Signals come from four sources: the Attestd registry (human-verified), OSV malicious-package advisories, PyPI yanks with security annotations, and npm deprecation messages with targeted attack language. This means you can block deployment before running code from a compromised dependency.
Quick start
Check a PyPI package for supply chain compromise. LiteLLM 1.82.7 is a confirmed malicious publish — use it to test your integration end-to-end:
# Check a PyPI package for supply chain compromise
curl "https://api.attestd.io/v1/check?product=litellm&version=1.82.7" \
-H "Authorization: Bearer YOUR_API_KEY"npm packages use the same endpoint. URL-encode scoped names (@scope/name becomes %40scope%2Fname):
# Check an npm package for supply chain compromise
# Scoped package names must be URL-encoded (@scope/name -> %40scope%2Fname)
curl "https://api.attestd.io/v1/check?product=%40bitwarden%2Fcli&version=2026.4.0" \
-H "Authorization: Bearer YOUR_API_KEY"A safe package returns compromised: false with an empty sources array.
Example responses
Safe version
{
"product": "langchain",
"version": "0.3.0",
"supported": true,
"risk_state": "none",
"supply_chain": {
"compromised": false,
"sources": [],
"malware_type": null,
"advisory_url": null,
"compromised_at": null,
"removed_at": null
},
"last_updated": "2026-02-23T18:21:30Z"
}Compromised version
When a malicious publish is detected:
{
"product": "litellm",
"version": "1.82.7",
"supported": true,
"risk_state": "none",
"supply_chain": {
"compromised": true,
"sources": ["osv", "registry"],
"malware_type": "malicious_package",
"description": "Malicious version published by attacker impersonating the litellm maintainer",
"advisory_url": "https://osv.dev/MAL-2024-2961",
"compromised_at": "2024-11-05T10:00:00Z",
"removed_at": "2024-11-05T18:30:00Z"
},
"last_updated": "2026-02-23T18:21:30Z"
}Using the SDKs
Python SDK: check supply chain status in your application or deployment pipeline:
import attestd
client = attestd.Client(api_key="YOUR_API_KEY")
# Check a PyPI package (safe version)
result = client.check("langchain", "0.3.0")
# Check supply chain signal
if result.supply_chain and result.supply_chain.compromised:
print(f"[ALERT] Malicious version detected: {result.product} {result.version}")
print(f"Detected by: {', '.join(result.supply_chain.sources)}")
print(f"Description: {result.supply_chain.description}")
raise SystemExit("Do not deploy - compromised dependency")
if result.supply_chain is None:
print(f"Note: {result.product} is not in supply chain monitoring")
# Still check CVE risk
if result.risk_state in ("critical", "high"):
raise SystemExit(f"CVE risk detected: {result.risk_state}")
print(f"Safe to proceed: {result.product} {result.version}")JavaScript / TypeScript SDK: same check for npm packages:
import { Client } from '@attestd/sdk';
const client = new Client({ apiKey: process.env.ATTESTD_API_KEY });
// Check an npm package
const result = await client.check('@bitwarden/cli', '2026.4.0');
if (result.supplyChain?.compromised) {
console.error('SUPPLY CHAIN ALERT:', result.supplyChain.description);
process.exit(1);
}
console.log(`Safe to proceed: ${result.product} ${result.version}`);Scanning a requirements file
Scan all dependencies in requirements.txt for supply chain compromise before deploying:
# requirements-check.py
import attestd
import re
import sys
def parse_requirements(filename):
"""Parse requirements.txt and return list of (package, version) tuples"""
packages = []
with open(filename) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
# Handle == and >= operators
if '==' in line:
pkg, ver = line.split('==')
packages.append((pkg.strip(), ver.strip()))
elif '>=' in line:
pkg, ver = line.split('>=')
packages.append((pkg.strip(), ver.strip()))
return packages
def check_dependencies(filename, api_key):
"""Check all dependencies for supply chain compromise"""
client = attestd.Client(api_key=api_key)
packages = parse_requirements(filename)
compromised = []
for package, version in packages:
try:
result = client.check(package, version)
# Check supply chain compromise
if result.supply_chain and result.supply_chain.compromised:
compromised.append({
'package': package,
'version': version,
'reason': result.supply_chain.description,
'sources': result.supply_chain.sources
})
# Also check CVE risk
if result.risk_state in ("critical", "high"):
compromised.append({
'package': package,
'version': version,
'reason': f"CVE risk: {result.risk_state}",
'cves': result.cve_ids
})
except attestd.AttestdUnsupportedProductError:
# Not in attestd coverage - not necessarily bad, but alert
print(f"[WARNING] {package} not in attestd coverage")
if compromised:
print("[ERROR] Compromised or risky packages found:")
for item in compromised:
print(f" {item['package']}@{item['version']}: {item['reason']}")
sys.exit(1)
else:
print(f"[OK] All {len(packages)} dependencies passed supply chain check")Run this as part of your CI/CD pipeline with:
python requirements-check.py requirements.txtMonitored packages
52 PyPI and 70 npm packages across AI/ML frameworks (incl. TanStack Router / Start family), JS tooling, web frameworks, databases, auth, cloud SDKs, dev toolchain, and more. The npm list expanded in May 2026 after high-download packages were auto-qualified from OSV malicious-package data (e.g. CVE-2026-45321). Use the exact package name as the product query parameter (URL-encode scoped npm names).
LLM / AI frameworks (17)
- litellm (LiteLLM)
- langchain (LangChain)
- langchain-core (LangChain Core)
- langgraph (LangGraph)
- langgraph-checkpoint (LangGraph Checkpoint)
- autogen-agentchat (AutoGen AgentChat)
- autogen-core (AutoGen Core)
- crewai (CrewAI)
- transformers (HuggingFace Transformers)
- openai (OpenAI SDK)
- anthropic (Anthropic SDK)
- google-generativeai (Google Generative AI)
- cohere (Cohere SDK)
- mistralai (MistralAI SDK)
- together (Together AI)
- ollama (Ollama)
- guardrails-ai (Guardrails AI)
ML training (1)
- pytorch-lightning (PyTorch Lightning)
Data science (5)
- numpy (NumPy)
- pandas (pandas)
- scikit-learn (scikit-learn)
- polars (Polars)
- dask (Dask)
dbt / data observability (7)
- elementary-data (Elementary Data)
- dbt-core (dbt Core)
- dbt-bigquery (dbt BigQuery)
- dbt-snowflake (dbt Snowflake)
- dbt-postgres (dbt Postgres)
- dbt-databricks (dbt Databricks)
- great-expectations (Great Expectations)
Orchestration / workflow (4)
- apache-airflow (Apache Airflow)
- prefect (Prefect)
- dagster (Dagster)
- celery (Celery)
Vector databases (3)
- pinecone (Pinecone)
- qdrant-client (Qdrant Client)
- chromadb (ChromaDB)
Web frameworks (3)
- fastapi (FastAPI)
- django (Django)
- flask (Flask)
Cloud SDKs (3)
- boto3 (boto3 (AWS))
- google-cloud-storage (Google Cloud Storage)
- azure-core (Azure Core SDK)
HTTP / core utilities (7)
- requests (Requests)
- httpx (HTTPX)
- aiohttp (aiohttp)
- uvicorn (Uvicorn)
- sqlalchemy (SQLAlchemy)
- pydantic (Pydantic)
- paramiko (Paramiko)
Dev toolchain (2)
- pytest (pytest)
- setuptools (setuptools)
npm packages (70)
- @langchain/core (LangChain Core (JS))
- langchain (LangChain (JS))
- openai (OpenAI SDK (JS))
- @anthropic-ai/sdk (Anthropic SDK (JS))
- llamaindex (LlamaIndex (JS))
- @huggingface/transformers (Hugging Face Transformers (JS))
- ai (Vercel AI SDK)
- @google/generative-ai (Gemini JS SDK)
- groq-sdk (Groq SDK (JS))
- cohere-ai (Cohere SDK (JS))
- @mistralai/mistralai (Mistral SDK (JS))
- @modelcontextprotocol/sdk (MCP SDK)
- @tanstack/arktype-adapter (TanStack ArkType adapter)
- @tanstack/eslint-plugin-router (TanStack ESLint (router))
- @tanstack/eslint-plugin-start (TanStack ESLint (Start))
- @tanstack/history (TanStack History)
- @tanstack/nitro-v2-vite-plugin (TanStack Nitro Vite plugin)
- @tanstack/react-router (TanStack React Router)
- @tanstack/react-router-devtools (TanStack React Router Devtools)
- @tanstack/react-router-ssr-query (TanStack React Router SSR Query)
- @tanstack/react-start (TanStack React Start)
- @tanstack/react-start-client (TanStack React Start (client))
- @tanstack/react-start-rsc (TanStack React Start (RSC))
- @tanstack/react-start-server (TanStack React Start (server))
- @tanstack/router-cli (TanStack Router CLI)
- @tanstack/router-core (TanStack Router Core)
- @tanstack/router-devtools (TanStack Router Devtools)
- @tanstack/router-devtools-core (TanStack Router Devtools Core)
- @tanstack/router-generator (TanStack Router Generator)
- @tanstack/router-plugin (TanStack Router plugin)
- @tanstack/router-ssr-query-core (TanStack Router SSR Query Core)
- @tanstack/router-utils (TanStack Router Utils)
- @tanstack/router-vite-plugin (TanStack Router Vite plugin)
- @tanstack/solid-router (TanStack Solid Router)
- @tanstack/solid-router-devtools (TanStack Solid Router Devtools)
- @opensearch-project/opensearch (OpenSearch JS client)
- express (Express)
- fastify (Fastify)
- next (Next.js)
- nuxt (Nuxt)
- @nestjs/core (NestJS Core)
- hono (Hono)
- @prisma/client (Prisma Client)
- mongoose (Mongoose)
- typeorm (TypeORM)
- pg (node-postgres (pg))
- drizzle-orm (Drizzle ORM)
- jsonwebtoken (jsonwebtoken)
- bcryptjs (bcryptjs)
- passport (Passport.js)
- helmet (Helmet)
- @bitwarden/cli (Bitwarden CLI)
- @checkmarx/kics (Checkmarx KICS)
- axios (Axios)
- got (Got)
- node-fetch (node-fetch)
- undici (undici)
- ky (Ky)
- @aws-sdk/client-s3 (AWS SDK S3 (JS))
- firebase (Firebase JS SDK)
- @google-cloud/storage (Google Cloud Storage (JS))
- @azure/storage-blob (Azure Blob Storage (JS))
- typescript (TypeScript)
- eslint (ESLint)
- prettier (Prettier)
- jest (Jest)
- vite (Vite)
- socket.io (Socket.IO)
- mqtt (MQTT.js)
- common-tg-service (common-tg-service)
Package-specific examples
Common supply chain checks for packages used in AI and data stacks:
LangChain (AI orchestration)
curl "https://api.attestd.io/v1/check?product=langchain&version=0.3.0" \
-H "Authorization: Bearer YOUR_API_KEY"Requests (HTTP dependency)
curl "https://api.attestd.io/v1/check?product=requests&version=2.31.0" \
-H "Authorization: Bearer YOUR_API_KEY"NumPy (data science)
curl "https://api.attestd.io/v1/check?product=numpy&version=1.24.0" \
-H "Authorization: Bearer YOUR_API_KEY"FastAPI (web framework)
curl "https://api.attestd.io/v1/check?product=fastapi&version=0.115.0" \
-H "Authorization: Bearer YOUR_API_KEY"Sources
- registry (Manually curated YAML in Attestd's repo; fastest human-verified signal (confidence 1.0 when confirming a compromise).
- osv (OSV.dev malicious-packages advisories with IDs prefixed
MAL-(confidence 0.95). - pypi_yank Versions yanked on PyPI with a security-related
yanked_reasonheuristic (confidence 0.80). - npm_deprecation npm versions with deprecation messages containing targeted attack language such as
malicious,backdoor, orcompromised. Generic maintenance notices are filtered out (confidence 0.80).
The sources array lists which sources flagged the version (e.g. ["osv", "registry"] when both agree).
Understanding the response
supply_chain: null
Product is not in supply chain monitoring. For example, infrastructure packages (nginx, postgres) or unsupported PyPI packages. This is not a safety signal. Check risk_state for CVE status.
compromised: false, sources: []
Package is monitored and no malicious publish was found at the last ingestion. Safe to use. The last_updated field shows when monitoring last ran; check this if you need the absolute latest data.
compromised: true
A malicious publish has been confirmed. Block deployment immediately. Do not auto-upgrade. Treat as an active incident. The compromised_at and removed_at timestamps indicate when the malicious version appeared and when it was pulled from the registry.
sources
Which detection mechanisms flagged the compromise. Multiple sources (e.g., ["registry", "osv"]) mean independent confirmations. A single source like ["pypi_yank"] means the version was yanked with a security annotation.
risk_state vs supply_chain
These are independent signals. A package can have risk_state: "none" (no CVEs) but supply_chain.compromised: true (malicious publish). Always check both before deploying.
See also Response Field Reference, CI/CD Integration, Account & Portal (for scoped keys), the Python SDK, and the JavaScript SDK (SupplyChainSignal on RiskResult).