use crate::audits::traits::FileAudit;
use crate::findings::types::{Confidence, Evidence, Finding, FindingCategory, Severity};
use crate::knowledge::decision::apply_file_decision;
use crate::scan::config::ScanConfig;
use crate::scan::facts::FileFacts;
const SECRET_KEYS: &[&str] = &[
"api_key",
"apikey",
"api_secret",
"secret_key",
"secret",
"password",
"passwd",
"private_key",
"access_key_id",
"secret_access_key",
"access_key",
"auth_token",
"access_token",
"refresh_token",
"token",
"client_secret",
"signing_key",
"encryption_key",
"bearer",
"jwt",
];
pub struct SecretCandidateAudit;
impl FileAudit for SecretCandidateAudit {
fn audit(&self, file: &FileFacts, _config: &ScanConfig) -> Vec<Finding> {
let lower_path = file.path.to_string_lossy().to_lowercase();
let ext = file
.path
.extension()
.and_then(|e| e.to_str())
.unwrap_or_default();
if matches!(ext, "md" | "mdx" | "rst" | "txt") {
return vec![];
}
if is_lock_file(&file.path) {
return vec![];
}
if lower_path.contains("test")
|| lower_path.contains("fixture")
|| lower_path.contains("example")
|| lower_path.contains("mock")
{
return vec![];
}
file.content
.as_deref()
.unwrap_or("")
.lines()
.enumerate()
.filter_map(|(index, line)| {
detect_secret_line(line, index + 1, &file.path).and_then(|finding| {
apply_file_decision("security.secret-candidate", file, finding, None)
})
})
.collect()
}
}
fn is_lock_file(path: &std::path::Path) -> bool {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or_default();
if matches!(ext, "lock" | "lockb") {
return true;
}
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default();
matches!(
name,
"package-lock.json"
| "pnpm-lock.yaml"
| "pnpm-lock.yml"
| "go.sum"
| "go.work.sum"
| "bun.lock"
)
}
fn detect_secret_line(line: &str, line_number: usize, path: &std::path::Path) -> Option<Finding> {
if line.trim_start().starts_with("-----BEGIN") {
return None;
}
if let Some(jwt) = find_jwt_like_token(line) {
return Some(build_finding(
line_number,
path,
"jwt-like token",
mask_token_in_line(line.trim(), jwt),
));
}
let lower = line.to_ascii_lowercase();
if let Some(matched_key) = SECRET_KEYS
.iter()
.find(|&&key| assigned_secret_value_for_key(line, &lower, key).is_some())
{
return Some(build_finding(
line_number,
path,
matched_key,
mask_secret_value(line.trim()),
));
}
None
}
include!("secret_candidate/helpers.rs");
include!("secret_candidate/finding.rs");