keyhog_scanner/structured/parsers/
yaml.rs1use super::{line::find_line_number, ExtractedPair};
2
3pub fn parse_k8s_secret(text: &str) -> Vec<ExtractedPair> {
10 let mut pairs = Vec::new();
11 let value: serde_yaml::Value = match serde_yaml::from_str(text) {
12 Ok(v) => v,
13 Err(error) => {
14 tracing::debug!(target: "keyhog::structured", %error, "k8s secret YAML parse failed");
15 return pairs;
16 }
17 };
18
19 if let Some(serde_yaml::Value::Mapping(map)) = value.get("data") {
20 for (k, v) in map {
21 let key = k.as_str().unwrap_or_default();
22 let encoded = v.as_str().unwrap_or_default();
23 if key.is_empty() || encoded.is_empty() {
24 continue;
25 }
26 let decoded = match keyhog_core::encoding::decode_standard_base64(encoded) {
27 Ok(bytes) => String::from_utf8_lossy(&bytes).into_owned(),
28 Err(_) => continue,
29 };
30 let line = find_line_number(text, &format!("{}:", key))
31 .or_else(|| find_line_number(text, encoded))
32 .unwrap_or(1);
33 pairs.push(ExtractedPair {
34 context: key.to_string(),
35 value: decoded,
36 line,
37 });
38 }
39 }
40
41 if let Some(serde_yaml::Value::Mapping(map)) = value.get("stringData") {
42 for (k, v) in map {
43 let key = k.as_str().unwrap_or_default();
44 let secret_value = v.as_str().unwrap_or_default().to_string();
45 if key.is_empty() {
46 continue;
47 }
48 let line = find_line_number(text, key).unwrap_or(1);
49 pairs.push(ExtractedPair {
50 context: key.to_string(),
51 value: secret_value,
52 line,
53 });
54 }
55 }
56
57 pairs
58}
59
60pub fn parse_docker_compose(text: &str) -> Vec<ExtractedPair> {
62 let mut pairs = Vec::new();
63 let value: serde_yaml::Value = match serde_yaml::from_str(text) {
64 Ok(v) => v,
65 Err(error) => {
66 tracing::debug!(target: "keyhog::structured", %error, "docker-compose YAML parse failed");
67 return pairs;
68 }
69 };
70 find_environment_pairs(&value, text, &mut pairs, 0);
71 pairs
72}
73
74const MAX_COMPOSE_DEPTH: usize = 256;
77
78fn find_environment_pairs(
79 value: &serde_yaml::Value,
80 text: &str,
81 pairs: &mut Vec<ExtractedPair>,
82 depth: usize,
83) {
84 if depth >= MAX_COMPOSE_DEPTH {
85 return;
86 }
87 match value {
88 serde_yaml::Value::Mapping(map) => {
89 for (k, v) in map {
90 if k.as_str() == Some("environment") {
91 extract_environment_block(v, text, pairs);
92 } else {
93 find_environment_pairs(v, text, pairs, depth + 1);
94 }
95 }
96 }
97 serde_yaml::Value::Sequence(seq) => {
98 for v in seq {
99 find_environment_pairs(v, text, pairs, depth + 1);
100 }
101 }
102 _ => {}
103 }
104}
105
106fn extract_environment_block(
107 value: &serde_yaml::Value,
108 text: &str,
109 pairs: &mut Vec<ExtractedPair>,
110) {
111 match value {
112 serde_yaml::Value::Mapping(map) => {
113 for (k, v) in map {
114 let key = k.as_str().unwrap_or_default();
115 let val = v.as_str().unwrap_or_default().to_string();
116 if key.is_empty() {
117 continue;
118 }
119 let line = find_line_number(text, key).unwrap_or(1);
120 pairs.push(ExtractedPair {
121 context: key.to_string(),
122 value: val,
123 line,
124 });
125 }
126 }
127 serde_yaml::Value::Sequence(seq) => {
128 for item in seq {
129 if let Some(s) = item.as_str() {
130 if let Some((key, val)) = s.split_once('=') {
131 if key.is_empty() {
132 continue;
133 }
134 let line = find_line_number(text, s).unwrap_or(1);
135 pairs.push(ExtractedPair {
136 context: key.to_string(),
137 value: val.to_string(),
138 line,
139 });
140 }
141 }
142 }
143 }
144 _ => {}
145 }
146}