use crate::security::SecurityIssueType;
use serde::{Deserialize, Serialize};
pub const CURRENT_TAXONOMY_VERSION: &str = "2025-draft";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OwaspTag {
pub id: String,
pub version: String,
}
impl OwaspTag {
pub fn new(id: &'static str) -> Self {
Self {
id: id.to_string(),
version: CURRENT_TAXONOMY_VERSION.to_string(),
}
}
}
pub fn tags_for_security_issue(issue: SecurityIssueType) -> Vec<OwaspTag> {
use SecurityIssueType::*;
match issue {
ToolPoisoning => vec![OwaspTag::new("MCP02"), OwaspTag::new("MCP03")],
SQLInjection | CommandInjection => vec![OwaspTag::new("MCP07")],
PathTraversal => vec![OwaspTag::new("MCP04")],
AuthBypass => vec![OwaspTag::new("MCP08")],
PromptInjection => vec![OwaspTag::new("MCP01")],
Jailbreak => vec![OwaspTag::new("MCP01"), OwaspTag::new("MCP03")],
PIILeakage => vec![OwaspTag::new("MCP09")],
SecretsLeakage => vec![OwaspTag::new("MCP06"), OwaspTag::new("MCP09")],
}
}
pub fn tags_for_yara_rule(rule_name: &str) -> Vec<OwaspTag> {
match rule_name {
"SecretsLeakage" | "EnvironmentVariableLeakage" => {
vec![OwaspTag::new("MCP06"), OwaspTag::new("MCP09")]
}
"SSHKeyExposure" | "PEMFileAccess" => vec![OwaspTag::new("MCP06")],
"CommandInjection" => vec![OwaspTag::new("MCP07")],
"SQLInjection" => vec![OwaspTag::new("MCP07")],
"PathTraversalVulnerability" => vec![OwaspTag::new("MCP04")],
"MCPConfigRisk" => vec![OwaspTag::new("MCP10"), OwaspTag::new("MCP07")],
"MCPConfigChanged" => vec![OwaspTag::new("MCP02")],
"VulnerableDependency" => vec![OwaspTag::new("MCP10")],
"CrossOriginEscalation"
| "CrossDomainContamination"
| "DomainOutlier"
| "MixedSecuritySchemes" => vec![OwaspTag::new("MCP05")],
"PromptInjectionSignature" | "UnicodeSteganography" | "IndirectPromptInjection" => {
vec![OwaspTag::new("MCP01")]
}
"CoerciveInjection" => vec![OwaspTag::new("MCP01"), OwaspTag::new("MCP02")],
"AutonomyAbuse" => vec![OwaspTag::new("MCP03")],
"CapabilityInflation" => vec![OwaspTag::new("MCP02"), OwaspTag::new("MCP03")],
"SkillCredentialHarvesting" => {
vec![OwaspTag::new("MCP06"), OwaspTag::new("MCP09")]
}
"SkillToolChainingExfiltration" => {
vec![OwaspTag::new("MCP06"), OwaspTag::new("MCP09")]
}
"SkillSystemManipulation" => {
vec![OwaspTag::new("MCP03"), OwaspTag::new("MCP04")]
}
"OverbroadAllowedTools" => vec![OwaspTag::new("MCP03")],
"VagueSkillTrigger" => vec![OwaspTag::new("MCP02"), OwaspTag::new("MCP03")],
"GenericSkillTrigger" => vec![OwaspTag::new("MCP02"), OwaspTag::new("MCP03")],
"DataExfiltrationGrant" => vec![OwaspTag::new("MCP06"), OwaspTag::new("MCP09")],
"SkillSensitiveFileReference" => vec![OwaspTag::new("MCP06"), OwaspTag::new("MCP09")],
"SkillNameCollision" => vec![OwaspTag::new("MCP02"), OwaspTag::new("MCP03")],
"SkillEmbeddedPayload" => vec![OwaspTag::new("MCP01"), OwaspTag::new("MCP10")],
"AgentskillsNameMismatch"
| "AgentskillsInvalidName"
| "AgentskillsMissingName"
| "AgentskillsUnknownFrontmatterField" => vec![OwaspTag::new("MCP02")],
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_security_issue_type_has_at_least_one_tag() {
use SecurityIssueType::*;
let all = [
ToolPoisoning,
SQLInjection,
CommandInjection,
PathTraversal,
AuthBypass,
PromptInjection,
Jailbreak,
PIILeakage,
SecretsLeakage,
];
for issue in all {
assert!(
!tags_for_security_issue(issue).is_empty(),
"SecurityIssueType::{issue:?} has no OWASP tags — every \
detection class should map to at least one Top 10 entry"
);
}
}
#[test]
fn known_yara_rules_have_tags() {
for name in [
"SecretsLeakage",
"EnvironmentVariableLeakage",
"SSHKeyExposure",
"PEMFileAccess",
"CommandInjection",
"SQLInjection",
"PathTraversalVulnerability",
"MCPConfigRisk",
"CrossOriginEscalation",
"CrossDomainContamination",
"DomainOutlier",
"MixedSecuritySchemes",
"PromptInjectionSignature",
"UnicodeSteganography",
"CoerciveInjection",
"IndirectPromptInjection",
"AutonomyAbuse",
"CapabilityInflation",
"SkillCredentialHarvesting",
"SkillToolChainingExfiltration",
"SkillSystemManipulation",
] {
assert!(
!tags_for_yara_rule(name).is_empty(),
"YARA rule {name} should be tagged"
);
}
}
#[test]
fn unknown_yara_rule_returns_empty_not_panic() {
assert!(tags_for_yara_rule("totally-made-up-rule").is_empty());
}
}