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 TriggerContextMismatch,
59 CrossWorkflowAuthorityChain,
61 AuthorityCycle,
63 UpliftWithoutAttestation,
65 SelfMutatingPipeline,
67 CheckoutSelfPrExposure,
69 VariableGroupInPrJob,
71 SelfHostedPoolPrHijack,
73 ServiceConnectionScopeMismatch,
75 #[doc(hidden)]
78 EgressBlindspot,
79 #[doc(hidden)]
81 MissingAuditTrail,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(tag = "type", rename_all = "snake_case")]
87pub enum Recommendation {
88 TsafeRemediation {
89 command: String,
90 explanation: String,
91 },
92 CellosRemediation {
93 reason: String,
94 spec_hint: String,
95 },
96 PinAction {
97 current: String,
98 pinned: String,
99 },
100 ReducePermissions {
101 current: String,
102 minimum: String,
103 },
104 FederateIdentity {
105 static_secret: String,
106 oidc_provider: String,
107 },
108 Manual {
109 action: String,
110 },
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct Finding {
116 pub severity: Severity,
117 pub category: FindingCategory,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub path: Option<PropagationPath>,
120 pub nodes_involved: Vec<NodeId>,
121 pub message: String,
122 pub recommendation: Recommendation,
123}