AutoGen Security Tool: CVE and Supply Chain Checks with Attestd

How to Give Your AutoGen Agent a Security Gate#
Microsoft is moving long-term agent development toward the Microsoft Agent Framework. AutoGen 0.7.5 is still where most Python multi-agent code runs today: roughly 1.3 million monthly downloads, stable APIs, and the patterns teams already ship.
AutoGen multi-agent workflows are where security gates matter most. A coding agent that deploys a compromised dependency can propagate that compromise across every other agent in the workflow that trusts its output. Without a structured check, the LLM is guessing from training data.
This tutorial builds an AutoGen FunctionTool that wraps the Attestd API and shows both a single-agent check and a multi-agent safety gate pattern.
What you will need#
pip install "autogen-agentchat==0.7.5" autogen-core "autogen-ext[openai]" attestd
An Attestd API key from api.attestd.io/portal/login. Free tier, 1,000 calls a month.
What the tool returns#
The Attestd API returns a deterministic risk_state derived from NVD CVE data and the CISA KEV catalog. For PyPI and npm packages it also returns a supply_chain object indicating whether the version was a known malicious publish.
Two signals, independent of each other. A package can have risk_state: "none" and supply_chain.compromised: true at the same time. litellm 1.82.7 is exactly that case:
{
"product": "litellm",
"version": "1.82.7",
"risk_state": "none",
"supply_chain": {
"compromised": true,
"sources": ["osv", "registry"],
"malware_type": "backdoor",
"description": "TeamPCP supply chain attack. Credential stealer in proxy_server.py",
"compromised_at": "2026-03-24T10:39:00Z",
"removed_at": "2026-03-24T16:00:00Z"
}
}
No CVEs. Still malicious. A risk_state check alone would have passed it.
Pattern 1: Building the tool#
Instantiate Client once at module level. Never instantiate inside the function on every call.
import os
import attestd
from autogen_core.tools import FunctionTool
from attestd import AttestdUnsupportedProductError
_client = attestd.Client(api_key=os.environ["ATTESTD_API_KEY"])
def check_package_vulnerability(product: str, version: str) -> dict:
"""Check whether a software package version has known CVE vulnerabilities
or supply chain compromise. Use before deploying or recommending any
software dependency. outside_coverage=True means no data. Treat as
unknown risk, not safe."""
try:
result = _client.check(product, version)
return {
"outside_coverage": False,
"risk_state": result.risk_state,
"actively_exploited": result.actively_exploited,
"patch_available": result.patch_available,
"fixed_version": result.fixed_version,
"supply_chain_compromised": (
result.supply_chain.compromised
if result.supply_chain is not None
else False
),
}
except AttestdUnsupportedProductError:
return {
"outside_coverage": True,
"risk_state": None,
"message": f"No Attestd coverage for '{product}'. Treat as unknown risk.",
}
attestd_tool = FunctionTool(
check_package_vulnerability,
description=(
"Check CVE risk and supply chain integrity for a software dependency. "
"outside_coverage=True means unknown risk, not safe."
),
)
AttestdUnsupportedProductError must be caught. An agent that raises on an unsupported product is not production-ready. The outside_coverage: True return gives the agent a signal it can branch on: no data from Attestd is unknown risk, not a clean bill of health.
Pattern 2: Single agent#
One AssistantAgent with the Attestd tool in a RoundRobinGroupChat. AutoGen 0.7.5 is async-first: use await team.run_stream(...).
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
model_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
security_agent = AssistantAgent(
name="security_agent",
model_client=model_client,
tools=[attestd_tool],
system_message=(
"You are a security-aware deployment assistant. "
"Before approving any software dependency, call check_package_vulnerability. "
"Block if risk_state is 'critical' or 'high', or if supply_chain_compromised is True. "
"If outside_coverage is True, state that explicitly. Do not treat it as safe. "
"End with APPROVED or BLOCKED and your reasoning."
),
)
termination = (
TextMentionTermination("APPROVED", sources=["security_agent"])
| TextMentionTermination("BLOCKED", sources=["security_agent"])
)
team = RoundRobinGroupChat([security_agent], termination_condition=termination)
async def main() -> None:
await Console(
team.run_stream(task="Is it safe to deploy with runc 1.0.0 and litellm 1.82.7?")
)
asyncio.run(main())
Use sources= on TextMentionTermination so the team does not terminate early if the user task happens to contain APPROVED or BLOCKED.
Pattern 3: Multi-agent safety gate#
A dedicated security_gate agent runs Attestd checks before a deployment_agent can proceed. In a multi-agent workflow, a compromised dependency approved by one agent propagates to every agent that trusts its output.
security_gate = AssistantAgent(
name="security_gate",
model_client=model_client