1use crate::graph::NodeId;
2use crate::propagation::PropagationPath;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum Severity {
8 Critical,
9 High,
10 Medium,
11 Low,
12 Info,
13}
14
15impl Severity {
16 fn rank(self) -> u8 {
17 match self {
18 Severity::Critical => 0,
19 Severity::High => 1,
20 Severity::Medium => 2,
21 Severity::Low => 3,
22 Severity::Info => 4,
23 }
24 }
25}
26
27impl Ord for Severity {
28 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
29 self.rank().cmp(&other.rank())
30 }
31}
32
33impl PartialOrd for Severity {
34 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
35 Some(self.cmp(other))
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum FindingCategory {
44 AuthorityPropagation,
46 OverPrivilegedIdentity,
47 UnpinnedAction,
48 UntrustedWithAuthority,
49 ArtifactBoundaryCrossing,
50 FloatingImage,
52 LongLivedCredential,
53 PersistedCredential,
57 #[doc(hidden)]
60 EgressBlindspot,
61 #[doc(hidden)]
63 MissingAuditTrail,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(tag = "type", rename_all = "snake_case")]
69pub enum Recommendation {
70 TsafeRemediation {
71 command: String,
72 explanation: String,
73 },
74 CellosRemediation {
75 reason: String,
76 spec_hint: String,
77 },
78 PinAction {
79 current: String,
80 pinned: String,
81 },
82 ReducePermissions {
83 current: String,
84 minimum: String,
85 },
86 FederateIdentity {
87 static_secret: String,
88 oidc_provider: String,
89 },
90 Manual {
91 action: String,
92 },
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct Finding {
98 pub severity: Severity,
99 pub category: FindingCategory,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub path: Option<PropagationPath>,
102 pub nodes_involved: Vec<NodeId>,
103 pub message: String,
104 pub recommendation: Recommendation,
105}