pylon_plugin/builtin/
net_guard.rs1pub fn is_private_ip(host: &str) -> bool {
22 let clean_host = if host.starts_with('[') {
24 host.trim_start_matches('[')
26 .split(']')
27 .next()
28 .unwrap_or(host)
29 } else if host.contains("::") || host.matches(':').count() > 1 {
30 host
32 } else {
33 host.rsplit_once(':').map(|(h, _)| h).unwrap_or(host)
35 };
36
37 if clean_host == "localhost" || clean_host == "0.0.0.0" {
39 return true;
40 }
41
42 if clean_host == "::1" || clean_host == "::0" || clean_host == "::" {
44 return true;
45 }
46
47 if let Some(mapped) = clean_host.strip_prefix("::ffff:") {
49 return is_private_ipv4(mapped);
50 }
51
52 if clean_host.starts_with("fc") || clean_host.starts_with("fd") {
54 return true;
55 }
56
57 if clean_host.starts_with("fe80") {
59 return true;
60 }
61
62 is_private_ipv4(clean_host)
64}
65
66fn is_private_ipv4(host: &str) -> bool {
68 let octets: Vec<u8> = host.split('.').filter_map(|s| s.parse().ok()).collect();
69 if octets.len() == 4 {
70 match (octets[0], octets[1]) {
71 (127, _) => true, (10, _) => true, (172, 16..=31) => true, (192, 168) => true, (169, 254) => true, (0, _) => true, _ => false,
78 }
79 } else {
80 false
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
91 fn blocks_localhost() {
92 assert!(is_private_ip("localhost"));
93 assert!(is_private_ip("localhost:8080"));
94 }
95
96 #[test]
97 fn blocks_loopback_127() {
98 assert!(is_private_ip("127.0.0.1"));
99 assert!(is_private_ip("127.0.0.1:80"));
100 assert!(is_private_ip("127.255.255.255"));
101 assert!(is_private_ip("127.0.0.2"));
102 }
103
104 #[test]
105 fn blocks_ipv6_loopback() {
106 assert!(is_private_ip("::1"));
107 assert!(is_private_ip("[::1]:8080"));
108 }
109
110 #[test]
111 fn blocks_ipv6_unspecified() {
112 assert!(is_private_ip("::0"));
113 assert!(is_private_ip("::"));
114 }
115
116 #[test]
117 fn blocks_unspecified() {
118 assert!(is_private_ip("0.0.0.0"));
119 assert!(is_private_ip("0.0.0.0:443"));
120 }
121
122 #[test]
125 fn blocks_10_network() {
126 assert!(is_private_ip("10.0.0.1"));
127 assert!(is_private_ip("10.255.255.255"));
128 assert!(is_private_ip("10.0.0.1:9090"));
129 }
130
131 #[test]
132 fn blocks_172_16_network() {
133 assert!(is_private_ip("172.16.0.1"));
134 assert!(is_private_ip("172.31.255.255"));
135 assert!(is_private_ip("172.20.0.1:443"));
136 }
137
138 #[test]
139 fn allows_172_outside_range() {
140 assert!(!is_private_ip("172.15.0.1"));
141 assert!(!is_private_ip("172.32.0.1"));
142 }
143
144 #[test]
145 fn blocks_192_168_network() {
146 assert!(is_private_ip("192.168.0.1"));
147 assert!(is_private_ip("192.168.1.100:8080"));
148 assert!(is_private_ip("192.168.255.255"));
149 }
150
151 #[test]
154 fn blocks_link_local() {
155 assert!(is_private_ip("169.254.0.1"));
156 assert!(is_private_ip("169.254.169.254")); assert!(is_private_ip("169.254.169.254:80"));
158 }
159
160 #[test]
163 fn blocks_zero_network() {
164 assert!(is_private_ip("0.1.2.3"));
165 assert!(is_private_ip("0.255.255.255"));
166 }
167
168 #[test]
171 fn blocks_ipv4_mapped_ipv6() {
172 assert!(is_private_ip("::ffff:127.0.0.1"));
173 assert!(is_private_ip("::ffff:10.0.0.1"));
174 assert!(is_private_ip("::ffff:192.168.1.1"));
175 }
176
177 #[test]
178 fn allows_ipv4_mapped_ipv6_public() {
179 assert!(!is_private_ip("::ffff:8.8.8.8"));
180 }
181
182 #[test]
183 fn blocks_ipv6_unique_local() {
184 assert!(is_private_ip("fc00::1"));
185 assert!(is_private_ip("fd12::1"));
186 assert!(is_private_ip("fdab:cdef::1"));
187 }
188
189 #[test]
190 fn blocks_ipv6_link_local() {
191 assert!(is_private_ip("fe80::1"));
192 assert!(is_private_ip("fe80::abcd:1234"));
193 }
194
195 #[test]
196 fn blocks_bracketed_ipv6_with_port() {
197 assert!(is_private_ip("[::1]:8080"));
198 assert!(is_private_ip("[fc00::1]:443"));
199 assert!(is_private_ip("[fe80::1]:80"));
200 }
201
202 #[test]
205 fn allows_public_ips() {
206 assert!(!is_private_ip("8.8.8.8"));
207 assert!(!is_private_ip("1.1.1.1"));
208 assert!(!is_private_ip("203.0.113.1"));
209 assert!(!is_private_ip("93.184.216.34:443"));
210 }
211
212 #[test]
213 fn allows_public_ipv6() {
214 assert!(!is_private_ip("2001:db8::1"));
216 }
217
218 #[test]
219 fn allows_public_hostnames() {
220 assert!(!is_private_ip("example.com"));
221 assert!(!is_private_ip("example.com:443"));
222 assert!(!is_private_ip("api.github.com"));
223 }
224}