Skip to main content

pjson_rs/
security.rs

1//! Security validation utilities
2
3use crate::{
4    config::SecurityConfig,
5    error::{Error, Result},
6};
7
8pub mod compression_bomb;
9pub mod rate_limit;
10
11pub use compression_bomb::{
12    CompressionBombConfig, CompressionBombDetector, CompressionBombError, CompressionBombProtector,
13    CompressionStats,
14};
15pub use rate_limit::{
16    RateLimitConfig, RateLimitError, RateLimitGuard, RateLimitStats, WebSocketRateLimiter,
17};
18
19/// Security validator with configuration-based limits
20#[derive(Debug, Clone)]
21pub struct SecurityValidator {
22    config: SecurityConfig,
23}
24
25impl SecurityValidator {
26    /// Create new validator with given security configuration
27    pub fn new(config: SecurityConfig) -> Self {
28        Self { config }
29    }
30
31    /// Validate input size against security limits
32    pub fn validate_input_size(&self, size: usize) -> Result<()> {
33        if size > self.config.json.max_input_size {
34            return Err(Error::Other(format!(
35                "Input size {} exceeds maximum allowed {} bytes",
36                size, self.config.json.max_input_size
37            )));
38        }
39        Ok(())
40    }
41
42    /// Validate JSON depth to prevent stack overflow
43    pub fn validate_json_depth(&self, depth: usize) -> Result<()> {
44        if depth > self.config.json.max_depth {
45            return Err(Error::Other(format!(
46                "JSON nesting depth {} exceeds maximum allowed {}",
47                depth, self.config.json.max_depth
48            )));
49        }
50        Ok(())
51    }
52
53    /// Validate JSON nesting depth by scanning raw bytes ahead of parsing.
54    ///
55    /// Tracks in-string state (a `"` toggles it, a `\` escapes the following
56    /// byte) so that `{`/`}`/`[`/`]` bytes inside string literals are never
57    /// mistaken for structural delimiters — matching the JSON grammar's own
58    /// definition of nesting. Intended as a cheap pre-parse guard shared by
59    /// every JSON entry point, so a parser backend without its own recursion
60    /// limit (e.g. sonic-rs) cannot be driven into unbounded recursion by a
61    /// payload whose *reported* depth was deflated via crafted string content.
62    pub fn validate_json_depth_bytes(&self, input: &[u8]) -> Result<()> {
63        let mut depth: usize = 0;
64        let mut max_depth: usize = 0;
65        let mut in_string = false;
66        let mut escaped = false;
67
68        for &byte in input {
69            if in_string {
70                if escaped {
71                    escaped = false;
72                } else if byte == b'\\' {
73                    escaped = true;
74                } else if byte == b'"' {
75                    in_string = false;
76                }
77                continue;
78            }
79
80            match byte {
81                b'"' => in_string = true,
82                b'{' | b'[' => {
83                    depth += 1;
84                    max_depth = max_depth.max(depth);
85                    self.validate_json_depth(max_depth)?;
86                }
87                b'}' | b']' => depth = depth.saturating_sub(1),
88                _ => {}
89            }
90        }
91
92        Ok(())
93    }
94
95    /// Validate array length
96    pub fn validate_array_length(&self, length: usize) -> Result<()> {
97        if length > self.config.json.max_array_length {
98            return Err(Error::Other(format!(
99                "Array length {} exceeds maximum allowed {}",
100                length, self.config.json.max_array_length
101            )));
102        }
103        Ok(())
104    }
105
106    /// Validate object key count
107    pub fn validate_object_keys(&self, key_count: usize) -> Result<()> {
108        if key_count > self.config.json.max_object_keys {
109            return Err(Error::Other(format!(
110                "Object key count {} exceeds maximum allowed {}",
111                key_count, self.config.json.max_object_keys
112            )));
113        }
114        Ok(())
115    }
116
117    /// Validate string length
118    pub fn validate_string_length(&self, length: usize) -> Result<()> {
119        if length > self.config.json.max_string_length {
120            return Err(Error::Other(format!(
121                "String length {} exceeds maximum allowed {}",
122                length, self.config.json.max_string_length
123            )));
124        }
125        Ok(())
126    }
127
128    /// Validate session ID format and length
129    pub fn validate_session_id(&self, session_id: &str) -> Result<()> {
130        let len = session_id.len();
131
132        if len < self.config.sessions.min_session_id_length {
133            return Err(Error::Other(format!(
134                "Session ID too short: {} characters (minimum {})",
135                len, self.config.sessions.min_session_id_length
136            )));
137        }
138
139        if len > self.config.sessions.max_session_id_length {
140            return Err(Error::Other(format!(
141                "Session ID too long: {} characters (maximum {})",
142                len, self.config.sessions.max_session_id_length
143            )));
144        }
145
146        // Check for valid characters (alphanumeric + hyphens + underscores)
147        if !session_id
148            .chars()
149            .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
150        {
151            return Err(Error::Other(
152                "Session ID contains invalid characters (only alphanumeric, hyphens and underscores allowed)".to_string()
153            ));
154        }
155
156        Ok(())
157    }
158
159    /// Validate WebSocket frame size
160    pub fn validate_websocket_frame_size(&self, size: usize) -> Result<()> {
161        if size > self.config.network.max_websocket_frame_size {
162            return Err(Error::Other(format!(
163                "WebSocket frame size {} exceeds maximum allowed {}",
164                size, self.config.network.max_websocket_frame_size
165            )));
166        }
167        Ok(())
168    }
169
170    /// Validate buffer size for pooling
171    pub fn validate_buffer_size(&self, size: usize) -> Result<()> {
172        if size > self.config.buffers.max_buffer_size {
173            return Err(Error::Other(format!(
174                "Buffer size {} exceeds maximum allowed {}",
175                size, self.config.buffers.max_buffer_size
176            )));
177        }
178        Ok(())
179    }
180}
181
182impl Default for SecurityValidator {
183    fn default() -> Self {
184        Self::new(SecurityConfig::default())
185    }
186}
187
188/// JSON depth tracker for preventing stack overflow
189pub struct DepthTracker {
190    current_depth: usize,
191    max_depth: usize,
192}
193
194impl DepthTracker {
195    /// Create depth tracker from security config
196    pub fn from_config(config: &SecurityConfig) -> Self {
197        Self {
198            current_depth: 0,
199            max_depth: config.json.max_depth,
200        }
201    }
202
203    /// Create a new depth tracker with custom limit
204    pub fn with_max_depth(max_depth: usize) -> Self {
205        Self {
206            current_depth: 0,
207            max_depth,
208        }
209    }
210
211    /// Enter a new nesting level (array/object)
212    pub fn enter(&mut self) -> Result<()> {
213        if self.current_depth >= self.max_depth {
214            return Err(Error::Other(format!(
215                "JSON nesting depth {} would exceed maximum allowed {}",
216                self.current_depth + 1,
217                self.max_depth
218            )));
219        }
220        self.current_depth += 1;
221        Ok(())
222    }
223
224    /// Exit a nesting level
225    pub fn exit(&mut self) {
226        if self.current_depth > 0 {
227            self.current_depth -= 1;
228        }
229    }
230
231    /// Get current depth
232    pub fn current_depth(&self) -> usize {
233        self.current_depth
234    }
235}
236
237impl Default for DepthTracker {
238    fn default() -> Self {
239        Self::with_max_depth(64) // Default depth limit
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_security_validator_default() {
249        let validator = SecurityValidator::default();
250
251        assert!(validator.validate_input_size(1024).is_ok());
252        assert!(validator.validate_json_depth(10).is_ok());
253        assert!(validator.validate_session_id("valid-session-123").is_ok());
254    }
255
256    #[test]
257    fn test_validate_json_depth_bytes_ignores_delimiters_in_strings() {
258        let validator = SecurityValidator::new(SecurityConfig {
259            json: crate::config::security::JsonLimits {
260                max_depth: 3,
261                ..SecurityConfig::default().json
262            },
263            ..SecurityConfig::default()
264        });
265
266        // Real structural depth is 1, but the string content is packed with
267        // brace/bracket bytes that a naive counter would misread as nesting.
268        let json = br#"["{[{[{[{[{[", "}}}}}}}}}}"]"#;
269        assert!(validator.validate_json_depth_bytes(json).is_ok());
270    }
271
272    #[test]
273    fn test_validate_json_depth_bytes_respects_escaped_quotes() {
274        let validator = SecurityValidator::default();
275
276        // `\"` must not toggle out of string state; if it did, the `]`
277        // right after would be seen as a real structural close.
278        let json = br#"["a\"]", [1, 2, 3]]"#;
279        assert!(validator.validate_json_depth_bytes(json).is_ok());
280    }
281
282    #[test]
283    fn test_validate_json_depth_bytes_rejects_real_nesting() {
284        let validator = SecurityValidator::new(SecurityConfig {
285            json: crate::config::security::JsonLimits {
286                max_depth: 3,
287                ..SecurityConfig::default().json
288            },
289            ..SecurityConfig::default()
290        });
291
292        let json = b"[[[[1]]]]"; // 4 levels deep, exceeds limit of 3
293        assert!(validator.validate_json_depth_bytes(json).is_err());
294    }
295
296    #[test]
297    fn test_validate_json_depth_bytes_exactly_at_limit_is_ok() {
298        let validator = SecurityValidator::new(SecurityConfig {
299            json: crate::config::security::JsonLimits {
300                max_depth: 3,
301                ..SecurityConfig::default().json
302            },
303            ..SecurityConfig::default()
304        });
305
306        let json = b"[[[1]]]"; // exactly 3 levels deep
307        assert!(validator.validate_json_depth_bytes(json).is_ok());
308
309        let json_over = b"[[[[1]]]]"; // 4 levels: one over the limit
310        assert!(validator.validate_json_depth_bytes(json_over).is_err());
311    }
312
313    #[test]
314    fn test_validate_json_depth_bytes_trailing_lone_backslash_does_not_panic() {
315        let validator = SecurityValidator::default();
316
317        // Malformed/truncated input ending mid-escape must not panic; the
318        // real JSON parser rejects it as a syntax error downstream.
319        let json = br#"["abc\"#;
320        assert!(validator.validate_json_depth_bytes(json).is_ok());
321    }
322
323    #[test]
324    fn test_validate_json_depth_bytes_unterminated_string_does_not_panic() {
325        let validator = SecurityValidator::default();
326
327        // An opening quote with no matching close: everything after it is
328        // treated as string content through EOF, so no depth is counted for
329        // the remainder. The real parser rejects this as a syntax error.
330        let json = br#"["abc"#;
331        assert!(validator.validate_json_depth_bytes(json).is_ok());
332    }
333
334    #[test]
335    fn test_validate_json_depth_bytes_consecutive_backslashes_toggle_correctly() {
336        let validator = SecurityValidator::new(SecurityConfig {
337            json: crate::config::security::JsonLimits {
338                max_depth: 2,
339                ..SecurityConfig::default().json
340            },
341            ..SecurityConfig::default()
342        });
343
344        // `\\` is an escaped backslash, not an escape of the following `"`,
345        // so the string ends right after it and `,[1,2,3]]` is real
346        // structure: 2 levels deep, within the limit.
347        let json = br#"["a\\",[1,2,3]]"#;
348        assert!(validator.validate_json_depth_bytes(json).is_ok());
349
350        // Same shape but one level deeper must now be rejected — proves the
351        // even/odd backslash run is being tracked, not just tolerated.
352        let json_over = br#"[["a\\",[1,2,3]]]"#;
353        assert!(validator.validate_json_depth_bytes(json_over).is_err());
354    }
355
356    #[test]
357    fn test_security_validator_with_config() {
358        let config = SecurityConfig::low_memory();
359        let validator = SecurityValidator::new(config.clone());
360
361        // Should use low memory limits
362        assert!(
363            validator
364                .validate_input_size(config.json.max_input_size)
365                .is_ok()
366        );
367        assert!(
368            validator
369                .validate_input_size(config.json.max_input_size + 1)
370                .is_err()
371        );
372
373        // Test other validations
374        assert!(validator.validate_json_depth(config.json.max_depth).is_ok());
375        assert!(
376            validator
377                .validate_json_depth(config.json.max_depth + 1)
378                .is_err()
379        );
380    }
381
382    #[test]
383    fn test_validate_session_id() {
384        let validator = SecurityValidator::default();
385
386        // Valid session IDs
387        assert!(validator.validate_session_id("session-123").is_ok());
388        assert!(validator.validate_session_id("abcd1234-5678-90ef").is_ok());
389        assert!(validator.validate_session_id("test_session_id").is_ok());
390
391        // Invalid session IDs
392        assert!(validator.validate_session_id("ab").is_err()); // Too short
393        assert!(validator.validate_session_id(&"a".repeat(200)).is_err()); // Too long
394        assert!(validator.validate_session_id("session@123").is_err()); // Invalid chars
395        assert!(validator.validate_session_id("session 123").is_err()); // Space
396    }
397
398    #[test]
399    fn test_depth_tracker() {
400        let mut tracker = DepthTracker::with_max_depth(64);
401
402        assert_eq!(tracker.current_depth(), 0);
403
404        assert!(tracker.enter().is_ok());
405        assert_eq!(tracker.current_depth(), 1);
406
407        assert!(tracker.enter().is_ok());
408        assert_eq!(tracker.current_depth(), 2);
409
410        tracker.exit();
411        assert_eq!(tracker.current_depth(), 1);
412
413        tracker.exit();
414        assert_eq!(tracker.current_depth(), 0);
415    }
416
417    #[test]
418    fn test_depth_tracker_limit() {
419        let mut tracker = DepthTracker::with_max_depth(2);
420
421        assert!(tracker.enter().is_ok());
422        assert!(tracker.enter().is_ok());
423        assert!(tracker.enter().is_err()); // Should exceed limit
424    }
425
426    #[test]
427    fn test_depth_tracker_from_config() {
428        let config = SecurityConfig::low_memory();
429        let mut tracker = DepthTracker::from_config(&config);
430
431        // Should respect config limits
432        for _ in 0..config.json.max_depth {
433            assert!(tracker.enter().is_ok());
434        }
435        assert!(tracker.enter().is_err()); // Should exceed limit
436    }
437
438    #[test]
439    fn test_high_throughput_config() {
440        let config = SecurityConfig::high_throughput();
441        let _validator = SecurityValidator::new(config.clone());
442
443        // High throughput should have higher limits than default
444        let default_config = SecurityConfig::default();
445        assert!(config.json.max_input_size >= default_config.json.max_input_size);
446        assert!(config.buffers.max_total_memory >= default_config.buffers.max_total_memory);
447    }
448
449    #[test]
450    fn test_validate_array_length() {
451        let config = SecurityConfig::low_memory();
452        let max_len = config.json.max_array_length;
453        let validator = SecurityValidator::new(config);
454
455        // Valid array length
456        assert!(validator.validate_array_length(100).is_ok());
457
458        // Invalid array length
459        let result = validator.validate_array_length(max_len + 1);
460        assert!(result.is_err());
461    }
462
463    #[test]
464    fn test_validate_object_keys() {
465        let validator = SecurityValidator::default();
466
467        // Valid key count
468        assert!(validator.validate_object_keys(10).is_ok());
469
470        // Test with limit
471        let config = SecurityConfig::low_memory();
472        let max_keys = config.json.max_object_keys;
473        let validator = SecurityValidator::new(config);
474        let result = validator.validate_object_keys(max_keys + 1);
475        assert!(result.is_err());
476    }
477
478    #[test]
479    fn test_validate_string_length() {
480        let validator = SecurityValidator::default();
481
482        // Valid string length
483        assert!(validator.validate_string_length(100).is_ok());
484
485        // Test with limit
486        let config = SecurityConfig::low_memory();
487        let max_str_len = config.json.max_string_length;
488        let validator = SecurityValidator::new(config);
489        let result = validator.validate_string_length(max_str_len + 1);
490        assert!(result.is_err());
491    }
492
493    #[test]
494    fn test_validate_websocket_frame_size() {
495        let validator = SecurityValidator::default();
496
497        // Valid frame size
498        assert!(validator.validate_websocket_frame_size(1024).is_ok());
499
500        // Invalid frame size
501        let config = SecurityConfig::low_memory();
502        let max_frame = config.network.max_websocket_frame_size;
503        let validator = SecurityValidator::new(config);
504        let result = validator.validate_websocket_frame_size(max_frame + 1);
505        assert!(result.is_err());
506    }
507
508    #[test]
509    fn test_validate_buffer_size() {
510        let validator = SecurityValidator::default();
511
512        // Valid buffer size
513        assert!(validator.validate_buffer_size(4096).is_ok());
514
515        // Invalid buffer size
516        let config = SecurityConfig::low_memory();
517        let max_buf = config.buffers.max_buffer_size;
518        let validator = SecurityValidator::new(config);
519        let result = validator.validate_buffer_size(max_buf + 1);
520        assert!(result.is_err());
521    }
522
523    #[test]
524    fn test_validate_input_size_boundary() {
525        let config = SecurityConfig::low_memory();
526        let max_input = config.json.max_input_size;
527        let validator = SecurityValidator::new(config);
528
529        // At boundary
530        assert!(validator.validate_input_size(max_input).is_ok());
531
532        // Just over boundary
533        let result = validator.validate_input_size(max_input + 1);
534        assert!(result.is_err());
535    }
536
537    #[test]
538    fn test_validate_json_depth_boundary() {
539        let config = SecurityConfig::low_memory();
540        let max_depth = config.json.max_depth;
541        let validator = SecurityValidator::new(config);
542
543        // At boundary
544        assert!(validator.validate_json_depth(max_depth).is_ok());
545
546        // Just over boundary
547        let result = validator.validate_json_depth(max_depth + 1);
548        assert!(result.is_err());
549    }
550
551    #[test]
552    fn test_session_id_length_boundaries() {
553        let validator = SecurityValidator::default();
554
555        // Too short
556        let result = validator.validate_session_id("a");
557        assert!(result.is_err());
558
559        // Too long (default max is 128)
560        let long_id = "a".repeat(200);
561        let result = validator.validate_session_id(&long_id);
562        assert!(result.is_err());
563
564        // Valid length with hyphens
565        assert!(
566            validator
567                .validate_session_id("valid-session-id-123")
568                .is_ok()
569        );
570
571        // Valid length with underscores
572        assert!(
573            validator
574                .validate_session_id("valid_session_id_123")
575                .is_ok()
576        );
577    }
578
579    #[test]
580    fn test_session_id_invalid_characters() {
581        let validator = SecurityValidator::default();
582
583        // Invalid: special characters
584        let result = validator.validate_session_id("session@123");
585        assert!(result.is_err());
586
587        // Invalid: spaces
588        let result = validator.validate_session_id("session 123");
589        assert!(result.is_err());
590
591        // Invalid: dots
592        let result = validator.validate_session_id("session.123");
593        assert!(result.is_err());
594
595        // Valid: alphanumeric only
596        assert!(validator.validate_session_id("session123").is_ok());
597    }
598
599    #[test]
600    fn test_depth_tracker_boundary_cases() {
601        let mut tracker = DepthTracker::with_max_depth(1);
602
603        // Can enter once
604        assert!(tracker.enter().is_ok());
605        assert_eq!(tracker.current_depth(), 1);
606
607        // Cannot enter again
608        assert!(tracker.enter().is_err());
609        assert_eq!(tracker.current_depth(), 1); // Depth not incremented on error
610
611        // Can exit
612        tracker.exit();
613        assert_eq!(tracker.current_depth(), 0);
614
615        // Can enter again after exit
616        assert!(tracker.enter().is_ok());
617    }
618
619    #[test]
620    fn test_depth_tracker_exit_at_zero() {
621        let mut tracker = DepthTracker::with_max_depth(64);
622
623        // Starting at 0
624        assert_eq!(tracker.current_depth(), 0);
625
626        // Exiting at 0 should not go negative
627        tracker.exit();
628        assert_eq!(tracker.current_depth(), 0);
629
630        // Should still be able to enter
631        assert!(tracker.enter().is_ok());
632    }
633
634    #[test]
635    fn test_depth_tracker_multiple_cycles() {
636        let mut tracker = DepthTracker::with_max_depth(3);
637
638        // Cycle 1: enter, exit
639        assert!(tracker.enter().is_ok());
640        tracker.exit();
641        assert_eq!(tracker.current_depth(), 0);
642
643        // Cycle 2: multiple enters and exits
644        assert!(tracker.enter().is_ok());
645        assert!(tracker.enter().is_ok());
646        tracker.exit();
647        assert_eq!(tracker.current_depth(), 1);
648        tracker.exit();
649        assert_eq!(tracker.current_depth(), 0);
650
651        // Cycle 3: stress to max
652        for _ in 0..3 {
653            assert!(tracker.enter().is_ok());
654        }
655        assert_eq!(tracker.current_depth(), 3);
656    }
657}