AI Agent Security Audit Outreach: CVE Verification Before Cold-Emailing Maintainers
A cold email to a project maintainer is a one-shot credibility test. In security research outreach, the fastest way to fail it is to cite a CVE that does not apply — a wrong ID, a wrong severity, or a fix version that never shipped. This field note documents how we built a repeatable method for reaching AI/Agent projects (MCP servers, agent frameworks, LLM gateways, vector databases) and why CVE pre-verification is the lifeblood of the whole process.
Everything below comes from a real outreach campaign run in August 2026. Where a number or CVE appears, it is drawn from the campaign's own scorecards and send logs — no figure here is invented.
AI agent security audit targets: score, don't guess
We scored AI/Agent ecosystem projects on six dimensions — activity, commercialization, security responsiveness, AI relevance, reachability, and "big-tech" status — with security responsiveness and reachability weighted highest (25% each). The round that this article documents focused on the top 10 projects by that score:
| Project | Score | Category |
|---|---|---|
| promptfoo | 95 | LLM evaluation / red-teaming |
| langfuse | 93 | LLM observability |
| ollama | 91 | Local LLM runtime |
| weaviate | 89 | Vector database |
| lancedb | 88 | Vector database |
| Composio | 88 | Agent tool platform |
| dify | 86 | LLM application platform |
| qdrant | 86 | Vector database |
| Guardrails AI | 84 | Output validation layer |
| mem0 | 83 | Agent memory layer |
Source: scorecards/round2_top10_20260813.md (campaign scorecard, 2026-08-13).
Email reachability without guessing: 10/10
Every address used came from a public page — we never guessed or inferred a format. Three channels covered all 10 targets:
- SECURITY.md — the project's own vulnerability-reporting file: promptfoo, langfuse, ollama, mem0.
- security.txt (RFC 9116) — the domain's published contact: lancedb.
- GitHub org API — the org's public email field: weaviate, Composio, dify, qdrant, Guardrails AI.
Result: 10/10 targets had a verified, publicly-sourced email address. Each entry in the scorecard carries the source URL used to verify it (e.g. https://raw.githubusercontent.com/ollama/ollama/main/SECURITY.md, https://lancedb.com/.well-known/security.txt). Source: scorecards/round2_top10_20260813.md, "邮箱核实明细" section.
The rule that kept us honest: if no public email could be found, we did not send. Projects that only run a GitHub Security Advisory channel (vLLM, fastmcp, Skyvern, beeai) were excluded from this round rather than contacted through a guessed address.
CVE verification: the four-gate check in an AI agent security audit
Before any email that carried a CVE, each candidate CVE had to pass a four-gate check, with the verification method recorded for every entry:
- It exists (real CVE/GHSA, not a hallucinated ID).
- It is public (disclosed, not embargoed).
- It has a fix version (a patch shipped).
- The version range is correct (the affected/fixed range matches what we would claim).
Any gate failing → we did not cite that CVE in the cold email, and switched to a preventive-audit pitch instead.
The gate is easy to encode and rerun per candidate, which is what makes the audit reproducible rather than vibes-based:
def passes_cve_gate(cve_id, advisory_url, fixed_versions):
exists = advisory_accessible(advisory_url) # gate 1: real
is_public = advisory_disclosed(advisory_url) # gate 2: public
has_fix = bool(fixed_versions) # gate 3: fix shipped
correct = verify_range(fixed_versions, target_range) # gate 4: range right
return all([exists, is_public, has_fix, correct])
What the check actually caught: three real corrections
The scorecard initially listed a "score" for weaviate and a fix version for Composio that did not survive the four-gate check:
-
weaviate CVE-2025-67818 / CVE-2025-67819 — the draft card said "CVSS 8.7". Verification showed the actual scores are 7.2 (67818) and 4.9 (67819). We cited the CVE IDs in the email but deliberately did not cite the scores. Source:
weaviate.iosecurity release (Nov 2025) andGHSA-hmmh-292h-3364; documented in the scorecard appendix. -
Composio CVE-2024-8953 / CVE-2024-8958 — the draft card said "fixed in 0.6.9+". Verification showed the actual fix shipped in composio-core 0.5.43. We corrected the version before sending. Source: Snyk advisory
SNYK-PYTHON-COMPOSIOCORE-9637814and GitLab gemnasium-db. -
mem0 CVE-2026-31241 / CVE-2026-31245 — real and public, but the official advisories show no fix version (
fixed_versions: []on the GitHub Advisory). Under gate 3 (must have a fix), we did not cite the CVE numbers and switched to a preventive pitch referencing the vulnerability class (unsafe pickle deserialization + unauthenticated memory endpoints) instead. Source:GHSA-gq6f-qwv9-rf4jand dependabot.ecosyste.ms.
The six CVEs that made it into the emails
After the gate, six projects carried a verified CVE payload (each with a source URL in the scorecard appendix):
| Project | CVE | Class | Verified fix |
|---|---|---|---|
| promptfoo | CVE-2025-64756 | glob CLI command injection (7.5) | glob 10.5.0/11.1.0; promptfoo 0.34.0 |
| langfuse | CVE-2025-9799 | webhook SSRF | 3.89.0 (≤3.88.0 affected) |
| ollama | CVE-2024-37032 | path traversal → RCE (9.8) | 0.1.34 |
| weaviate | CVE-2025-67818 / 67819 | backup ZipSlip + shard read (IDs only, no score) | 1.33.4 |
| Composio | CVE-2024-8953 / 8958 | eval/file-path RCE (9.8) | composio-core ≥0.5.43 |
| Guardrails AI | CVE-2024-45858 (GHSA-w392-75q8-vr67) | XML validation eval / arbitrary code execution | 0.5.10 |
Source: scorecards/round2_top10_20260813.md, "第五波 CVE 前置核验附录" (2026-08-13), with per-CVE source URLs.
Channel discipline: when a CVE must NOT go in the email
Verification is not enough — the channel matters. Two of our targets (dify and qdrant) officially handle vulnerabilities through GitHub Security Advisories. Cold-emailing a CVE list to a team that runs a formal advisory process reads as process ignorance. For those two, we sent no CVE payload and used a preventive-audit pitch instead. lancedb has no CVE history at all (clean record per Sonatype/safetycli), so it also got a preventive "first-disclosure" offer.
Final payload split across the 10 emails: 6 with verified CVE citations, 2 with preventive pitches (lancedb, mem0), 2 with no CVE by channel rule (dify, qdrant).
What a pre-verified send looks like
The emails followed a tight format: 2–4 word lowercase subject, 95–112 word body, a single call-to-action ("reply 'interested'"), and an ASCII signature. All 10 were sent and logged with per-recipient Message-IDs. 10/10 sent OK (SMTP accepted; delivery confirmed via the send log's receipt tracking). Source: SENT_LOG_20260813.md (campaign send log, 2026-08-13).
The reusable checklist
- Reachability before payload: SECURITY.md, then security.txt (RFC 9116), then the GitHub org API. No public source → no email.
- Four-gate every CVE: exists, public, has fix, version range correct. One failure → drop the ID, keep the vulnerability class as the pitch.
- Fix the card before you send: verify the actual CVSS and the actual patched version against the advisory, not against your own draft.
- Match the channel: if a project runs GitHub Security Advisories, don't cold-mail a CVE list — send a preventive pitch.
Conclusion: get your AI agent security audited
This method is the backbone of our own audit work at Correctover. CCS is the security validation layer for AI applications: it statically scans AI/LLM code for SSRF, prompt-injection, and unsafe file access in CI (16 systemic vulnerability patterns), and enforces agent tool-call selection, arguments, and permissions at runtime before a call executes. We run this exact verification discipline on every engagement — the same four-gate CVE check above, applied to your stack.
If you are building an AI agent, an MCP server, or an LLM gateway and want a free static scan pass of your codebase, reach us at correctover.com.
Top comments (0)