Skip to main content

attestd

PyPI version

Attestd checks whether a dependency version has exploitable CVEs or a confirmed supply-chain compromise. One API call returns a structured risk response.

Get a free API key · Full docs

Requires Python 3.10+.

Install

pip install attestd

Quick start

import os
import attestd

client = attestd.Client(api_key=os.environ["ATTESTD_API_KEY"])
result = client.check("nginx", "1.20.0")
print(result.risk_state)  # "high"
print(result.cve_ids)     # ["CVE-2021-23017", ...]

Batch check

Check up to 100 packages in one request. Each item costs one API call. A 429 rejects the entire batch before billing.

results = client.batch_check([
    ("litellm", "1.82.7"),
    ("nginx", "1.25.3"),
])
# results[i] is RiskResult | None — None means outside coverage

Async:

results = await client.batch_check([("litellm", "1.82.7"), ("nginx", "1.25.3")])

Per-CVE detail

Default check / batch_check omit include and return compact results (cves is []). Pass include=["cves"] to request CVSS and EPSS per CVE. Compact and detailed responses are cached separately.

detailed = client.check("nginx", "1.20.0", include=["cves"])
print(detailed.cves[0].cvss_score)
print(detailed.cves[0].epss_score)

batch = client.batch_check(
    [("nginx", "1.20.0")],
    include=["cves"],
)

Catalog and quota

Three additional endpoints for product discovery, CVE lookup, and quota monitoring. All require a valid API key.

Products catalog

catalog = client.products()
print(catalog.total)
print(catalog.cve_products[0].slug)
print(catalog.supply_chain_packages[0].package)

Returns ProductsResult with cve_products, supply_chain_packages, and total. Maps to GET /v1/products.

CVE detail

try:
    detail = client.cve("CVE-2021-44228")
    print(detail.cvss_score, detail.epss_score)
except attestd.AttestdAPIError as e:
    if e.status_code == 404:
        print("CVE not in database")

Returns CveDetail with CVSS, EPSS, KEV status, and affected products. Raises AttestdAPIError(status_code=404) when the CVE is not in Attestd's database.

Usage quota

usage = client.usage()
print(usage.tier)
print(usage.key_calls_this_month, "/", usage.included_calls)
print(usage.billing_period_end)

Returns UsageResult with calls used this month, included cap, billing period, and overage estimate. Use for quota monitoring in CI or agent loops.

Supply chain check

Attestd monitors select PyPI and npm packages for known malicious publishes.

result = client.check("litellm", "1.82.7")
if result.supply_chain:
    print(result.supply_chain.compromised)  # True

Error handling

AttestdUnsupportedProductError means the product is outside Attestd coverage. That is unknown risk, not a safety signal.

try:
    result = client.check(product, version)
except attestd.AttestdUnsupportedProductError:
    raise RuntimeError(f"{product} is outside Attestd coverage")

See Outside coverage below for policy options.


Async

import asyncio
import os
import attestd

async def main():
    async with attestd.AsyncClient(api_key=os.environ["ATTESTD_API_KEY"]) as client:
        result = await client.check("log4j", "2.14.1")
        if result.risk_state in ("critical", "high"):
            raise RuntimeError(f"Vulnerable dependency: {result.cve_ids}")

asyncio.run(main())

CI/CD deployment gate

import os
import attestd

DEPENDENCIES = [
    ("nginx", "1.20.0"),
    ("log4j", "2.17.1"),
    ("openssh", "9.2p1"),
]

with attestd.Client(api_key=os.environ["ATTESTD_API_KEY"]) as client:
    for product, version in DEPENDENCIES:
        try:
            result = client.check(product, version)
        except attestd.AttestdUnsupportedProductError:
            continue

        if result.risk_state in ("critical", "high"):
            print(f"BLOCK: {product} {version} ({result.risk_state})")
            print(f"  CVEs: {', '.join(result.cve_ids)}")
            print(f"  Fix:  upgrade to {result.fixed_version}")
            exit(1)

AI agent tool

import os
import attestd

client = attestd.Client(api_key=os.environ["ATTESTD_API_KEY"])

