Skip to content

Commit 74731a2

Browse files
fix: bump MSRV to 1.82, fmt + clippy cleanup
Update rust-version from 1.75 to 1.82. Add #![allow(dead_code)] to WIP modules (federated, intel, ml). Fix clamp pattern, suppress await_holding_lock and new_ret_no_self warnings.
1 parent 26c53e2 commit 74731a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1332
-1011
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "zentinel-agent-waf"
33
version = "0.2.0"
44
edition = "2021"
5-
rust-version = "1.75"
5+
rust-version = "1.82"
66
authors = ["Zentinel Contributors"]
77
license = "Apache-2.0"
88
repository = "https://github.com/zentinelproxy/zentinel-agent-waf"

benches/waf_benchmark.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
//! - <50MB steady state memory
66
77
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
8-
use zentinel_agent_waf::{WafConfig, WafEngine};
98
use std::collections::HashMap;
9+
use zentinel_agent_waf::{WafConfig, WafEngine};
1010

1111
/// Generate realistic test payloads
1212
fn generate_payloads() -> Vec<(&'static str, String)> {
@@ -21,7 +21,10 @@ fn generate_payloads() -> Vec<(&'static str, String)> {
2121
"1'/**/UNION/**/SELECT/**/password/**/FROM/**/users--".to_string(),
2222
),
2323
("xss_simple", "<script>alert(1)</script>".to_string()),
24-
("xss_encoded", "%3Cscript%3Ealert(1)%3C/script%3E".to_string()),
24+
(
25+
"xss_encoded",
26+
"%3Cscript%3Ealert(1)%3C/script%3E".to_string(),
27+
),
2528
("xss_event", "<img src=x onerror=alert(1)>".to_string()),
2629
("path_traversal", "../../etc/passwd".to_string()),
2730
("cmd_injection", "; cat /etc/passwd".to_string()),
@@ -187,11 +190,9 @@ fn benchmark_body_sizes(c: &mut Criterion) {
187190
let body = generate_body_with_attack(size);
188191

189192
group.throughput(Throughput::Bytes(size as u64));
190-
group.bench_with_input(
191-
BenchmarkId::new("bytes", size),
192-
&body,
193-
|b, input| b.iter(|| engine.check(black_box(input), black_box("body"))),
194-
);
193+
group.bench_with_input(BenchmarkId::new("bytes", size), &body, |b, input| {
194+
b.iter(|| engine.check(black_box(input), black_box("body")))
195+
});
195196
}
196197

197198
group.finish();
@@ -324,12 +325,10 @@ fn benchmark_throughput(c: &mut Criterion) {
324325

325326
// Measure raw requests per second with typical payload
326327
let query = "search=laptop&category=electronics&page=1";
327-
let headers: HashMap<String, Vec<String>> = [(
328-
"User-Agent".to_string(),
329-
vec!["Mozilla/5.0".to_string()],
330-
)]
331-
.into_iter()
332-
.collect();
328+
let headers: HashMap<String, Vec<String>> =
329+
[("User-Agent".to_string(), vec!["Mozilla/5.0".to_string()])]
330+
.into_iter()
331+
.collect();
333332

334333
group.bench_function("requests_per_sec", |b| {
335334
b.iter(|| {

examples/memory_profile.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
//!
33
//! Measures steady-state memory usage for different configurations.
44
5-
use zentinel_agent_waf::{WafConfig, WafEngine};
65
use std::alloc::{GlobalAlloc, Layout, System};
76
use std::collections::HashMap;
87
use std::sync::atomic::{AtomicUsize, Ordering};
8+
use zentinel_agent_waf::{WafConfig, WafEngine};
99

1010
fn separator(char: char, width: usize) {
11-
println!("{}", std::iter::repeat(char).take(width).collect::<String>());
11+
println!(
12+
"{}",
13+
std::iter::repeat(char).take(width).collect::<String>()
14+
);
1215
}
1316

1417
// Custom allocator to track memory usage
@@ -128,25 +131,16 @@ fn main() {
128131
" All features (PL4): {:.2} MB steady state",
129132
bytes_to_mb(after - before)
130133
);
131-
println!(
132-
" Peak during init: {:.2} MB",
133-
bytes_to_mb(peak - before)
134-
);
134+
println!(" Peak during init: {:.2} MB", bytes_to_mb(peak - before));
135135

136136
// Simulate request processing
137137
println!();
138138
println!("Memory During Request Processing:");
139139
separator('-', 40);
140140

141141
let headers: HashMap<String, Vec<String>> = [
142-
(
143-
"User-Agent".to_string(),
144-
vec!["Mozilla/5.0".to_string()],
145-
),
146-
(
147-
"Cookie".to_string(),
148-
vec!["session=abc123".to_string()],
149-
),
142+
("User-Agent".to_string(), vec!["Mozilla/5.0".to_string()]),
143+
("Cookie".to_string(), vec!["session=abc123".to_string()]),
150144
]
151145
.into_iter()
152146
.collect();
@@ -181,7 +175,10 @@ fn main() {
181175
let total = ALLOCATED.load(Ordering::SeqCst);
182176
let total_peak = PEAK.load(Ordering::SeqCst);
183177
println!(" Total current allocation: {:.2} MB", bytes_to_mb(total));
184-
println!(" Total peak allocation: {:.2} MB", bytes_to_mb(total_peak));
178+
println!(
179+
" Total peak allocation: {:.2} MB",
180+
bytes_to_mb(total_peak)
181+
);
185182
println!();
186183

187184
let target = 50.0;

src/api/graphql.rs

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,23 @@ static INTROSPECTION_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
6868
Regex::new(r"(?i)(__schema|__type)\s*[\{\(]|__typename\b").unwrap()
6969
});
7070

71-
static QUERY_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
72-
Regex::new(r"(?i)(query|mutation|subscription)\s*[\w]*\s*[\(\{]").unwrap()
73-
});
71+
static QUERY_PATTERN: LazyLock<Regex> =
72+
LazyLock::new(|| Regex::new(r"(?i)(query|mutation|subscription)\s*[\w]*\s*[\(\{]").unwrap());
7473

75-
static DIRECTIVE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
76-
Regex::new(r"@\w+").unwrap()
77-
});
74+
static DIRECTIVE_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"@\w+").unwrap());
7875

