Skip to main content

forge_guard/ai/
mod.rs

1//! AI-powered auditing — integrates OpenAI, Anthropic Claude, and Ollama
2//! for intelligent vulnerability analysis of Solidity smart contracts.
3//!
4//! # Architecture
5//!
6//! ```text
7//! CLI  ──→  ConsensusEngine  ──→  AuditorAgent(s)
8//!               │                      │
9//!               │                      └── LlmProvider (HTTP)
10//!               │                           ├── OpenAiProvider
11//!               │                           ├── ClaudeProvider
12//!               │                           └── OllamaProvider
13//!               │
14//!               └── ConsensusReport (dedup + confidence scoring)
15//! ```
16//!
17//! # Usage
18//!
19//! ```ignore
20//! forge-guard audit --ai                          # use default provider (OpenAI)
21//! forge-guard audit --ai --ai-provider claude     # use Claude
22//! forge-guard audit --ai --ai-provider ollama     # use local Ollama
23//! ```
24
25pub mod auditors;
26pub mod consensus;
27pub mod providers;
28
29use crate::core::Severity;
30use std::collections::HashMap;
31
32// ─────────────────────────────────────────────────────────────
33// Shared types
34// ─────────────────────────────────────────────────────────────
35
36/// Configuration for AI auditors.
37#[derive(Debug, Clone)]
38pub struct AiAuditorConfig {
39    /// Provider type: "openai", "claude", "ollama".
40    pub provider: String,
41    /// Model identifier (e.g. "gpt-5", "claude-5-sonnet-20260701").
42    pub model: String,
43    /// Sampling temperature (0.0–1.0).
44    pub temperature: f64,
45    /// Maximum tokens per response.
46    pub max_tokens: u32,
47    /// Minimum confidence (0.0–1.0) for a finding to be included.
48    pub min_confidence: f64,
49    /// Optional API key override. Reads from env var when `None`.
50    pub api_key: Option<String>,
51    /// Optional Ollama endpoint override.
52    pub ollama_endpoint: Option<String>,
53    /// Whether to include gas and logic auditors in addition to security.
54    pub full_audit: bool,
55}
56
57impl Default for AiAuditorConfig {
58    fn default() -> Self {
59        Self {
60            provider: "openai".into(),
61            model: "gpt-5".into(),
62            temperature: 0.1,
63            max_tokens: 4000,
64            min_confidence: 0.5,
65            api_key: None,
66            ollama_endpoint: None,
67            full_audit: false,
68        }
69    }
70}
71
72/// Context passed to each auditor agent for analysis.
73#[derive(Debug, Clone)]
74pub struct AuditContext {
75    /// Full Solidity source code to analyze.
76    pub source_code: String,
77    /// File name (for context in prompts).
78    pub file_name: String,
79    /// Compiler version string (e.g. "^0.8.20").
80    pub compiler_version: String,
81    /// Additional context key-value pairs.
82    pub additional: HashMap<String, String>,
83}
84
85impl AuditContext {
86    /// Create a new audit context.
87    pub fn new(source_code: &str, file_name: &str, compiler_version: &str) -> Self {
88        Self {
89            source_code: source_code.to_owned(),
90            file_name: file_name.to_owned(),
91            compiler_version: compiler_version.to_owned(),
92            additional: HashMap::new(),
93        }
94    }
95}
96
97/// A single finding produced by an AI auditor.
98#[derive(Debug, Clone)]
99pub struct AuditorFinding {
100    /// Human-readable title.
101    pub title: String,
102    /// Detailed description of the vulnerability.
103    pub description: String,
104    /// Model confidence (0.0 – 1.0).
105    pub confidence: f64,
106    /// Assigned severity.
107    pub severity: Severity,
108    /// Suggested remediation.
109    pub suggestion: String,
110    /// Line numbers where the issue appears.
111    pub line_numbers: Vec<usize>,
112    /// Vulnerability category (e.g. "Reentrancy", "AccessControl").
113    pub category: String,
114}
115
116/// A finding with consensus metadata.
117#[derive(Debug, Clone)]
118pub struct ConsensusFinding {
119    /// Name of the auditor that produced this finding.
120    pub auditor: String,
121    /// Domain of the auditor (e.g. "Security", "Gas").
122    pub domain: String,
123    /// The finding itself.
124    pub finding: AuditorFinding,
125    /// Whether this finding was cross-validated by multiple auditors.
126    pub cross_validated: bool,
127}
128
129/// Convert a consensus finding into a core Finding for the audit pipeline.
130pub fn consensus_to_core_finding(cf: &ConsensusFinding, file_name: &str) -> crate::core::Finding {
131    use crate::core::FindingBuilder;
132
133    let line = cf.finding.line_numbers.first().copied();
134
135    FindingBuilder::default()
136        .id(&format!("AI-{:04}", rand_id()))
137        .title(&cf.finding.title)
138        .description(&format!(
139            "[AI {}] {} (confidence: {:.0}%){}",
140            cf.auditor,
141            cf.finding.description,
142            cf.finding.confidence * 100.0,
143            if cf.cross_validated {
144                " [cross-validated]"
145            } else {
146                ""
147            }
148        ))
149        .severity(cf.finding.severity)
150        .file(file_name)
151        .location(line.unwrap_or(0), 0)
152        .code(&format!(
153            "AI-auditor '{}' ({}) — {}",
154            cf.auditor, cf.domain, cf.finding.title
155        ))
156        .recommendation(&cf.finding.suggestion)
157        .category(&cf.finding.category)
158        .build()
159}
160
161fn rand_id() -> u16 {
162    use std::time::{SystemTime, UNIX_EPOCH};
163    let nanos = SystemTime::now()
164        .duration_since(UNIX_EPOCH)
165        .unwrap_or_default()
166        .subsec_nanos();
167    (nanos % 9999) as u16
168}
169
170// ── Re-exports ───────────────────────────────────────────────
171pub use auditors::*;
172pub use consensus::*;
173pub use providers::*;