def check_dependency_risk(product: str, version: str) -> dict:
    """Return risk_state, CVE IDs, and fixed version for a dependency."""
    try:
        result = client.check(product, version)
        return {
            "supported": True,
            "risk_state": result.risk_state,
            "actively_exploited": result.actively_exploited,
            "fixed_version": result.fixed_version,
            "cve_ids": result.cve_ids,
        }
    except attestd.AttestdUnsupportedProductError:
        return {"supported": False}

Full error handling

import os
import time
import attestd

with attestd.Client(api_key=os.environ["ATTESTD_API_KEY"]) as client:
    try:
        result = client.check("nginx", "1.20.0")
    except attestd.AttestdUnsupportedProductError:
        pass
    except attestd.AttestdRateLimitError as e:
        time.sleep(e.retry_after or 60)
    except attestd.AttestdAuthError:
        raise
    except attestd.AttestdAPIError as e:
        print(f"API error: {e.status_code}")
    except attestd.AttestdError:
        pass

Outside coverage: not a safety signal

AttestdUnsupportedProductError means Attestd has no vulnerability data for this product, not that the product is free of vulnerabilities. An agent that catches the exception and treats it as safe to proceed is making a dangerous inference.

Recommended handling:

try:
    result = client.check(product, version)
except attestd.AttestdUnsupportedProductError as e:
    raise RuntimeError(
        f"{e.product} is outside Attestd's coverage. "
        "Manual security review required before deploying."
    )

RiskResult fields

Field Type Description
product str Product name
version str Version queried
risk_state str One of critical, high, elevated, low, none
risk_factors list[str] Machine-readable factors (see below)
actively_exploited bool On the CISA KEV list
remote_exploitable bool Remotely exploitable
authentication_required bool True only if ALL CVEs require auth
patch_available bool A fixed version is known
fixed_version str | None Earliest version that resolves all CVEs
confidence float Synthesis confidence (0.0-1.0)
cve_ids list[str] CVE IDs in this assessment
max_epss float | None Highest EPSS probability across matching CVEs
cves list[CveSummary] Per-CVE detail when include=["cves"] was passed; otherwise []
last_updated datetime UTC timestamp of last synthesis run
supply_chain SupplyChainSignal | None PyPI/npm supply chain signal when monitored; None for CVE-only products
typosquat TyposquatSignal | None Present when the package name resembles a known product

TyposquatSignal: detected, resembles, confidence, ecosystem, kind, likely_intended

CveSummary: cve_id, cvss_score, actively_exploited, remote_exploitable, epss_score, epss_percentile

Risk states

State Meaning
critical Actively exploited in the wild (CISA KEV)
high Remote unauthenticated exploitation possible
elevated Remote exploitation requires authentication
low Local-only or low-impact vulnerability
none No known vulnerabilities affecting this version

Risk factors

Factor Meaning
active_exploitation CVE on CISA KEV list
remote_code_execution Remote exploitation possible
no_authentication_required Remote + no auth required
internet_exposed_service Remote + no auth (surface area flag)
patch_available A fix is available

Catalog and quota types

Type Key fields
ProductEntry slug, display_name
SupplyChainEntry package, ecosystem, display_name
ProductsResult cve_products, supply_chain_packages, total
CveSummary cve_id, cvss_score, actively_exploited, remote_exploitable, epss_score, epss_percentile
CveDetail cve_id, description, cvss_score, cvss_vector, actively_exploited, remote_exploitable, authentication_required, affected_products, epss_score, epss_percentile, source_published_at, last_checked_at
UsageResult tier, key_calls_this_month, account_calls_this_month, included_calls, billing_period_start, billing_period_end, overage_calls, estimated_overage_usd

Configuration

client = attestd.Client(
    api_key=os.environ.get("ATTESTD_API_KEY"),
    base_url="https://api.attestd.io",
    timeout=10.0,
    max_retries=3,
    retry_delay=1.0,
    cache_policy="runtime",
)
print(client.stats())
client.invalidate_cache("nginx", "1.20.0")

cache_policy is one of development, runtime, ci, or none. Default is runtime. stats() returns session counters (api_calls_made, cache_hits, batch_saves, calls_saved). invalidate_cache(product, version) drops both the compact and detailed (include=["cves"]) cached results for that product and version.