7976
static INJECTION_PATTERNS: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
8077
vec![
8178
// SQL-like patterns in variables
82-
(Regex::new(r#""\s*;\s*(DROP|DELETE|UPDATE|INSERT)"#).unwrap(), "sql-in-variable"),
79+
(
80+
Regex::new(r#""\s*;\s*(DROP|DELETE|UPDATE|INSERT)"#).unwrap(),
81+
"sql-in-variable",
82+
),
8383
// Script injection in string values
84-
(Regex::new(r#""[^"]*<script"#).unwrap(), "script-in-variable"),
84+
(
85+
Regex::new(r#""[^"]*<script"#).unwrap(),
86+
"script-in-variable",
87+
),
8588
// Template injection
8689
(Regex::new(r#"\$\{\{.*\}\}"#).unwrap(), "template-injection"),
8790
// NoSQL injection patterns
@@ -174,7 +177,11 @@ impl GraphQLInspector {
174177
matched_value: format!("depth={} (max={})", depth, self.config.max_depth),
175178
location: "body".to_string(),
176179
base_score: 7,
177-
tags: vec!["graphql".to_string(), "dos".to_string(), "depth".to_string()],
180+
tags: vec![
181+
"graphql".to_string(),
182+
"dos".to_string(),
183+
"depth".to_string(),
184+
],
178185
})
179186
} else {
180187
None
@@ -192,7 +199,11 @@ impl GraphQLInspector {
192199
matched_value: format!("fields={} (max={})", count, self.config.max_fields),
193200
location: "body".to_string(),
194201
base_score: 6,
195-
tags: vec!["graphql".to_string(), "dos".to_string(), "fields".to_string()],
202+
tags: vec![
203+
"graphql".to_string(),
204+
"dos".to_string(),
205+
"fields".to_string(),
206+
],
196207
})
197208
} else {
198209
None
@@ -210,10 +221,17 @@ impl GraphQLInspector {
210221
rule_id: 98004,
211222
rule_name: "GraphQL Batch Query Abuse".to_string(),
212223
attack_type: AttackType::ProtocolAttack,
213-
matched_value: format!("batch_size={} (max={})", count, self.config.max_batch_size),
224+
matched_value: format!(
225+
"batch_size={} (max={})",
226+
count, self.config.max_batch_size
227+
),
214228
location: "body".to_string(),
215229
base_score: 6,
216-
tags: vec!["graphql".to_string(), "dos".to_string(), "batch".to_string()],
230+
tags: vec![
231+
"graphql".to_string(),
232+
"dos".to_string(),
233+
"batch".to_string(),
234+
],
217235
});
218236
}
219237
}
@@ -312,7 +330,16 @@ fn calculate_depth(body: &str) -> usize {
312330
/// Count the number of fields in a GraphQL query (approximation)
313331
fn count_fields(body: &str) -> usize {
314332
// Simple heuristic: count identifiers that aren't keywords
315-
let keywords = ["query", "mutation", "subscription", "fragment", "on", "true", "false", "null"];
333+
let keywords = [
334+
"query",
335+
"mutation",
336+
"subscription",
337+
"fragment",
338+
"on",
339+
"true",
340+
"false",
341+
"null",
342+
];
316343

317344
let mut count = 0;
318345
let mut in_string = false;
@@ -330,14 +357,12 @@ fn count_fields(body: &str) -> usize {
330357

331358
if ch.is_alphanumeric() || ch == '_' {
332359
current_word.push(ch);
333-
} else {
334-
if !current_word.is_empty() {
335-
let word_lower = current_word.to_lowercase();
336-
if !keywords.contains(&word_lower.as_str()) && !current_word.starts_with('$') {
337-
count += 1;
338-
}
339-
current_word.clear();
360+
} else if !current_word.is_empty() {
361+
let word_lower = current_word.to_lowercase();
362+
if !keywords.contains(&word_lower.as_str()) && !current_word.starts_with('$') {
363+
count += 1;
340364
}
365+
current_word.clear();
341366
}
342367
}
343368

src/api/json.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,19 @@ static NOSQL_PATTERNS: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
4949
(Regex::new(r#""\$exists"\s*:"#).unwrap(), "$exists operator"),
5050
(Regex::new(r#""\$type"\s*:"#).unwrap(), "$type operator"),
5151
(Regex::new(r#""\$expr"\s*:"#).unwrap(), "$expr operator"),
52-
(Regex::new(r#""\$jsonSchema"\s*:"#).unwrap(), "$jsonSchema operator"),
52+
(
53+
Regex::new(r#""\$jsonSchema"\s*:"#).unwrap(),
54+
"$jsonSchema operator",
55+
),
5356
// Function injection
54-
(Regex::new(r#""\$function"\s*:"#).unwrap(), "$function operator"),
55-
(Regex::new(r#""\$accumulator"\s*:"#).unwrap(), "$accumulator operator"),
57+
(
58+
Regex::new(r#""\$function"\s*:"#).unwrap(),
59+
"$function operator",
60+
),
61+
(
62+
Regex::new(r#""\$accumulator"\s*:"#).unwrap(),
63+
"$accumulator operator",
64+
),
5665
]
5766
});
5867

@@ -75,7 +84,10 @@ static SUSPICIOUS_KEYS: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(||
7584
(Regex::new(r#""permissions"\s*:"#).unwrap(), "permissions"),
7685
(Regex::new(r#""privileges"\s*:"#).unwrap(), "privileges"),
7786
(Regex::new(r#""password"\s*:"#).unwrap(), "password"),
78-
(Regex::new(r#""password_hash"\s*:"#).unwrap(), "password_hash"),
87+
(
88+
Regex::new(r#""password_hash"\s*:"#).unwrap(),
89+
"password_hash",
90+
),
7991
(Regex::new(r#""api_key"\s*:"#).unwrap(), "api_key"),
8092
(Regex::new(r#""secret"\s*:"#).unwrap(), "secret"),
8193
(Regex::new(r#""internal"\s*:"#).unwrap(), "internal"),
@@ -129,7 +141,11 @@ impl JsonInspector {
129141
matched_value: m.as_str().to_string(),
130142
location: "body".to_string(),
131143
base_score: 8,
132-
tags: vec!["json".to_string(), "nosql".to_string(), "injection".to_string()],
144+
tags: vec![
145+
"json".to_string(),
146+
"nosql".to_string(),
147+
"injection".to_string(),
148+
],
133149
});
134150
}
135151
}
@@ -261,7 +277,9 @@ mod tests {
261277

262278
let where_injection = r#"{"$where": "this.password == 'test'"}"#;
263279
let detections = inspector.inspect(where_injection);
264-
assert!(detections.iter().any(|d| d.matched_value.contains("$where")));
280+
assert!(detections
281+
.iter()
282+
.any(|d| d.matched_value.contains("$where")));
265283
}
266284

267285
#[test]

src/api/jwt.rs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -105,22 +105,25 @@ impl JwtInspector {
105105
let mut detections = Vec::new();
106106

107107
// Check for "none" algorithm
108-
if self.config.block_none_algorithm {
109-
if header.contains(r#""alg":"none""#)
108+
if self.config.block_none_algorithm
109+
&& (header.contains(r#""alg":"none""#)
110110
|| header.contains(r#""alg": "none""#)
111111
|| header.contains(r#""alg":"None""#)
112-
|| header.contains(r#""alg":"NONE""#)
113-
{
114-
detections.push(Detection {
115-
rule_id: 98200,
116-
rule_name: "JWT None Algorithm Attack".to_string(),
117-
attack_type: AttackType::ProtocolAttack,
118-
matched_value: truncate(token, 50),
119-
location: "header:Authorization".to_string(),
120-
base_score: 9,
121-
tags: vec!["jwt".to_string(), "algorithm".to_string(), "none".to_string()],
122-
});
123-
}
112+
|| header.contains(r#""alg":"NONE""#))
113+
{
114+
detections.push(Detection {
115+
rule_id: 98200,
116+
rule_name: "JWT None Algorithm Attack".to_string(),
117+
attack_type: AttackType::ProtocolAttack,
118+
matched_value: truncate(token, 50),
119+
location: "header:Authorization".to_string(),
120+
base_score: 9,
121+
tags: vec![
122+
"jwt".to_string(),
123+
"algorithm".to_string(),
124+
"none".to_string(),
125+
],
126+
});
124127
}
125128

126129
// Check for weak algorithms
@@ -137,7 +140,11 @@ impl JwtInspector {
137140
matched_value: truncate(token, 50),
138141
location: "header:Authorization".to_string(),
139142
base_score: 6,
140-
tags: vec!["jwt".to_string(), "algorithm".to_string(), "weak".to_string()],
143+
tags: vec![
144+
"jwt".to_string(),
145+
"algorithm".to_string(),
146+
"weak".to_string(),
147+
],
141148
});
142149
break;
143150
}
@@ -223,13 +230,7 @@ impl JwtInspector {
223230
}
224231

225232
// Check for injection in claims
226-
let injection_patterns = [
227-
"<script",
228-
"javascript:",
229-
"' OR ",
230-
"\" OR ",
231-
"; DROP ",
232-
];
233+
let injection_patterns = ["<script", "javascript:", "' OR ", "\" OR ", "; DROP "];
233234

234235
for pattern in injection_patterns {
235236
if payload.to_lowercase().contains(&pattern.to_lowercase()) {
@@ -276,7 +277,9 @@ fn extract_exp_claim(payload: &str) -> Option<u64> {
276277
for pattern in exp_patterns {
277278
if let Some(start) = payload.find(pattern) {
278279
let after_key = &payload[start + pattern.len()..];
279-
let end = after_key.find(|c: char| !c.is_ascii_digit()).unwrap_or(after_key.len());
280+
let end = after_key
281+
.find(|c: char| !c.is_ascii_digit())
282+
.unwrap_or(after_key.len());
280283
if let Ok(exp) = after_key[..end].parse::<u64>() {
281284
return Some(exp);
282285
}
@@ -338,7 +341,9 @@ mod tests {
338341

339342
let detections = inspector.inspect(&format!("Bearer {}", rs256_jwt));
340343
// Should not detect none algorithm or weak algorithm
341-
assert!(!detections.iter().any(|d| d.rule_id == 98200 || d.rule_id == 98201));
344+
assert!(!detections
345+
.iter()
346+
.any(|d| d.rule_id == 98200 || d.rule_id == 98201));
342347
}
343348

344349
#[test]

0 commit comments

Comments
 (0)