#[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,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CustomerState {
BlockedByPolicy,
Compromised,
Malicious,
RansomwareLinked,
ActivelyExploited,
Vulnerable,
RangeNotResolved,
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 from_wire_with_source_state(source: &str, source_state: Option<&str>) -> CustomerState {
match source_state {
Some(s) if s.eq_ignore_ascii_case("RANGE_NOT_RESOLVED") => {
CustomerState::RangeNotResolved
}
Some(s) if !s.is_empty() => CustomerState::from_wire(s),
_ => CustomerState::from_wire(source),
}
}
pub fn from_wire_with_source_state_and_origin(
source: &str,
source_state: Option<&str>,
matched_rule_id: Option<&str>,
) -> CustomerState {
if is_curated_supply_chain_compromise(matched_rule_id) {
let effective_source = match source_state {
Some(s) if !s.is_empty() => s,
_ => source,
};
return CustomerState::from_block_origin(effective_source, true);
}
CustomerState::from_wire_with_source_state(source, source_state)
}
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::RangeNotResolved => "range_not_resolved",
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::RangeNotResolved => "Range not resolved",
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::RangeNotResolved => {
"The requested version range couldn't be resolved to a concrete \
version. Pin an exact version and re-check."
}
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::RangeNotResolved
| CustomerState::NotYetAssessed => Tier::Warn,
CustomerState::Clean => Tier::Clean,
}
}
pub fn exit_code(self) -> i32 {
self.tier().exit_code()
}
pub fn is_needs_input(self) -> bool {
matches!(
self,
CustomerState::NotYetAssessed | CustomerState::RangeNotResolved
)
}
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::RangeNotResolved => "--cl-unknown",
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::RangeNotResolved => "#7C8696",
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::RangeNotResolved => "❔",
CustomerState::NotYetAssessed => "❔",
CustomerState::Clean => "✅",
}
}
pub fn all() -> [CustomerState; 9] {
[
CustomerState::BlockedByPolicy,
CustomerState::Compromised,
CustomerState::Malicious,
CustomerState::RansomwareLinked,
CustomerState::ActivelyExploited,
CustomerState::Vulnerable,
CustomerState::RangeNotResolved,
CustomerState::NotYetAssessed,
CustomerState::Clean,
]
}
}
pub fn is_curated_supply_chain_compromise(matched_rule_id: Option<&str>) -> bool {
matched_rule_id
.map(|id| id.starts_with("supply-chain-compromise-bridge-"))
.unwrap_or(false)
}
#[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 cleanlib_518_range_not_resolved_source_state_maps_distinctly() {
assert_eq!(
CustomerState::from_wire_with_source_state("INSUFFICIENT_DATA", Some("RANGE_NOT_RESOLVED")),
CustomerState::RangeNotResolved
);
assert_eq!(
CustomerState::from_wire_with_source_state("INSUFFICIENT_DATA", Some("range_not_resolved")),
CustomerState::RangeNotResolved
);
assert_eq!(CustomerState::RangeNotResolved.label(), "Range not resolved");
assert_ne!(
CustomerState::RangeNotResolved.label(),
CustomerState::NotYetAssessed.label()
);
assert_eq!(CustomerState::RangeNotResolved.exit_code(), 2);
assert_eq!(
CustomerState::from_wire_with_source_state("CVE_FINDING", None),
CustomerState::Vulnerable
);
assert_eq!(
CustomerState::from_wire_with_source_state("CVE_FINDING", Some("CVE_FINDING")),
CustomerState::Vulnerable
);
}
#[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 needs_input_family_is_exactly_not_yet_assessed_and_range_not_resolved() {
for s in CustomerState::all() {
let expected = matches!(
s,
CustomerState::NotYetAssessed | CustomerState::RangeNotResolved
);
assert_eq!(s.is_needs_input(), expected, "{s:?}");
if s.is_needs_input() {
assert_eq!(s.tier(), Tier::Warn, "{s:?} needs-input must be warn-tier");
assert_eq!(s.color_token(), "--cl-unknown", "{s:?}");
assert_eq!(s.emoji(), "❔", "{s:?}");
}
}
}
#[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());
}
}
#[test]
fn cleanlib_657_cx8_only_clean_is_allowed_tier() {
use CustomerState::*;
let all = [
BlockedByPolicy, Compromised, Malicious, RansomwareLinked,
ActivelyExploited, Vulnerable, RangeNotResolved, NotYetAssessed, Clean,
];
for s in all {
if matches!(s, Clean) {
assert_eq!(s.tier(), Tier::Clean, "Clean must be allowed-tier");
} else {
assert_ne!(s.tier(), Tier::Clean, "{s:?} must NEVER be allowed (Clean) tier");
}
}
}
#[test]
fn cleanlib_855_is_curated_supply_chain_compromise_matches_the_real_bridge_prefix() {
assert!(is_curated_supply_chain_compromise(Some(
"supply-chain-compromise-bridge-event-stream-2018-mh6f-8j2x-4483"
)));
assert!(is_curated_supply_chain_compromise(Some(
"supply-chain-compromise-bridge-ctx-2022-67r3-h899-9w95-compromised"
)));
assert!(!is_curated_supply_chain_compromise(Some("rule_cors_deny_old")));
assert!(!is_curated_supply_chain_compromise(None));
assert!(!is_curated_supply_chain_compromise(Some("")));
}
#[test]
fn cleanlib_855_from_wire_with_source_state_and_origin_renders_compromised() {
let state = CustomerState::from_wire_with_source_state_and_origin(
"DM_THRESHOLD_BLOCK",
None,
Some("supply-chain-compromise-bridge-event-stream-2018-mh6f-8j2x-4483"),
);
assert_eq!(state, CustomerState::Compromised);
assert_eq!(state.label(), "Compromised");
}
#[test]
fn cleanlib_855_from_wire_with_source_state_and_origin_ordinary_policy_denies_unaffected() {
let state = CustomerState::from_wire_with_source_state_and_origin(
"DM_THRESHOLD_BLOCK",
None,
Some("rule_cors_deny_old"),
);
assert_eq!(state, CustomerState::BlockedByPolicy);
}
#[test]
fn cleanlib_855_from_wire_with_source_state_and_origin_none_matches_plain_from_wire() {
for src in ALL_VERDICT_SOURCES {
assert_eq!(
CustomerState::from_wire_with_source_state_and_origin(src, None, None),
CustomerState::from_wire_with_source_state(src, None),
"no matched_rule_id must never change behavior for source {src}"
);
}
}
#[test]
fn cleanlib_855_curated_compromise_never_fires_on_a_non_block_source() {
let state = CustomerState::from_wire_with_source_state_and_origin(
"CVE_FINDING",
None,
Some("supply-chain-compromise-bridge-whatever"),
);
assert_eq!(state, CustomerState::Vulnerable);
}
#[test]
fn cleanlib_855_source_state_takes_precedence_over_source_for_origin_resolution() {
let state = CustomerState::from_wire_with_source_state_and_origin(
"VECTOR_VERDICT", Some("DM_THRESHOLD_BLOCK"),
Some("supply-chain-compromise-bridge-event-stream-2018-mh6f-8j2x-4483"),
);
assert_eq!(state, CustomerState::Compromised);
}
}