veracode_platform/
json_validator.rs

1//! JSON validation utilities to prevent `DoS` attacks
2//!
3//! This module provides functions to validate JSON structure before deserialization,
4//! preventing Denial of Service attacks through deeply nested JSON structures.
5
6use log::warn;
7use serde_json::Value;
8
9/// Maximum allowed JSON nesting depth
10///
11/// This limit prevents `DoS` attacks via deeply nested `JSON` that can cause:
12/// - Stack overflow
13/// - Excessive memory consumption
14/// - CPU exhaustion during parsing
15///
16/// A depth of 32 is sufficient for legitimate API responses while protecting
17/// against malicious payloads. Most real-world APIs use <10 levels of nesting.
18pub const MAX_JSON_DEPTH: usize = 32;
19
20/// Validate JSON nesting depth to prevent `DoS` attacks
21///
22/// # Arguments
23///
24/// * `json_str` - The JSON string to validate
25/// * `max_depth` - Maximum allowed nesting depth (use `MAX_JSON_DEPTH` for default)
26///
27/// # Returns
28///
29/// * `Ok(())` if the JSON is valid and within depth limits
30/// * `Err(String)` with error message if validation fails
31///
32/// # Examples
33///
34/// ```
35/// use veracode_platform::json_validator::{validate_json_depth, MAX_JSON_DEPTH};
36///
37/// // Valid JSON within depth limit
38/// let json = r#"{"user": {"profile": {"settings": {"theme": "dark"}}}}"#;
39/// assert!(validate_json_depth(json, MAX_JSON_DEPTH).is_ok());
40///
41/// // Deeply nested JSON should be rejected
42/// let deep_json = (0..50).fold(String::from("{\"a\":"), |acc, _| acc + "{\"a\":")
43///     + &(0..50).map(|_| "}").collect::<String>();
44/// assert!(validate_json_depth(&deep_json, MAX_JSON_DEPTH).is_err());
45/// ```
46///
47/// # Security
48///
49/// This function protects against:
50/// - Stack overflow from recursive parsing
51/// - CPU exhaustion from excessive nesting
52/// - Memory exhaustion from deeply nested structures
53///
54/// # Errors
55///
56/// Returns an error if the JSON is invalid or exceeds the maximum nesting depth.
57/// Error messages are sanitized to avoid information disclosure, with detailed
58/// errors logged internally for debugging.
59pub fn validate_json_depth(json_str: &str, max_depth: usize) -> Result<(), String> {
60    // First, try to parse the JSON
61    let value: Value = serde_json::from_str(json_str).map_err(|e| {
62        // Log detailed parse error for debugging (internal only)
63        warn!("JSON parse error: {}", e);
64        // Return sanitized error to caller (may be exposed to users)
65        "Invalid JSON format".to_string()
66    })?;
67
68    // Calculate the actual nesting depth
69    let depth = calculate_depth(&value);
70
71    if depth > max_depth {
72        // Log detailed depth information for debugging (internal only)
73        warn!(
74            "JSON depth validation failed: depth {} exceeds maximum {}",
75            depth, max_depth
76        );
77        // Return sanitized error to caller (may be exposed to users)
78        return Err("JSON structure too deeply nested".to_string());
79    }
80
81    Ok(())
82}
83
84/// Calculate the maximum nesting depth of a JSON value
85///
86/// # Arguments
87///
88/// * `value` - The JSON value to analyze
89///
90/// # Returns
91///
92/// The maximum nesting depth (0 for scalars, 1+ for nested structures)
93///
94/// # Security
95///
96/// Uses bounded recursion to prevent stack overflow. Stops early if depth exceeds `MAX_JSON_DEPTH`.
97fn calculate_depth(value: &Value) -> usize {
98    calculate_depth_limited(value, 0)
99}
100
101/// Calculate depth with recursion limit to prevent stack overflow
102///
103/// Stops recursion early once `MAX_JSON_DEPTH` is exceeded, preventing stack overflow
104/// on maliciously deep JSON (e.g., 10,000+ nesting levels).
105fn calculate_depth_limited(value: &Value, current_depth: usize) -> usize {
106    // Early termination: stop recursing if we've exceeded the limit
107    // We don't need exact depth if it's already > MAX_JSON_DEPTH
108    if current_depth > MAX_JSON_DEPTH {
109        return current_depth;
110    }
111
112    match value {
113        Value::Array(arr) => {
114            if arr.is_empty() {
115                1
116            } else {
117                let max_child = arr
118                    .iter()
119                    .map(|v| calculate_depth_limited(v, current_depth.saturating_add(1)))
120                    .max()
121                    .unwrap_or(0);
122                1_usize.saturating_add(max_child)
123            }
124        }
125        Value::Object(obj) => {
126            if obj.is_empty() {
127                1
128            } else {
129                let max_child = obj
130                    .values()
131                    .map(|v| calculate_depth_limited(v, current_depth.saturating_add(1)))
132                    .max()
133                    .unwrap_or(0);
134                1_usize.saturating_add(max_child)
135            }
136        }
137        // Scalars have depth 0
138        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => 0,
139    }
140}
141
142#[cfg(test)]
143#[allow(clippy::expect_used)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_calculate_depth_scalar() {
149        let value = serde_json::json!("test");
150        assert_eq!(calculate_depth(&value), 0);
151
152        let value = serde_json::json!(42);
153        assert_eq!(calculate_depth(&value), 0);
154
155        let value = serde_json::json!(true);
156        assert_eq!(calculate_depth(&value), 0);
157
158        let value = serde_json::json!(null);
159        assert_eq!(calculate_depth(&value), 0);
160    }
161
162    #[test]
163    fn test_calculate_depth_simple_object() {
164        let value = serde_json::json!({"key": "value"});
165        assert_eq!(calculate_depth(&value), 1);
166    }
167
168    #[test]
169    fn test_calculate_depth_simple_array() {
170        let value = serde_json::json!([1, 2, 3]);
171        assert_eq!(calculate_depth(&value), 1);
172    }
173
174    #[test]
175    fn test_calculate_depth_nested_object() {
176        let value = serde_json::json!({
177            "user": {
178                "profile": {
179                    "settings": {
180                        "theme": "dark"
181                    }
182                }
183            }
184        });
185        assert_eq!(calculate_depth(&value), 4);
186    }
187
188    #[test]
189    fn test_calculate_depth_nested_array() {
190        let value = serde_json::json!([[[1, 2], [3, 4]]]);
191        assert_eq!(calculate_depth(&value), 3);
192    }
193
194    #[test]
195    fn test_calculate_depth_mixed() {
196        let value = serde_json::json!({
197            "data": [
198                {"nested": [1, 2, 3]},
199                {"nested": [4, 5, 6]}
200            ]
201        });
202        // Depth: root object (1) + array (1) + inner objects (1) + inner arrays (1) = 4
203        assert_eq!(calculate_depth(&value), 4);
204    }
205
206    #[test]
207    fn test_calculate_depth_empty_structures() {
208        let value = serde_json::json!({});
209        assert_eq!(calculate_depth(&value), 1);
210
211        let value = serde_json::json!([]);
212        assert_eq!(calculate_depth(&value), 1);
213    }
214
215    #[test]
216    fn test_validate_json_depth_valid() {
217        let json = r#"{"user": {"profile": {"name": "test"}}}"#;
218        assert!(validate_json_depth(json, MAX_JSON_DEPTH).is_ok());
219    }
220
221    #[test]
222    fn test_validate_json_depth_at_limit() {
223        // Create JSON at exactly MAX_JSON_DEPTH
224        let mut json = String::from("{");
225        for i in 0..MAX_JSON_DEPTH - 1 {
226            json.push_str(&format!("\"level{}\":{{", i));
227        }
228        json.push_str("\"value\":42");
229        json.push_str(&"}".repeat(MAX_JSON_DEPTH));
230
231        assert!(validate_json_depth(&json, MAX_JSON_DEPTH).is_ok());
232    }
233
234    #[test]
235    fn test_validate_json_depth_exceeds_limit() {
236        // Create JSON that exceeds MAX_JSON_DEPTH
237        let mut json = String::from("{");
238        for i in 0..MAX_JSON_DEPTH + 5 {
239            json.push_str(&format!("\"level{}\":{{", i));
240        }
241        json.push_str("\"value\":42");
242        json.push_str(&"}".repeat(MAX_JSON_DEPTH + 6));
243
244        let result = validate_json_depth(&json, MAX_JSON_DEPTH);
245        assert!(result.is_err());
246        assert!(
247            result
248                .expect_err("should fail on deeply nested json")
249                .contains("too deeply nested")
250        );
251    }
252
253    #[test]
254    fn test_validate_json_depth_invalid_json() {
255        let json = r#"{"invalid": json}"#;
256        let result = validate_json_depth(json, MAX_JSON_DEPTH);
257        assert!(result.is_err());
258        assert!(
259            result
260                .expect_err("should fail on invalid json")
261                .contains("Invalid JSON format")
262        );
263    }
264
265    #[test]
266    fn test_validate_json_depth_deeply_nested_array() {
267        // Create deeply nested array
268        let mut json = String::new();
269        for _ in 0..50 {
270            json.push('[');
271        }
272        json.push_str("42");
273        for _ in 0..50 {
274            json.push(']');
275        }
276
277        let result = validate_json_depth(&json, MAX_JSON_DEPTH);
278        assert!(result.is_err());
279        assert!(
280            result
281                .expect_err("should fail on deeply nested json")
282                .contains("too deeply nested")
283        );
284    }
285
286    #[test]
287    fn test_validate_json_depth_custom_limit() {
288        let json = r#"{"a": {"b": {"c": {"d": "value"}}}}"#;
289
290        // Should pass with limit 5
291        assert!(validate_json_depth(json, 5).is_ok());
292
293        // Should fail with limit 3
294        let result = validate_json_depth(json, 3);
295        assert!(result.is_err());
296    }
297
298    #[test]
299    fn test_realistic_api_response() {
300        // Simulate a realistic API response with moderate nesting
301        let json = r#"{
302            "_embedded": {
303                "applications": [
304                    {
305                        "id": 123,
306                        "profile": {
307                            "name": "TestApp",
308                            "settings": {
309                                "scan": {
310                                    "enabled": true
311                                }
312                            }
313                        }
314                    }
315                ]
316            }
317        }"#;
318
319        assert!(validate_json_depth(json, MAX_JSON_DEPTH).is_ok());
320    }
321
322    #[test]
323    fn test_dos_payload_detection() {
324        // Test case similar to what a fuzzer might generate
325        // This creates a very deeply nested structure that could cause DoS
326        let depth = 100;
327        let mut json = String::new();
328
329        // Create nested objects
330        for i in 0..depth {
331            json.push_str(&format!("{{\"level_{}\":", i));
332        }
333        json.push_str("null");
334        for _ in 0..depth {
335            json.push('}');
336        }
337
338        let result = validate_json_depth(&json, MAX_JSON_DEPTH);
339        assert!(result.is_err());
340        assert!(
341            result
342                .expect_err("should fail on deeply nested json")
343                .contains("too deeply nested")
344        );
345    }
346}
347
348// ============================================================================
349// TIER 1: PROPERTY-BASED SECURITY TESTS (Fast, High ROI)
350// ============================================================================
351//
352// These tests use proptest to validate security properties against adversarial
353// inputs. They run 1000 test cases in normal mode and 10 under Miri for UB detection.
354
355#[cfg(test)]
356#[allow(clippy::expect_used)]
357mod proptest_security {
358    use super::*;
359    use proptest::prelude::*;
360
361    // ============================================================================
362    // SECURITY TEST: JSON Depth Validation with Adversarial Inputs
363    // ============================================================================
364
365    proptest! {
366        #![proptest_config(ProptestConfig {
367            cases: if cfg!(miri) { 5 } else { 1000 },
368            failure_persistence: None,
369            .. ProptestConfig::default()
370        })]
371
372        /// Property: Valid JSON within depth limits must always succeed
373        /// Tests that legitimate JSON structures are never incorrectly rejected
374        #[test]
375        fn proptest_valid_json_within_limits_succeeds(
376            depth in 1usize..=MAX_JSON_DEPTH,
377        ) {
378            // Create JSON with exactly 'depth' nesting levels
379            let mut json = String::new();
380            for i in 0..depth {
381                json.push_str(&format!("{{\"level{}\":", i));
382            }
383            json.push_str("\"value\"");
384            json.push_str(&"}".repeat(depth));
385
386            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
387            prop_assert!(result.is_ok(), "Valid JSON at depth {} should succeed", depth);
388        }
389
390        /// Property: JSON exceeding depth limits must always fail
391        /// Tests that deeply nested JSON is correctly rejected
392        #[test]
393        fn proptest_deeply_nested_json_rejected(
394            excess_depth in 1usize..=50,
395        ) {
396            let depth = MAX_JSON_DEPTH.saturating_add(excess_depth);
397
398            // Create JSON that exceeds MAX_JSON_DEPTH
399            let mut json = String::new();
400            for i in 0..depth {
401                json.push_str(&format!("{{\"level{}\":", i));
402            }
403            json.push_str("null");
404            json.push_str(&"}".repeat(depth));
405
406            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
407            prop_assert!(result.is_err(), "JSON at depth {} should be rejected", depth);
408
409            // Error can be either from depth validation or from serde_json's recursion limit
410            if let Err(msg) = result {
411                prop_assert!(
412                    msg.contains("too deeply nested") || msg == "Invalid JSON format",
413                    "Error message should indicate rejection: {}", msg
414                );
415            }
416        }
417
418        /// Property: Invalid JSON must return parse error, never panic
419        /// Tests that malformed JSON is handled gracefully
420        #[test]
421        fn proptest_invalid_json_returns_error(
422            garbage in ".*{0,200}",
423        ) {
424            // Most random strings are not valid JSON
425            let result = validate_json_depth(&garbage, MAX_JSON_DEPTH);
426
427            // Either valid JSON or error, never panic
428            match result {
429                Ok(_) => {
430                    // If it succeeded, must be valid JSON
431                    prop_assert!(serde_json::from_str::<Value>(&garbage).is_ok());
432                },
433                Err(msg) => {
434                    // Error should be sanitized
435                    prop_assert!(
436                        msg == "Invalid JSON format" || msg.contains("too deeply nested"),
437                        "Error message should be sanitized"
438                    );
439                }
440            }
441        }
442
443        /// Property: Empty and whitespace-only JSON must be handled
444        /// Tests edge cases with minimal input
445        #[test]
446        fn proptest_empty_and_whitespace_json(
447            whitespace in "\\s{0,100}",
448        ) {
449            let result = validate_json_depth(&whitespace, MAX_JSON_DEPTH);
450
451            // Empty/whitespace is invalid JSON, should return error
452            match result {
453                Ok(_) => {
454                    // Only succeeds if it's valid JSON (unlikely with just whitespace)
455                    prop_assert!(serde_json::from_str::<Value>(&whitespace).is_ok());
456                },
457                Err(msg) => {
458                    prop_assert_eq!(msg, "Invalid JSON format");
459                }
460            }
461        }
462
463        /// Property: Custom depth limits must be respected
464        /// Tests that the max_depth parameter is correctly enforced
465        #[test]
466        fn proptest_custom_depth_limit_enforced(
467            max_depth in 5usize..=MAX_JSON_DEPTH,
468            test_depth in 1usize..=MAX_JSON_DEPTH,
469        ) {
470            // Only test within MAX_JSON_DEPTH to avoid serde_json's own limits
471            // Create JSON with test_depth nesting
472            let mut json = String::new();
473            for i in 0..test_depth {
474                json.push_str(&format!("{{\"d{}\":", i));
475            }
476            json.push('0');
477            json.push_str(&"}".repeat(test_depth));
478
479            let result = validate_json_depth(&json, max_depth);
480
481            // Property: validation result should match depth comparison
482            if test_depth <= max_depth {
483                prop_assert!(result.is_ok(),
484                    "JSON depth {} should pass with limit {}", test_depth, max_depth);
485            } else {
486                prop_assert!(result.is_err(),
487                    "JSON depth {} should fail with limit {}", test_depth, max_depth);
488            }
489        }
490    }
491
492    // ============================================================================
493    // SECURITY TEST: String Handling and Injection Attacks
494    // ============================================================================
495
496    proptest! {
497        #![proptest_config(ProptestConfig {
498            cases: if cfg!(miri) { 5 } else { 1000 },
499            failure_persistence: None,
500            .. ProptestConfig::default()
501        })]
502
503        /// Property: Special characters in JSON strings must be handled safely
504        /// Tests against JSON injection and escape sequence attacks
505        #[test]
506        fn proptest_special_characters_in_strings(
507            special_chars in r#"[<>'"&\x00-\x1f\x7f\\]{0,100}"#,
508        ) {
509            let json = serde_json::json!({
510                "payload": special_chars,
511                "nested": {
512                    "value": special_chars
513                }
514            }).to_string();
515
516            // Property 1: Must not panic on special characters
517            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
518            prop_assert!(result.is_ok());
519
520            // Property 2: Parse result must preserve the data
521            let parsed: Value = serde_json::from_str(&json)
522                .expect("serde_json should handle its own output");
523            prop_assert_eq!(parsed.get("payload").and_then(|v| v.as_str()), Some(special_chars.as_str()));
524        }
525
526        /// Property: Control characters must be properly escaped
527        /// Tests that control characters don't break JSON parsing
528        #[test]
529        fn proptest_control_characters_safe(
530            // Test various control characters that could cause issues
531            control_char in prop::sample::select(vec![
532                '\0', '\t', '\n', '\r', '\x01', '\x02', '\x08', '\x0c', '\x1f', '\x7f'
533            ]),
534        ) {
535            let payload = format!("test{}value", control_char);
536            let json = serde_json::json!({
537                "data": payload
538            }).to_string();
539
540            // Must successfully validate and parse
541            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
542            prop_assert!(result.is_ok());
543        }
544
545        /// Property: Extremely long strings must not cause buffer overflows
546        /// Tests memory safety with large string values
547        #[test]
548        fn proptest_large_strings_safe(
549            length in 0usize..=10000,
550        ) {
551            let large_string = "A".repeat(length);
552            let json = serde_json::json!({
553                "large_field": large_string
554            }).to_string();
555
556            // Property 1: Must not panic on large strings
557            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
558            prop_assert!(result.is_ok());
559
560            // Property 2: Depth should be 1 (just the object)
561            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
562            prop_assert_eq!(calculate_depth(&parsed), 1);
563        }
564
565        /// Property: Unicode edge cases must be handled correctly
566        /// Tests UTF-8 boundary handling and multi-byte characters
567        #[test]
568        fn proptest_unicode_handling(
569            // Test various Unicode ranges including emojis and special scripts
570            unicode_str in "[\\p{L}\\p{N}\\p{S}\\p{M}]{0,200}",
571        ) {
572            let json = serde_json::json!({
573                "unicode": unicode_str,
574                "nested": {
575                    "more_unicode": unicode_str
576                }
577            }).to_string();
578
579            // Property 1: Must handle Unicode correctly
580            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
581            prop_assert!(result.is_ok());
582
583            // Property 2: Unicode should be preserved
584            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
585            prop_assert_eq!(parsed.get("unicode").and_then(|v| v.as_str()), Some(unicode_str.as_str()));
586        }
587
588        /// Property: Path traversal sequences in JSON values must be safely contained
589        /// Tests that path traversal strings don't affect JSON validation
590        #[test]
591        fn proptest_path_traversal_sequences_safe(
592            // Test common path traversal patterns
593            traversal in prop::sample::select(vec![
594                "../", "..\\", "../../", "../../../etc/passwd",
595                "....//", "..\\..\\", "/etc/passwd", "C:\\Windows\\System32",
596                "%2e%2e%2f", "%2e%2e/", "..%2f", "..%5c"
597            ]),
598        ) {
599            let json = serde_json::json!({
600                "filename": traversal,
601                "path": traversal
602            }).to_string();
603
604            // Property: Path traversal sequences are just string data in JSON
605            // They should not affect validation or cause any special behavior
606            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
607            prop_assert!(result.is_ok());
608
609            // Data should be preserved as-is
610            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
611            prop_assert_eq!(parsed.get("filename").and_then(|v| v.as_str()), Some(traversal));
612        }
613
614        /// Property: Null byte injection attempts must be handled safely
615        /// Tests that null bytes in JSON strings don't cause truncation
616        #[test]
617        fn proptest_null_byte_injection_safe(
618            prefix in "[a-zA-Z0-9]{0,50}",
619            suffix in "[a-zA-Z0-9]{0,50}",
620        ) {
621            // Create a string with null byte
622            let payload = format!("{}\0{}", prefix, suffix);
623            let json = serde_json::json!({
624                "payload": payload
625            }).to_string();
626
627            // Property: Null bytes should be properly escaped in JSON
628            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
629            prop_assert!(result.is_ok());
630
631            // Verify the null byte was preserved through encoding/decoding
632            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
633            if let Some(s) = parsed.get("payload").and_then(|v| v.as_str()) {
634                // The null byte should be present in the decoded string
635                let expected_without_null = format!("{}{}", prefix, suffix);
636                prop_assert!(s.contains('\0') || s == expected_without_null);
637            }
638        }
639    }
640
641    // ============================================================================
642    // SECURITY TEST: Array and Object Boundary Conditions
643    // ============================================================================
644
645    proptest! {
646        #![proptest_config(ProptestConfig {
647            cases: if cfg!(miri) { 5 } else { 1000 },
648            failure_persistence: None,
649            .. ProptestConfig::default()
650        })]
651
652        /// Property: Large arrays must not cause memory exhaustion
653        /// Tests that large (but shallow) arrays are handled efficiently
654        #[test]
655        fn proptest_large_arrays_safe(
656            size in 0usize..=1000,
657        ) {
658            let array: Vec<i32> = (0..size).map(|i| i32::try_from(i).unwrap_or(0)).collect();
659            let json = serde_json::json!({
660                "large_array": array
661            }).to_string();
662
663            // Property 1: Must not panic on large arrays
664            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
665            prop_assert!(result.is_ok());
666
667            // Property 2: Depth should be 2 (object + array)
668            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
669            prop_assert_eq!(calculate_depth(&parsed), 2);
670        }
671
672        /// Property: Large objects must not cause memory exhaustion
673        /// Tests that objects with many keys are handled efficiently
674        #[test]
675        fn proptest_large_objects_safe(
676            key_count in 0usize..=500,
677        ) {
678            // Create object with key_count keys
679            let mut obj = serde_json::Map::new();
680            for i in 0..key_count {
681                obj.insert(format!("key_{}", i), Value::from(i));
682            }
683            let json = Value::Object(obj).to_string();
684
685            // Property 1: Must not panic on large objects
686            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
687            prop_assert!(result.is_ok());
688
689            // Property 2: Depth should be 1 (flat object)
690            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
691            prop_assert_eq!(calculate_depth(&parsed), 1);
692        }
693
694        /// Property: Empty arrays and objects must be handled correctly
695        /// Tests edge cases with zero elements
696        #[test]
697        fn proptest_empty_structures_depth(
698            nest_level in 0usize..=10,
699        ) {
700            // Create nested empty objects
701            let mut json = String::new();
702            for _ in 0..nest_level {
703                json.push_str("{\"empty\":");
704            }
705            json.push_str("{}");
706            json.push_str(&"}".repeat(nest_level));
707
708            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
709            prop_assert!(result.is_ok());
710
711            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
712            let depth = calculate_depth(&parsed);
713            // Empty objects still count toward depth
714            prop_assert_eq!(depth, nest_level.saturating_add(1));
715        }
716
717        /// Property: Mixed nesting (arrays and objects) must be calculated correctly
718        /// Tests depth calculation with alternating structures
719        #[test]
720        fn proptest_mixed_nesting_depth_calculation(
721            depth in 1usize..=10,
722        ) {
723            // Create simple mixed nesting with arrays and objects
724            let mut json = String::new();
725            for _ in 0..depth {
726                json.push_str("[{\"x\":");
727            }
728            json.push_str("null");
729            json.push_str(&"}]".repeat(depth));
730
731            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
732            prop_assert!(result.is_ok(), "JSON construction should be valid");
733
734            let parsed: Value = serde_json::from_str(&json)
735                .expect("JSON should parse correctly");
736            let calculated_depth = calculate_depth(&parsed);
737            // Each iteration adds both array and object, so depth = 2*depth
738            prop_assert!(calculated_depth >= depth,
739                "Calculated depth {} should be >= nesting levels {}",
740                calculated_depth, depth);
741        }
742    }
743
744    // ============================================================================
745    // SECURITY TEST: Numeric Edge Cases and Integer Overflow
746    // ============================================================================
747
748    proptest! {
749        #![proptest_config(ProptestConfig {
750            cases: if cfg!(miri) { 5 } else { 1000 },
751            failure_persistence: None,
752            .. ProptestConfig::default()
753        })]
754
755        /// Property: Integer overflow in depth calculation must be prevented
756        /// Tests that saturating_add prevents overflow panics
757        #[test]
758        fn proptest_depth_calculation_no_overflow(
759            // Test with realistic depths that shouldn't overflow
760            depth in 0usize..=200,
761        ) {
762            // Create JSON with specified depth
763            let mut json = String::new();
764            for i in 0..depth {
765                json.push_str(&format!("{{\"{}\":", i));
766            }
767            json.push_str("42");
768            json.push_str(&"}".repeat(depth));
769
770            // Property: Must never panic on overflow
771            // Early termination at MAX_JSON_DEPTH prevents excessive recursion
772            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
773
774            if depth <= MAX_JSON_DEPTH {
775                prop_assert!(result.is_ok());
776            } else {
777                prop_assert!(result.is_err());
778            }
779        }
780
781        /// Property: Extreme numeric values in JSON must be handled
782        /// Tests that large numbers don't cause issues
783        #[test]
784        fn proptest_extreme_numeric_values(
785            value in prop::num::i64::ANY,
786        ) {
787            let json = serde_json::json!({
788                "number": value,
789                "nested": {
790                    "another_number": value
791                }
792            }).to_string();
793
794            // Property: Large numbers should not affect depth validation
795            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
796            prop_assert!(result.is_ok());
797
798            let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
799            prop_assert_eq!(calculate_depth(&parsed), 2);
800        }
801
802        /// Property: Boolean and null values must not affect depth calculation
803        /// Tests that scalar values are correctly identified as depth 0
804        #[test]
805        fn proptest_scalar_values_depth_zero(
806            bool_val in any::<bool>(),
807        ) {
808            // Test various scalar types
809            let null_value = Value::Null;
810            let bool_value = Value::Bool(bool_val);
811            let num_value = Value::from(42);
812            let str_value = Value::from("test");
813
814            prop_assert_eq!(calculate_depth(&null_value), 0);
815            prop_assert_eq!(calculate_depth(&bool_value), 0);
816            prop_assert_eq!(calculate_depth(&num_value), 0);
817            prop_assert_eq!(calculate_depth(&str_value), 0);
818        }
819
820        /// Property: Very deep recursion must be bounded
821        /// Tests that calculate_depth_limited provides early termination
822        #[test]
823        fn proptest_recursion_bounded(
824            depth in (MAX_JSON_DEPTH + 1)..=60,
825        ) {
826            // Create JSON deeper than MAX_JSON_DEPTH
827            let mut json = String::new();
828            for i in 0..depth {
829                json.push_str(&format!("[{{\"{}\":", i));
830            }
831            json.push('0');
832            json.push_str(&"}]".repeat(depth));
833
834            // Property: Should reject without stack overflow
835            // Early termination prevents excessive recursion
836            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
837            prop_assert!(result.is_err());
838
839            // Note: serde_json has its own recursion limit (~128 levels)
840            // For very deep JSON, serde_json may fail before our validation
841            // Either error is acceptable - both protect against DoS
842            if let Ok(parsed) = serde_json::from_str::<Value>(&json) {
843                let calculated = calculate_depth(&parsed);
844                // If parsing succeeded, depth calculation should detect the issue
845                prop_assert!(calculated > MAX_JSON_DEPTH);
846            }
847        }
848    }
849
850    // ============================================================================
851    // SECURITY TEST: DoS Attack Vectors
852    // ============================================================================
853
854    proptest! {
855        #![proptest_config(ProptestConfig {
856            cases: if cfg!(miri) { 5 } else { 500 },  // Fewer cases due to complexity
857            failure_persistence: None,
858            .. ProptestConfig::default()
859        })]
860
861        /// Property: Exponentially wide JSON must not cause memory exhaustion
862        /// Tests the "billion laughs" style attack adapted for JSON
863        #[test]
864        fn proptest_dos_exponential_width(
865            width in 1usize..=20,
866            depth in 1usize..=4,
867        ) {
868            // Create JSON with exponentially increasing width
869            // Each level has 'width' children
870            fn create_wide_json(width: usize, depth: usize) -> String {
871                if depth == 0 {
872                    return "42".to_string();
873                }
874
875                let mut json = String::from("[");
876                for i in 0..width {
877                    if i > 0 {
878                        json.push(',');
879                    }
880                    json.push_str(&create_wide_json(width, depth.saturating_sub(1)));
881                }
882                json.push(']');
883                json
884            }
885
886            let json = create_wide_json(width, depth);
887
888            // Property: Must handle or reject gracefully, never crash
889            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
890
891            if depth <= MAX_JSON_DEPTH {
892                prop_assert!(result.is_ok() || result.is_err()); // Either is fine
893            } else {
894                prop_assert!(result.is_err());
895            }
896        }
897
898        /// Property: Repeated deep nesting with different patterns
899        /// Tests that various nesting styles are consistently handled
900        #[test]
901        fn proptest_dos_varied_nesting_patterns(
902            depth in 30usize..=MAX_JSON_DEPTH + 20,
903            pattern in prop::sample::select(vec!["array", "object", "mixed"]),
904        ) {
905            let json = match pattern {
906                "array" => {
907                    let mut s = String::new();
908                    for _ in 0..depth {
909                        s.push('[');
910                    }
911                    s.push_str("null");
912                    s.push_str(&"]".repeat(depth));
913                    s
914                },
915                "object" => {
916                    let mut s = String::new();
917                    for i in 0..depth {
918                        s.push_str(&format!("{{\"k{}\":", i));
919                    }
920                    s.push_str("null");
921                    s.push_str(&"}".repeat(depth));
922                    s
923                },
924                _ => { // "mixed"
925                    let mut s = String::new();
926                    for i in 0..depth {
927                        if i % 2 == 0 {
928                            s.push('[');
929                        } else {
930                            s.push_str("{\"x\":");
931                        }
932                    }
933                    s.push_str("null");
934                    for i in (0..depth).rev() {
935                        if i % 2 == 0 {
936                            s.push(']');
937                        } else {
938                            s.push('}');
939                        }
940                    }
941                    s
942                }
943            };
944
945            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
946
947            if depth <= MAX_JSON_DEPTH {
948                prop_assert!(result.is_ok());
949            } else {
950                prop_assert!(result.is_err());
951            }
952        }
953
954        /// Property: Malformed JSON with unbalanced brackets must fail gracefully
955        /// Tests that parser errors are caught and sanitized
956        #[test]
957        fn proptest_malformed_json_graceful_failure(
958            open_brackets in 0usize..=50,
959            close_brackets in 0usize..=50,
960        ) {
961            // Create intentionally malformed JSON
962            let mut json = String::new();
963            json.push_str(&"[".repeat(open_brackets));
964            json.push_str("null");
965            json.push_str(&"]".repeat(close_brackets));
966
967            let result = validate_json_depth(&json, MAX_JSON_DEPTH);
968
969            // Property: Either valid (if balanced) or returns sanitized error
970            match result {
971                Ok(_) => {
972                    // If successful, brackets must be balanced
973                    prop_assert_eq!(open_brackets, close_brackets);
974                },
975                Err(msg) => {
976                    // Error must be sanitized
977                    prop_assert!(
978                        msg == "Invalid JSON format" || msg.contains("too deeply nested")
979                    );
980                }
981            }
982        }
983    }
984}