Skip to main content

Python SDK for the Attestd security risk API

Project description

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", ...]

Supply chain check

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

result = client.check("litellm", "1.82.7")
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
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

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

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,
)

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). 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,
)

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

attestd-0.2.4.tar.gz (17.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

attestd-0.2.4-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file attestd-0.2.4.tar.gz.

File metadata

  • Download URL: attestd-0.2.4.tar.gz
  • Upload date:
  • Size: 17.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for attestd-0.2.4.tar.gz
Algorithm Hash digest
SHA256 5517a7a9251cea369bac65ab490cba16331ad1397e44a381cf1e26e319af2398
MD5 9040d0f1dfaaa879eafcc70b02456b39
BLAKE2b-256 2559bd22c72fdc2ece8da0070f9c9d6dc6d2a6e5e352490711072788107f662d

See more details on using hashes here.

Provenance

The following attestation bundles were made for attestd-0.2.4.tar.gz:

Publisher: publish.yml on attestd-io/attestd-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file attestd-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: attestd-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 17.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for attestd-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 a02a939333b3721cb175d3f0cca16516608d889b00396a520bc3ac5e188bcfaa
MD5 9033d9a08b6227f6aa3d23f97722e41b
BLAKE2b-256 f5e5ec9459e129e25d54de5ecb198e3fe88ad9173d7e2b47f0b24054c53d240a

See more details on using hashes here.

Provenance

The following attestation bundles were made for attestd-0.2.4-py3-none-any.whl:

Publisher: publish.yml on attestd-io/attestd-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page