use std::collections::BTreeMap;
use trusty_common::memory_core::filter::{check_secret, find_secret_token};
const PROVIDER_PREFIXES: &[&str] = &[
"sk-",
"ghp_",
"gho_",
"ghs_",
"github_pat_",
"xoxb-",
"xoxp-",
];
const AWS_PREFIXES: &[&str] = &["AKIA", "ASIA"];
const AWS_KEY_ID_LEN: usize = 20;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SecretRule {
ProviderPrefix,
AwsKeyId,
Base64Blob,
MixedCaseAlnum,
Other,
}
impl SecretRule {
pub fn label(self) -> &'static str {
match self {
Self::ProviderPrefix => "provider_prefix",
Self::AwsKeyId => "aws_key_id",
Self::Base64Blob => "base64_blob",
Self::MixedCaseAlnum => "mixed_case_alnum",
Self::Other => "other",
}
}
}
pub type RuleTally = BTreeMap<&'static str, usize>;
pub fn tally(t: &mut RuleTally, rule: SecretRule) {
*t.entry(rule.label()).or_default() += 1;
}
pub fn screen(s: &str) -> Option<SecretRule> {
check_secret(s).err()?;
let token = s
.split(|c: char| c.is_whitespace() || c == '`')
.map(|raw| {
raw.trim_matches(|c: char| !(c.is_ascii_alphanumeric() || matches!(c, '-' | '_')))
})
.find(|t| find_secret_token(t).is_some());
Some(token.map_or(SecretRule::Other, classify))
}
fn classify(token: &str) -> SecretRule {
let lower = token.to_ascii_lowercase();
let at_boundary = |p: &str| {
lower
.match_indices(p)
.any(|(i, _)| i == 0 || lower[..i].ends_with(['-', '_', '.']))
};
if PROVIDER_PREFIXES.iter().any(|p| at_boundary(p)) {
SecretRule::ProviderPrefix
} else if token.len() == AWS_KEY_ID_LEN
&& AWS_PREFIXES.iter().any(|p| token.starts_with(p))
&& token
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
{
SecretRule::AwsKeyId
} else if token.contains(['+', '/', '=']) {
SecretRule::Base64Blob
} else {
SecretRule::MixedCaseAlnum
}
}