forge-guard 0.3.6

Pre-deployment smart contract auditing framework for Foundry
Documentation
use serde::{Deserialize, Serialize};

/// Global forge-guard configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ForgeGuardConfig {
    /// Deployment guard settings.
    #[serde(default)]
    pub deployment: DeploymentConfig,
    /// Security engine settings.
    #[serde(default)]
    pub security: SecurityConfig,
    /// Report generation settings.
    #[serde(default)]
    pub report: ReportConfig,
    /// Caching settings.
    #[serde(default)]
    pub cache: CacheConfig,
    /// Plugin settings.
    #[serde(default)]
    pub plugins: PluginConfig,
    /// AI auditor settings.
    #[serde(default)]
    pub ai: AiConfig,
    /// Webhook notification settings.
    #[serde(default)]
    pub notifications: NotificationConfig,
    /// Historical trend tracking settings.
    #[serde(default)]
    pub history: HistoryConfig,
}

/// Historical trend tracking configuration.
///
/// ```toml
/// [history]
/// enabled = true                  # persist audit results (opt-in)
/// db_path = "~/.forge-guard/history.db"  # optional override
/// ```
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HistoryConfig {
    /// Persist audit results for trend tracking. Opt-in — when false,
    /// audits are never recorded unless `--enable-history` is passed.
    #[serde(default)]
    pub enabled: bool,
    /// SQLite database path. Defaults to `~/.forge-guard/history.db`.
    /// `~` is expanded to the user's home directory; relative paths are
    /// resolved against the project root.
    #[serde(default)]
    pub db_path: Option<String>,
}

/// Deployment guard configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentConfig {
    /// Minimum security score required to deploy (0-100).
    #[serde(default = "default_min_score")]
    pub min_score: u8,
    /// Whether to block on high-severity findings.
    #[serde(default = "default_true")]
    pub block_on_high: bool,
    /// Whether to block on medium-severity findings.
    #[serde(default)]
    pub block_on_medium: bool,
    /// Whether to block on critical findings.
    #[serde(default = "default_true")]
    pub block_on_critical: bool,
    /// Require fuzzing to pass before deployment.
    #[serde(default = "default_true")]
    pub require_fuzzing: bool,
    /// Require invariant tests to pass before deployment.
    #[serde(default = "default_true")]
    pub require_invariants: bool,
    /// Whether to run deployment simulation.
    #[serde(default = "default_true")]
    pub simulate_deployment: bool,
    /// Require on-chain verification after deployment.
    #[serde(default)]
    pub require_verification: bool,
    /// Auto-verify after successful deployment.
    #[serde(default)]
    pub auto_verify: bool,
    /// Explorer API key (reads chain-specific env var when empty).
    #[serde(default)]
    pub explorer_api_key: Option<String>,
}

fn default_min_score() -> u8 {
    70
}
fn default_true() -> bool {
    true
}

impl Default for DeploymentConfig {
    fn default() -> Self {
        Self {
            min_score: 70,
            block_on_high: true,
            block_on_medium: false,
            block_on_critical: true,
            require_fuzzing: true,
            require_invariants: true,
            simulate_deployment: true,
            require_verification: false,
            auto_verify: false,
            explorer_api_key: None,
        }
    }
}

/// Security engine configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
    /// Enable HIGH severity checks.
    #[serde(default = "default_true")]
    pub enable_high: bool,
    /// Enable MEDIUM severity checks.
    #[serde(default = "default_true")]
    pub enable_medium: bool,
    /// Enable LOW severity checks.
    #[serde(default = "default_true")]
    pub enable_low: bool,
    /// Enable INFORMATIONAL checks.
    #[serde(default)]
    pub enable_info: bool,
    /// Enable exploit path analysis.
    #[serde(default = "default_true")]
    pub exploit_analysis: bool,
    /// Enable gas analysis.
    #[serde(default)]
    pub gas_analysis: bool,
    /// Enable bytecode analysis.
    #[serde(default)]
    pub bytecode_analysis: bool,
    /// Maximum number of findings per check.
    #[serde(default = "default_max_findings")]
    pub max_findings_per_check: usize,
    /// Custom check severities (check_name -> Severity override).
    #[serde(default)]
    pub severity_overrides: std::collections::HashMap<String, String>,
    /// Check IDs to force-enable (e.g. from an audit template).
    /// Empty = all checks enabled per severity gates.
    #[serde(default)]
    pub enabled_checks: Vec<String>,
    /// Check IDs to force-disable (e.g. from an audit template).
    #[serde(default)]
    pub disabled_checks: Vec<String>,
}

fn default_max_findings() -> usize {
    50
}

impl Default for SecurityConfig {
    fn default() -> Self {
        Self {
            enable_high: true,
            enable_medium: true,
            enable_low: true,
            enable_info: false,
            exploit_analysis: true,
            gas_analysis: false,
            bytecode_analysis: false,
            max_findings_per_check: 50,
            severity_overrides: std::collections::HashMap::new(),
            enabled_checks: Vec::new(),
            disabled_checks: Vec::new(),
        }
    }
}

