#![expect( clippy::disallowed_methods, reason = "build script: runs at compile time outside any runtime" )] use std::fmt::Write; use std::path::Path; use std::{env, fs}; use serde::Deserialize; #[derive(Deserialize)] struct Config { allowlist: Option, rules: Vec, } #[derive(Deserialize)] struct GlobalAllowlist { regexes: Option>, stopwords: Option>, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct Rule { id: String, regex: String, #[serde(default)] keywords: Vec, entropy: Option, #[serde(default)] allowlist: Option, #[allow( dead_code, reason = "The source TOML carries descriptions even though codegen ignores them." )] description: Option, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct RuleAllowlist { regexes: Option>, stopwords: Option>, regex_target: Option, } fn escape_rust_string(s: &str) -> String { let mut out = String::with_capacity(s.len() + 10); for ch in s.chars() { match ch { '\\' => out.push_str("\\\\"), '"' => out.push_str("\\\""), '\n' => out.push_str("\\n"), '\r' => out.push_str("\\r"), '\t' => out.push_str("\\t"), c => out.push(c), } } out } fn main() { println!("cargo:rerun-if-changed=data/gitleaks.toml"); let toml_path = Path::new("data/gitleaks.toml"); let toml_content = fs::read_to_string(toml_path).expect("failed to read gitleaks.toml"); let config: Config = toml::from_str(&toml_content).expect("failed to parse gitleaks.toml"); let out_dir = env::var("OUT_DIR").expect("OUT_DIR should be set for build scripts"); let out_path = Path::new(&out_dir).join("rules_generated.rs"); let mut code = String::new(); // Global allowlist regexes code.push_str("#[allow(unreachable_pub, reason = \"Generated constants are included through a private module boundary.\")]\npub const GLOBAL_ALLOWLIST_REGEXES: &[&str] = &[\n"); if let Some(ref al) = config.allowlist { if let Some(ref regexes) = al.regexes { for r in regexes { let _ = writeln!(code, " \"{}\",", escape_rust_string(r)); } } } code.push_str("];\n\n"); // Global allowlist stopwords code.push_str( "#[allow(unreachable_pub, reason = \"Generated constants are included through a private module boundary.\")]\npub const GLOBAL_ALLOWLIST_STOPWORDS: &[&str] = &[\n", ); if let Some(ref al) = config.allowlist { if let Some(ref stopwords) = al.stopwords { for sw in stopwords { let _ = writeln!(code, " \"{}\",", escape_rust_string(sw)); } } } code.push_str("];\n\n"); // Rule definitions code.push_str("#[allow(dead_code, unreachable_pub, reason = \"Generated metadata is included through a private module boundary and not every field is read directly.\")]\n"); code.push_str("pub struct RuleDef {\n"); code.push_str(" pub id: &'static str,\n"); code.push_str(" pub regex_pattern: &'static str,\n"); code.push_str(" pub keywords: &'static [&'static str],\n"); code.push_str(" pub entropy: Option,\n"); code.push_str(" pub allowlist_regexes: &'static [&'static str],\n"); code.push_str(" pub allowlist_stopwords: &'static [&'static str],\n"); code.push_str(" pub allowlist_regex_target: Option<&'static str>,\n"); code.push_str("}\n\n"); code.push_str("#[allow(unreachable_pub, reason = \"Generated constants are included through a private module boundary.\")]\npub const RULES: &[RuleDef] = &[\n"); for rule in &config.rules { code.push_str(" RuleDef {\n"); let _ = writeln!(code, " id: \"{}\",", escape_rust_string(&rule.id)); let _ = writeln!( code, " regex_pattern: \"{}\",", escape_rust_string(&rule.regex) ); // Keywords code.push_str(" keywords: &["); for kw in &rule.keywords { let _ = write!(code, "\"{}\", ", escape_rust_string(kw)); } code.push_str("],\n"); // Entropy match rule.entropy { Some(e) => { let _ = writeln!(code, " entropy: Some({e:.1}),"); } None => code.push_str(" entropy: None,\n"), } // Allowlist regexes code.push_str(" allowlist_regexes: &["); if let Some(ref al) = rule.allowlist { if let Some(ref regexes) = al.regexes { for r in regexes { let _ = write!(code, "\"{}\", ", escape_rust_string(r)); } } } code.push_str("],\n"); // Allowlist stopwords code.push_str(" allowlist_stopwords: &["); if let Some(ref al) = rule.allowlist { if let Some(ref stopwords) = al.stopwords { for sw in stopwords { let _ = write!(code, "\"{}\", ", escape_rust_string(sw)); } } } code.push_str("],\n"); // Allowlist regex target if let Some(ref al) = rule.allowlist { if let Some(ref target) = al.regex_target { let _ = writeln!( code, " allowlist_regex_target: Some(\"{}\"),", escape_rust_string(target) ); } else { code.push_str(" allowlist_regex_target: None,\n"); } } else { code.push_str(" allowlist_regex_target: None,\n"); } code.push_str(" },\n"); } code.push_str("];\n"); fs::write(&out_path, code).expect("failed to write generated rules"); }