AsyncClient accepts the same options plus batch_window_ms (default 5). Concurrent check() calls within that window are coalesced into one batch request. Set batch_window_ms=0 to disable coalescing.

Set ATTESTD_API_KEY in your environment from the portal. Then omit api_key in the constructor:

with attestd.Client() as client:
    result = client.check("nginx", "1.20.0")

The SDK retries on transient 5xx responses and connection failures with exponential backoff (1s, 2s, 4s between attempts). Timeouts raise immediately and are not retried. 401 and 429 are surfaced immediately without retry.

Supported products

See attestd.io/docs/products for the full list. Each product page documents the API slug, version format, and notable CVEs. Querying an unsupported product raises AttestdUnsupportedProductError.

Testing your integration

The SDK ships an attestd.testing module with httpx transports for injecting controlled API responses. No local Attestd instance required.

import attestd
from attestd.testing import (
    MockTransport,
    MockAsyncTransport,
    SequentialMockTransport,
    SequentialMockAsyncTransport,
    NGINX_SAFE,
    NGINX_VULNERABLE,
    LOG4J_CRITICAL,
    UNSUPPORTED,
    LITELLM_SAFE,
    LITELLM_COMPROMISED,
    PRODUCTS_RESPONSE,
    CVE_LOG4SHELL,
    USAGE_SOLO,
    STATS_EMPTY,
)

Test a deployment gate

from attestd.testing import MockTransport, LOG4J_CRITICAL

def test_deployment_blocked_on_critical():
    client = attestd.Client(
        api_key="test",
        transport=MockTransport(200, LOG4J_CRITICAL),
    )
    with pytest.raises(DeploymentBlockedError):
        run_deployment_gate(client, "log4j", "2.14.1")

Test retry behaviour

from attestd.testing import SequentialMockTransport, NGINX_SAFE

def test_retry_succeeds_on_second_attempt():
    transport = SequentialMockTransport([
        (503, {}),
        (200, NGINX_SAFE),
    ])
    client = attestd.Client(api_key="test", transport=transport, max_retries=1)
    result = client.check("nginx", "1.27.4")
    assert result.risk_state == "none"
    assert transport.call_count == 2

Test the outside coverage policy branch

from attestd.testing import MockTransport, UNSUPPORTED

def test_outside_coverage_is_blocked_not_allowed():
    client = attestd.Client(api_key="test", transport=MockTransport(200, UNSUPPORTED))
    with pytest.raises(attestd.AttestdUnsupportedProductError):
        run_check(client, "unknownproduct", "1.0.0")

Custom response bodies

All ready-made bodies (NGINX_SAFE, etc.) are plain dicts. Merge in overrides:

from attestd.testing import MockTransport, NGINX_VULNERABLE

body = {**NGINX_VULNERABLE, "actively_exploited": True, "risk_state": "critical"}
client = attestd.Client(api_key="test", transport=MockTransport(200, body))

License

MIT

Metadata

Release files for attestd 0.7.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for attestd 0.7.2
File Size Uploaded
attestd-0.7.2.tar.gz 33.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for attestd 0.7.2
File Interpreter ABI Platform
attestd-0.7.2-py3-none-any.whl Python 3 none any Details

Total release size: 61.0 kB

Release files / attestd-0.7.2.tar.gz

Download URL attestd-0.7.2.tar.gz
Size 33.2 kB
Tags Source
SHA-256 checksum
How to use checksums
cbdb2b3c3885c85f5d47b2e75dd646d00f8be783f8feea6760b83a1d0228d197
BLAKE2b-256 checksum
How to use checksums
6f58fe33157594e7f1c388928663c468c84def56577c71705b89c2b6f97d8559
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / attestd-0.7.2-py3-none-any.whl

Download URL attestd-0.7.2-py3-none-any.whl
Size 27.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b57feec30ce4c512fd0036fe151cdbebb7870c9735d11e0e7109431c26d127de
BLAKE2b-256 checksum
How to use checksums
9a0fb4aa80c1c57b9d34228afe38669b2ad0c011c2304509d8c8104561205d50
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.7.2 This release

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page