/// Report configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportConfig {
    /// Include code snippets in reports.
    #[serde(default = "default_true")]
    pub include_snippets: bool,
    /// Include exploit paths in reports.
    #[serde(default = "default_true")]
    pub include_exploit_paths: bool,
    /// Include recommendations in reports.
    #[serde(default = "default_true")]
    pub include_recommendations: bool,
    /// Output directory for report files.
    #[serde(default = "default_report_dir")]
    pub output_dir: String,
    /// Generate summary only (no details).
    #[serde(default)]
    pub summary_only: bool,
}

fn default_report_dir() -> String {
    "reports".into()
}

impl Default for ReportConfig {
    fn default() -> Self {
        Self {
            include_snippets: true,
            include_exploit_paths: true,
            include_recommendations: true,
            output_dir: default_report_dir(),
            summary_only: false,
        }
    }
}

/// Cache configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
    /// Enable caching.
    #[serde(default = "default_true")]
    pub enabled: bool,
    /// Cache directory path.
    #[serde(default = "default_cache_dir")]
    pub directory: String,
    /// Maximum cache size in MB.
    #[serde(default = "default_cache_size")]
    pub max_size_mb: u64,
    /// Cache TTL in seconds.
    #[serde(default = "default_cache_ttl")]
    pub ttl_seconds: u64,
}

fn default_cache_dir() -> String {
    ".forge-guard-cache".into()
}
fn default_cache_size() -> u64 {
    500
}
fn default_cache_ttl() -> u64 {
    3600
}

impl Default for CacheConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            directory: default_cache_dir(),
            max_size_mb: 500,
            ttl_seconds: 3600,
        }
    }
}

/// AI auditor configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiConfig {
    /// Provider type: "openai", "claude", "ollama".
    #[serde(default = "default_ai_provider")]
    pub provider: String,
    /// Model identifier.
    #[serde(default = "default_ai_model")]
    pub model: String,
    /// Sampling temperature.
    #[serde(default = "default_ai_temperature")]
    pub temperature: f64,
    /// Maximum tokens per response.
    #[serde(default = "default_ai_max_tokens")]
    pub max_tokens: u32,
    /// Minimum confidence for findings (0.0–1.0).
    #[serde(default = "default_ai_min_confidence")]
    pub min_confidence: f64,
    /// Run full audit (security + gas + logic) when enabled.
    #[serde(default)]
    pub full_audit: bool,
}

fn default_ai_provider() -> String {
    "openai".into()
}
fn default_ai_model() -> String {
    "gpt-4".into()
}
fn default_ai_temperature() -> f64 {
    0.1
}
fn default_ai_max_tokens() -> u32 {
    4000
}
fn default_ai_min_confidence() -> f64 {
    0.5
}

impl Default for AiConfig {
    fn default() -> Self {
        Self {
            provider: default_ai_provider(),
            model: default_ai_model(),
            temperature: default_ai_temperature(),
            max_tokens: default_ai_max_tokens(),
            min_confidence: default_ai_min_confidence(),
            full_audit: false,
        }
    }
}

/// Plugin configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
    /// Directories to search for plugins.
    #[serde(default = "default_plugin_dirs")]
    pub directories: Vec<String>,
    /// Plugins to enable (empty = all).
    #[serde(default)]
    pub enabled: Vec<String>,
    /// Plugins to disable.
    #[serde(default)]
    pub disabled: Vec<String>,
    /// Allow loading plugins from outside the project.
    #[serde(default)]
    pub allow_external: bool,
}

fn default_plugin_dirs() -> Vec<String> {
    vec![".forge-guard/plugins".into()]
}

impl Default for PluginConfig {
    fn default() -> Self {
        Self {
            directories: default_plugin_dirs(),
            enabled: Vec::new(),
            disabled: Vec::new(),
            allow_external: false,
        }
    }
}

/// Webhook notification configuration.
///
/// ```toml
/// [notifications.slack]
/// webhook = "https://hooks.slack.com/services/T000/B000/XXXX"
/// min_severity = "high"
///
/// [notifications.discord]
/// webhook = "https://discord.com/api/webhooks/123/abc"
/// min_severity = "critical"
/// ```
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NotificationConfig {
    /// Slack incoming webhook settings.
    #[serde(default)]
    pub slack: NotificationEndpoint,
    /// Discord webhook settings.
    #[serde(default)]
    pub discord: NotificationEndpoint,
}

/// A single webhook endpoint configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationEndpoint {
    /// Incoming webhook URL. Empty when notifications are disabled.
    #[serde(default)]
    pub webhook: Option<String>,
    /// Minimum finding severity that triggers a notification:
    /// `informational`, `low`, `medium`, `high`, or `critical`.
    #[serde(default = "default_notify_min_severity")]
    pub min_severity: String,
}

fn default_notify_min_severity() -> String {
    "high".into()
}

impl Default for NotificationEndpoint {
    fn default() -> Self {
        Self {
            webhook: None,
            min_severity: default_notify_min_severity(),
        }
    }
}