Skip to main content

agentshield/config/
mod.rs

1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{Result, ShieldError};
6use crate::rules::policy::Policy;
7
8/// Top-level configuration from `.agentshield.toml`.
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct Config {
11    #[serde(default)]
12    pub policy: Policy,
13    #[serde(default)]
14    pub scan: ScanConfig,
15}
16
17/// `[scan]` section of the config file.
18#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19pub struct ScanConfig {
20    /// Skip test files when true.
21    #[serde(default)]
22    pub ignore_tests: bool,
23}
24
25impl Config {
26    /// Load config from a TOML file. Returns default if file doesn't exist.
27    pub fn load(path: &Path) -> Result<Self> {
28        if !path.exists() {
29            return Ok(Self::default());
30        }
31        let content = std::fs::read_to_string(path)?;
32        let config: Config = toml::from_str(&content)?;
33        config.validate()?;
34        Ok(config)
35    }
36
37    /// Validate the loaded configuration.
38    ///
39    /// Called automatically by `load()`. Exposed for testing via
40    /// `validate_for_test()`.
41    fn validate(&self) -> Result<()> {
42        for s in &self.policy.suppressions {
43            if s.reason.trim().is_empty() {
44                return Err(ShieldError::Config(format!(
45                    "Suppression for fingerprint '{}' must have a non-empty reason",
46                    s.fingerprint,
47                )));
48            }
49        }
50        Ok(())
51    }
52
53    /// Validate without loading from file. Used by tests.
54    #[cfg(test)]
55    pub fn validate_for_test(&self) -> Result<()> {
56        self.validate()
57    }
58
59    /// Generate a starter config file.
60    pub fn starter_toml() -> &'static str {
61        r#"# AgentShield configuration
62# See https://github.com/limaronaldo/agentshield for documentation.
63
64[policy]
65# Minimum severity to fail the scan (info, low, medium, high, critical).
66fail_on = "high"
67
68# Rule IDs to ignore entirely.
69# ignore_rules = ["SHIELD-008"]
70
71# Per-rule severity overrides.
72# [policy.overrides]
73# "SHIELD-012" = "info"
74
75# Suppress specific findings by fingerprint.
76# Run `agentshield scan . --format json` to see fingerprints.
77# [[policy.suppressions]]
78# fingerprint = "abc123..."
79# reason = "False positive: input is validated by middleware"
80# expires = "2026-06-01"
81
82# [scan]
83# Skip test files (test/, tests/, __tests__/, *.test.ts, *.spec.ts, etc.).
84# ignore_tests = false
85"#
86    }
87}