use tower_lsp_server::ls_types::DiagnosticSeverity;
use super::types::{OsvAffected, VulnSeverity};
fn parse_severity_label(s: &str) -> Option<VulnSeverity> {
match s.to_ascii_uppercase().as_str() {
"CRITICAL" => Some(VulnSeverity::Critical),
"HIGH" => Some(VulnSeverity::High),
"MODERATE" | "MEDIUM" => Some(VulnSeverity::Medium),
"LOW" => Some(VulnSeverity::Low),
_ => None,
}
}
fn severity_from_json(value: &serde_json::Value) -> Option<VulnSeverity> {
value
.get("severity")?
.as_str()
.and_then(parse_severity_label)
}
pub(super) fn classify(
id: &str,
aliases: &[String],
database_specific: Option<&serde_json::Value>,
relevant_affected: &[&OsvAffected],
) -> VulnSeverity {
if id.starts_with("MAL-") || aliases.iter().any(|alias| alias.starts_with("MAL-")) {
return VulnSeverity::Malicious;
}
if let Some(v) = database_specific.and_then(severity_from_json) {
return v;
}
for affected in relevant_affected {
if let Some(v) = affected
.ecosystem_specific
.as_ref()
.and_then(severity_from_json)
{
return v;
}
}
VulnSeverity::Unknown
}
#[must_use]
pub const fn to_diagnostic_severity(severity: VulnSeverity) -> DiagnosticSeverity {
match severity {
VulnSeverity::Critical
| VulnSeverity::High
| VulnSeverity::Unknown
| VulnSeverity::Malicious => DiagnosticSeverity::WARNING,
VulnSeverity::Medium | VulnSeverity::Low => DiagnosticSeverity::INFORMATION,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::osv::types::OsvAffected;
#[test]
fn database_specific_severity_wins() {
let json = serde_json::json!({ "severity": "CRITICAL" });
assert_eq!(
classify("RUSTSEC-2020-0071", &[], Some(&json), &[]),
VulnSeverity::Critical
);
}
#[test]
fn database_specific_moderate_maps_to_medium() {
let json = serde_json::json!({ "severity": "MODERATE" });
assert_eq!(
classify("RUSTSEC-2020-0071", &[], Some(&json), &[]),
VulnSeverity::Medium
);
}
#[test]
fn falls_back_to_ecosystem_specific_severity() {
let affected = OsvAffected {
package: None,
ecosystem_specific: Some(serde_json::json!({ "severity": "LOW" })),
ranges: vec![],
};
assert_eq!(
classify("RUSTSEC-2020-0071", &[], None, &[&affected]),
VulnSeverity::Low
);
}
#[test]
fn cvss_vector_only_record_is_unknown() {
assert_eq!(
classify("RUSTSEC-2020-0071", &[], None, &[]),
VulnSeverity::Unknown
);
}
#[test]
fn no_severity_at_all_is_unknown() {
assert_eq!(
classify("RUSTSEC-2020-0071", &[], None, &[]),
VulnSeverity::Unknown
);
}
#[test]
fn non_mal_aliases_do_not_trigger_malicious_classification() {
let aliases = ["GHSA-xxxx-xxxx-xxxx".to_string(), "CVE-2020-1".to_string()];
assert_eq!(
classify("RUSTSEC-2020-0071", &aliases, None, &[]),
VulnSeverity::Unknown
);
}
#[test]
fn mal_prefix_with_no_severity_fields_is_malicious() {
assert_eq!(
classify("MAL-2025-47141", &[], None, &[]),
VulnSeverity::Malicious
);
}
#[test]
fn mal_prefix_wins_over_a_graded_severity_present_on_the_same_record() {
let json = serde_json::json!({ "severity": "CRITICAL" });
assert_eq!(
classify("MAL-2025-47141", &[], Some(&json), &[]),
VulnSeverity::Malicious
);
}
#[test]
fn mal_alias_without_mal_prefixed_id_is_still_malicious() {
let json = serde_json::json!({ "severity": "HIGH" });
let aliases = ["MAL-2022-1".to_string(), "RUSTSEC-2022-0042".to_string()];
assert_eq!(
classify("GHSA-7pwq-f4pq-78gm", &aliases, Some(&json), &[]),
VulnSeverity::Malicious
);
}
#[test]
fn rustdecimal_three_alias_group_all_classify_as_malicious() {
let ghsa_aliases = ["MAL-2022-1".to_string(), "RUSTSEC-2022-0042".to_string()];
let mal_aliases = [
"GHSA-7pwq-f4pq-78gm".to_string(),
"RUSTSEC-2022-0042".to_string(),
];
let rustsec_aliases = ["GHSA-7pwq-f4pq-78gm".to_string(), "MAL-2022-1".to_string()];
let ghsa_severity = serde_json::json!({ "severity": "HIGH" });
assert_eq!(
classify(
"GHSA-7pwq-f4pq-78gm",
&ghsa_aliases,
Some(&ghsa_severity),
&[]
),
VulnSeverity::Malicious
);
assert_eq!(
classify("MAL-2022-1", &mal_aliases, None, &[]),
VulnSeverity::Malicious
);
assert_eq!(
classify("RUSTSEC-2022-0042", &rustsec_aliases, None, &[]),
VulnSeverity::Malicious
);
}
#[test]
fn to_diagnostic_severity_mapping() {
assert_eq!(
to_diagnostic_severity(VulnSeverity::Critical),
DiagnosticSeverity::WARNING
);
assert_eq!(
to_diagnostic_severity(VulnSeverity::High),
DiagnosticSeverity::WARNING
);
assert_eq!(
to_diagnostic_severity(VulnSeverity::Medium),
DiagnosticSeverity::INFORMATION
);
assert_eq!(
to_diagnostic_severity(VulnSeverity::Low),
DiagnosticSeverity::INFORMATION
);
assert_eq!(
to_diagnostic_severity(VulnSeverity::Unknown),
DiagnosticSeverity::WARNING
);
assert_eq!(
to_diagnostic_severity(VulnSeverity::Malicious),
DiagnosticSeverity::WARNING
);
}
}