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 EgressBlindspot,
52 MissingAuditTrail,
53 FloatingImage,
54 LongLivedCredential,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(tag = "type", rename_all = "snake_case")]
60pub enum Recommendation {
61 TsafeRemediation {
62 command: String,
63 explanation: String,
64 },
65 CellosRemediation {
66 reason: String,
67 spec_hint: String,
68 },
69 PinAction {
70 current: String,
71 pinned: String,
72 },
73 ReducePermissions {
74 current: String,
75 minimum: String,
76 },
77 FederateIdentity {
78 static_secret: String,
79 oidc_provider: String,
80 },
81 Manual {
82 action: String,
83 },
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Finding {
89 pub severity: Severity,
90 pub category: FindingCategory,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub path: Option<PropagationPath>,
93 pub nodes_involved: Vec<NodeId>,
94 pub message: String,
95 pub recommendation: Recommendation,
96}