pub mod algorithmic;
#[cfg(test)]
mod algorithmic_tests;
pub mod behavioral;
#[cfg(test)]
mod behavioral_tests;
mod classify;
pub mod composites;
pub mod content;
pub mod taint;
#[cfg(test)]
mod tests;
pub mod tiered;
#[cfg(test)]
mod tiered_tests;
use crate::audits::context::classify::helpers::is_test_file;
use crate::config::model::SecurityBoundarySection;
use crate::review::diff::{ChangeStatus, ChangedFile, DiffTarget};
use serde::Serialize;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum BoundaryCategory {
AccessControl,
RequestTrust,
DeploySurface,
SupplyChain,
SecretConfig,
Custom,
}
impl BoundaryCategory {
pub fn label(self) -> &'static str {
match self {
Self::AccessControl => "access control",
Self::RequestTrust => "request trust",
Self::DeploySurface => "deploy surface",
Self::SupplyChain => "supply chain",
Self::SecretConfig => "secret config",
Self::Custom => "custom",
}
}
pub fn is_code_boundary(self) -> bool {
matches!(self, Self::AccessControl | Self::RequestTrust)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct BoundarySignal {
pub category: BoundaryCategory,
pub path: String,
pub status: ChangeStatus,
pub blast_radius: usize,
}
pub fn detect_boundary_signals(
repo_root: &Path,
target: DiffTarget<'_>,
changed_files: &[ChangedFile],
config: &SecurityBoundarySection,
) -> Vec<BoundarySignal> {
if !config.enabled {
return Vec::new();
}
let custom = classify::build_custom_globset(&config.extra_patterns);
let mut signals: Vec<BoundarySignal> = changed_files
.iter()
.filter(|file| !is_test_file(&file.path, false))
.filter_map(|file| {
let path = file.path_string();
let mut category = classify::classify_boundary(&path, custom.as_ref());
if category.is_none() {
if let Some(post_source) = content::post_change_source(repo_root, file, target) {
category = classify::classify_boundary_ast(&file.path, post_source.content());
}
}
category.map(|category| BoundarySignal {
category,
path,
status: file.status,
blast_radius: 0,
})
})
.collect();
signals.sort_by(|left, right| {
left.category
.cmp(&right.category)
.then_with(|| left.path.cmp(&right.path))
});
signals
}