#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Tier {
Block,
Warn,
Clean,
}
impl Tier {
pub fn exit_code(self) -> i32 {
match self {
Tier::Block => 1,
Tier::Warn => 2,
Tier::Clean => 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CustomerState {
BlockedByPolicy,
Compromised,
Malicious,
RansomwareLinked,
ActivelyExploited,
Vulnerable,
NotYetAssessed,
Clean,
}
impl CustomerState {
pub fn from_wire(source: &str) -> CustomerState {
match source {
"ALLOWED_NO_FINDINGS" => CustomerState::Clean,
"CVE_FINDING" => CustomerState::Vulnerable,
"CVE_FINDING_ON_KEV" => CustomerState::ActivelyExploited,
"CVE_FINDING_ON_RANSOMWARE" => CustomerState::RansomwareLinked,
"MALICIOUS_TRIAGE" => CustomerState::Malicious,
"DM_THRESHOLD_BLOCK" => CustomerState::BlockedByPolicy,
"INSUFFICIENT_DATA" => CustomerState::NotYetAssessed,
"VECTOR_VERDICT" => CustomerState::Vulnerable,
_ => CustomerState::NotYetAssessed,
}
}
pub fn from_block_origin(source: &str, curated_compromise: bool) -> CustomerState {
match (source, curated_compromise) {
("DM_THRESHOLD_BLOCK", true) => CustomerState::Compromised,
_ => CustomerState::from_wire(source),
}
}
pub fn as_str(self) -> &'static str {
match self {
CustomerState::BlockedByPolicy => "blocked_by_policy",
CustomerState::Compromised => "compromised",
CustomerState::Malicious => "malicious",
CustomerState::RansomwareLinked => "ransomware_linked",
CustomerState::ActivelyExploited => "actively_exploited",
CustomerState::Vulnerable => "vulnerable",
CustomerState::NotYetAssessed => "not_yet_assessed",
CustomerState::Clean => "clean",
}
}
pub fn label(self) -> &'static str {
match self {
CustomerState::BlockedByPolicy => "Blocked by policy",
CustomerState::Compromised => "Compromised",
CustomerState::Malicious => "Malicious",
CustomerState::RansomwareLinked => "Ransomware-linked",
CustomerState::ActivelyExploited => "Actively exploited",
CustomerState::Vulnerable => "Vulnerable",
CustomerState::NotYetAssessed => "Not yet assessed",
CustomerState::Clean => "Clean",
}
}
pub fn copy(self) -> &'static str {
match self {
CustomerState::BlockedByPolicy => {
"Blocked by your organization's policy. Contact your security \
owner or request an exception."
}
CustomerState::Compromised => {
"This version was tampered with in a supply-chain attack. Do \
not install — use a known-good version."
}
CustomerState::Malicious => {
"Known-malicious package (malware / typosquat). Do not install."
}
CustomerState::RansomwareLinked => {
"Carries a vulnerability tied to active ransomware campaigns. \
Upgrade before shipping."
}
CustomerState::ActivelyExploited => {
"Has a vulnerability that's being exploited in the wild (on the \
U.S. CISA exploited list). Upgrade urgently."
}
CustomerState::Vulnerable => {
"Has known vulnerabilities (CVEs). Review and upgrade to a \
fixed version."
}
CustomerState::NotYetAssessed => {
"No verdict on file yet — treat as needs-review until assessed."
}
CustomerState::Clean => "No known issues found.",
}
}
pub fn tier(self) -> Tier {
match self {
CustomerState::BlockedByPolicy
| CustomerState::Compromised
| CustomerState::Malicious
| CustomerState::RansomwareLinked
| CustomerState::ActivelyExploited => Tier::Block,
CustomerState::Vulnerable | CustomerState::NotYetAssessed => Tier::Warn,
CustomerState::Clean => Tier::Clean,
}
}
pub fn exit_code(self) -> i32 {
self.tier().exit_code()
}
pub fn color_token(self) -> &'static str {
match self {
CustomerState::BlockedByPolicy => "--cl-policy",
CustomerState::Compromised => "--cl-compromised",
CustomerState::Malicious => "--cl-malicious",
CustomerState::RansomwareLinked => "--cl-ransomware",
CustomerState::ActivelyExploited => "--cl-exploited",
CustomerState::Vulnerable => "--cl-vulnerable",
CustomerState::NotYetAssessed => "--cl-unknown",
CustomerState::Clean => "--cl-clean",
}
}
pub fn color_hex(self) -> &'static str {
match self {
CustomerState::BlockedByPolicy => "#C77DBB",
CustomerState::Compromised => "#D16D6A",
CustomerState::Malicious => "#D9534F",
CustomerState::RansomwareLinked => "#DB7B57",
CustomerState::ActivelyExploited => "#E0934A",
CustomerState::Vulnerable => "#D9A441",
CustomerState::NotYetAssessed => "#7C8696",
CustomerState::Clean => "#50C0E0",
}
}
pub fn emoji(self) -> &'static str {
match self {
CustomerState::BlockedByPolicy => "🛑",
CustomerState::Compromised => "⛔",
CustomerState::Malicious => "⛔",
CustomerState::RansomwareLinked => "🔴",
CustomerState::ActivelyExploited => "🔴",
CustomerState::Vulnerable => "⚠️",
CustomerState::NotYetAssessed => "❔",
CustomerState::Clean => "✅",
}
}
pub fn all() -> [CustomerState; 8] {
[
CustomerState::BlockedByPolicy,
CustomerState::Compromised,
CustomerState::Malicious,
CustomerState::RansomwareLinked,
CustomerState::ActivelyExploited,
CustomerState::Vulnerable,
CustomerState::NotYetAssessed,
CustomerState::Clean,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::envelope::ALL_VERDICT_SOURCES;
#[test]
fn wire_to_state_mapping_is_canonical() {
use CustomerState::*;
assert_eq!(CustomerState::from_wire("ALLOWED_NO_FINDINGS"), Clean);
assert_eq!(CustomerState::from_wire("CVE_FINDING"), Vulnerable);
assert_eq!(CustomerState::from_wire("CVE_FINDING_ON_KEV"), ActivelyExploited);
assert_eq!(CustomerState::from_wire("CVE_FINDING_ON_RANSOMWARE"), RansomwareLinked);
assert_eq!(CustomerState::from_wire("MALICIOUS_TRIAGE"), Malicious);
assert_eq!(CustomerState::from_wire("DM_THRESHOLD_BLOCK"), BlockedByPolicy);
assert_eq!(CustomerState::from_wire("INSUFFICIENT_DATA"), NotYetAssessed);
assert_eq!(CustomerState::from_wire("VECTOR_VERDICT"), Vulnerable);
}
#[test]
fn every_known_wire_source_maps_without_falling_through() {
for &src in ALL_VERDICT_SOURCES {
let state = CustomerState::from_wire(src);
if src != "ALLOWED_NO_FINDINGS" {
assert_ne!(
state,
CustomerState::Clean,
"known non-clean source {src} mapped to Clean"
);
}
}
}
#[test]
fn unknown_source_fails_closed_to_needs_review() {
assert_eq!(CustomerState::from_wire("SOME_FUTURE_VARIANT"), CustomerState::NotYetAssessed);
assert_eq!(CustomerState::from_wire(""), CustomerState::NotYetAssessed);
assert_ne!(CustomerState::from_wire("SOME_FUTURE_VARIANT"), CustomerState::Clean);
}
#[test]
fn curated_compromise_routes_dm_block_to_compromised() {
assert_eq!(
CustomerState::from_block_origin("DM_THRESHOLD_BLOCK", true),
CustomerState::Compromised
);
assert_eq!(
CustomerState::from_block_origin("DM_THRESHOLD_BLOCK", false),
CustomerState::BlockedByPolicy
);
assert_eq!(
CustomerState::from_block_origin("CVE_FINDING", true),
CustomerState::Vulnerable
);
}
#[test]
fn tier_and_exit_codes() {
assert_eq!(CustomerState::Clean.exit_code(), 0);
assert_eq!(CustomerState::Vulnerable.exit_code(), 2);
assert_eq!(CustomerState::NotYetAssessed.exit_code(), 2); for s in [
CustomerState::ActivelyExploited,
CustomerState::RansomwareLinked,
CustomerState::Malicious,
CustomerState::Compromised,
CustomerState::BlockedByPolicy,
] {
assert_eq!(s.exit_code(), 1, "{s:?} must be block-tier");
}
}
#[test]
fn no_internal_codenames_in_customer_strings() {
let banned = [
"Vector", "VECTOR", "Tricorder", "DmThreshold", "DM_THRESHOLD",
"CveFinding", "CVE_FINDING", "MaliciousTriage", "MALICIOUS_TRIAGE",
"InsufficientData", "INSUFFICIENT_DATA", "vuln_xref", "mock", "fixture",
];
for s in CustomerState::all() {
for text in [s.label(), s.copy()] {
for b in banned {
assert!(
!text.contains(b),
"codename {b:?} leaked into customer string {text:?} for {s:?}"
);
}
}
}
}
#[test]
fn no_raw_tier_words_in_customer_copy() {
for s in CustomerState::all() {
for b in ["DENY", "WARN", "ALLOW"] {
assert!(!s.copy().contains(b), "raw tier word {b} in copy for {s:?}");
assert!(!s.label().contains(b), "raw tier word {b} in label for {s:?}");
}
}
}
#[test]
fn every_state_has_complete_token_set() {
for s in CustomerState::all() {
assert!(!s.as_str().is_empty());
assert!(!s.label().is_empty());
assert!(!s.copy().is_empty());
assert!(s.color_token().starts_with("--cl-"));
assert!(s.color_hex().starts_with('#') && s.color_hex().len() == 7);
assert!(!s.emoji().is_empty());
}
}
#[test]
fn clean_uses_brand_cyan_others_do_not() {
assert_eq!(CustomerState::Clean.color_hex(), "#50C0E0");
for s in CustomerState::all() {
if s != CustomerState::Clean {
assert_ne!(s.color_hex(), "#50C0E0", "{s:?} must not reuse brand cyan");
}
}
}
#[test]
fn machine_strings_are_unique() {
let mut seen = std::collections::HashSet::new();
for s in CustomerState::all() {
assert!(seen.insert(s.as_str()), "duplicate state string {}", s.as_str());
}
}
}