mik_sql/validate/
filter.rs

1//! Filter validation logic for user-provided filters.
2
3use crate::{Filter, Operator, Value};
4use std::fmt;
5
6/// Maximum number of value nodes to validate (defense-in-depth).
7const MAX_VALUE_NODES: usize = 10000;
8
9/// Validation configuration for user-provided filters.
10///
11/// Provides four layers of security:
12/// 1. Field whitelist - only specific fields can be queried
13/// 2. Operator blacklist - dangerous operators can be denied
14/// 3. Nesting depth limit - prevent complex nested queries
15/// 4. Total node count limit - prevent DoS via large arrays
16#[derive(Debug, Clone)]
17#[non_exhaustive]
18pub struct FilterValidator {
19    /// Allowed field names (whitelist). Empty = allow all fields.
20    pub allowed_fields: Vec<String>,
21    /// Denied operators (blacklist).
22    pub denied_operators: Vec<Operator>,
23    /// Maximum nesting depth for complex filters.
24    pub max_depth: usize,
25}
26
27impl FilterValidator {
28    /// Create a new validator with secure defaults.
29    ///
30    /// Defaults:
31    /// - No field restrictions (allow all)
32    /// - Denies `Regex` operator (`ReDoS` prevention)
33    /// - Max nesting depth: 5
34    ///
35    /// This is the recommended constructor for user-facing filters.
36    /// For internal/trusted filters where you need all operators,
37    /// use [`permissive()`](Self::permissive).
38    ///
39    /// # Example
40    ///
41    /// ```
42    /// use mik_sql::FilterValidator;
43    ///
44    /// let validator = FilterValidator::new()
45    ///     .allow_fields(&["name", "email", "status"]);
46    /// ```
47    #[must_use]
48    pub fn new() -> Self {
49        Self {
50            allowed_fields: Vec::new(),
51            denied_operators: vec![crate::Operator::Regex],
52            max_depth: 5,
53        }
54    }
55
56    /// Create a permissive validator that allows all operators.
57    ///
58    /// **Warning:** Only use this for trusted/internal filters, never for
59    /// user-provided input. The `Regex` operator can cause `ReDoS` attacks.
60    ///
61    /// # Example
62    ///
63    /// ```
64    /// use mik_sql::FilterValidator;
65    ///
66    /// // Only for trusted internal filters!
67    /// let validator = FilterValidator::permissive();
68    /// ```
69    #[must_use]
70    pub const fn permissive() -> Self {
71        Self {
72            allowed_fields: Vec::new(),
73            denied_operators: Vec::new(),
74            max_depth: 5,
75        }
76    }
77
78    /// Set allowed fields (whitelist).
79    ///
80    /// Only fields in this list can be used in user filters.
81    /// If empty, all fields are allowed.
82    #[must_use]
83    pub fn allow_fields(mut self, fields: &[&str]) -> Self {
84        self.allowed_fields = fields.iter().map(|s| (*s).to_string()).collect();
85        self
86    }
87
88    /// Set denied operators (blacklist).
89    ///
90    /// These operators cannot be used in user filters.
91    /// Useful for blocking regex, pattern matching, or other expensive operations.
92    #[must_use]
93    pub fn deny_operators(mut self, ops: &[Operator]) -> Self {
94        self.denied_operators = ops.to_vec();
95        self
96    }
97
98    /// Set maximum nesting depth.
99    ///
100    /// Prevents complex nested queries that could impact performance.
101    /// Default is 5.
102    #[must_use]
103    pub const fn max_depth(mut self, depth: usize) -> Self {
104        self.max_depth = depth;
105        self
106    }
107
108    /// Validate a filter against the configured rules.
109    ///
110    /// Returns an error if:
111    /// - Field is not in the allowed list (when list is not empty)
112    /// - Operator is in the denied list
113    /// - Array nesting depth exceeds maximum
114    pub fn validate(&self, filter: &Filter) -> Result<(), ValidationError> {
115        self.validate_with_depth(filter, 0)
116    }
117
118    /// Internal validation with depth tracking.
119    fn validate_with_depth(&self, filter: &Filter, depth: usize) -> Result<(), ValidationError> {
120        // Check nesting depth
121        if depth > self.max_depth {
122            return Err(ValidationError::NestingTooDeep {
123                max: self.max_depth,
124                actual: depth,
125            });
126        }
127
128        // Check field whitelist (only if not empty)
129        if !self.allowed_fields.is_empty() && !self.allowed_fields.contains(&filter.field) {
130            return Err(ValidationError::FieldNotAllowed {
131                field: filter.field.clone(),
132                allowed: self.allowed_fields.clone(),
133            });
134        }
135
136        // Check operator blacklist
137        if self.denied_operators.contains(&filter.op) {
138            return Err(ValidationError::OperatorDenied {
139                operator: filter.op,
140                field: filter.field.clone(),
141            });
142        }
143
144        // Recursively validate array values (for complex nested filters)
145        if let Value::Array(values) = &filter.value {
146            let mut node_count = 0;
147            for value in values {
148                self.validate_value_with_count(value, depth + 1, &mut node_count)?;
149            }
150        }
151
152        Ok(())
153    }
154
155    /// Validate nested values in arrays with node count tracking.
156    fn validate_value_with_count(
157        &self,
158        value: &Value,
159        depth: usize,
160        count: &mut usize,
161    ) -> Result<(), ValidationError> {
162        *count += 1;
163        if *count > MAX_VALUE_NODES {
164            return Err(ValidationError::TooManyNodes {
165                max: MAX_VALUE_NODES,
166            });
167        }
168
169        if depth > self.max_depth {
170            return Err(ValidationError::NestingTooDeep {
171                max: self.max_depth,
172                actual: depth,
173            });
174        }
175
176        if let Value::Array(values) = value {
177            for v in values {
178                self.validate_value_with_count(v, depth + 1, count)?;
179            }
180        }
181
182        Ok(())
183    }
184}
185
186impl Default for FilterValidator {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192/// Validation error types.
193#[derive(Debug, Clone, PartialEq, Eq)]
194#[non_exhaustive]
195pub enum ValidationError {
196    /// Field is not in the allowed list.
197    FieldNotAllowed {
198        /// The field that was not allowed.
199        field: String,
200        /// The list of allowed fields.
201        allowed: Vec<String>,
202    },
203    /// Operator is denied for this field.
204    OperatorDenied {
205        /// The operator that was denied.
206        operator: Operator,
207        /// The field the operator was used on.
208        field: String,
209    },
210    /// Nesting depth exceeds maximum.
211    NestingTooDeep {
212        /// The maximum allowed nesting depth.
213        max: usize,
214        /// The actual nesting depth encountered.
215        actual: usize,
216    },
217    /// Too many value nodes (DoS prevention).
218    TooManyNodes {
219        /// The maximum allowed node count.
220        max: usize,
221    },
222}
223
224impl fmt::Display for ValidationError {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self {
227            Self::FieldNotAllowed { field, allowed } => {
228                if allowed.is_empty() {
229                    write!(f, "field `{field}` is not allowed (no fields permitted)")
230                } else {
231                    write!(
232                        f,
233                        "field `{field}` is not allowed; permitted: {}",
234                        allowed.join(", ")
235                    )
236                }
237            },
238            Self::OperatorDenied { operator, field } => {
239                let reason = match operator {
240                    Operator::Regex => " (ReDoS prevention)",
241                    _ => "",
242                };
243                write!(
244                    f,
245                    "operator `{operator:?}` denied for field `{field}`{reason}"
246                )
247            },
248            Self::NestingTooDeep { max, actual } => {
249                write!(
250                    f,
251                    "filter nesting depth {actual} exceeds maximum {max} (DoS prevention)"
252                )
253            },
254            Self::TooManyNodes { max } => {
255                write!(
256                    f,
257                    "filter contains too many value nodes (max {max}, DoS prevention)"
258                )
259            },
260        }
261    }
262}
263
264impl std::error::Error for ValidationError {}
265
266/// Merge trusted filters with validated user filters.
267///
268/// Combines system/policy filters with validated user-provided filters.
269///
270/// # Arguments
271///
272/// * `trusted` - System filters (e.g., `org_id`, `tenant_id`, `deleted_at`)
273/// * `user` - User-provided filters from request
274/// * `validator` - Validation rules for user filters
275///
276/// # Returns
277///
278/// Combined filter list with trusted filters first, then validated user filters.
279///
280/// # Errors
281///
282/// Returns `ValidationError` if any user filter violates the validator rules.
283///
284/// # Example
285///
286/// ```
287/// # use mik_sql::{Filter, FilterValidator, merge_filters, Operator, Value};
288/// // System ensures user can only see their org's data
289/// let trusted = vec![
290///     Filter::new("org_id", Operator::Eq, Value::Int(123)),
291/// ];
292///
293/// // User wants to filter by status
294/// let user = vec![
295///     Filter::new("status", Operator::Eq, Value::String("active".into())),
296/// ];
297///
298/// let validator = FilterValidator::new().allow_fields(&["status", "name"]);
299/// let all_filters = merge_filters(trusted, user, &validator).unwrap();
300/// assert_eq!(all_filters.len(), 2);
301/// assert_eq!(all_filters[0].field, "org_id");
302/// assert_eq!(all_filters[1].field, "status");
303/// ```
304pub fn merge_filters(
305    trusted: Vec<Filter>,
306    user: Vec<Filter>,
307    validator: &FilterValidator,
308) -> Result<Vec<Filter>, ValidationError> {
309    // Validate all user filters
310    for filter in &user {
311        validator.validate(filter)?;
312    }
313
314    // Combine: trusted first, then user filters
315    let mut result = trusted;
316    result.extend(user);
317    Ok(result)
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn test_validator_default_is_secure() {
326        let validator = FilterValidator::new();
327        assert!(validator.allowed_fields.is_empty());
328        // new() now denies Regex by default for security
329        assert_eq!(validator.denied_operators, vec![Operator::Regex]);
330        assert_eq!(validator.max_depth, 5);
331    }
332
333    #[test]
334    fn test_validator_permissive() {
335        let validator = FilterValidator::permissive();
336        assert!(validator.allowed_fields.is_empty());
337        assert!(validator.denied_operators.is_empty());
338        assert_eq!(validator.max_depth, 5);
339    }
340
341    #[test]
342    fn test_validator_builder() {
343        let validator = FilterValidator::new()
344            .allow_fields(&["name", "email"])
345            .deny_operators(&[Operator::Regex, Operator::ILike])
346            .max_depth(3);
347
348        assert_eq!(validator.allowed_fields.len(), 2);
349        assert_eq!(validator.denied_operators.len(), 2);
350        assert_eq!(validator.max_depth, 3);
351    }
352
353    #[test]
354    fn test_validate_allowed_field() {
355        let validator = FilterValidator::new().allow_fields(&["name", "email", "status"]);
356
357        let filter = Filter {
358            field: "name".into(),
359            op: Operator::Eq,
360            value: Value::String("Alice".into()),
361        };
362
363        assert!(validator.validate(&filter).is_ok());
364    }
365
366    #[test]
367    fn test_validate_disallowed_field() {
368        let validator = FilterValidator::new().allow_fields(&["name", "email"]);
369
370        let filter = Filter {
371            field: "password".into(),
372            op: Operator::Eq,
373            value: Value::String("secret".into()),
374        };
375
376        let result = validator.validate(&filter);
377        assert!(result.is_err());
378
379        let ValidationError::FieldNotAllowed { field, allowed } = result.unwrap_err() else {
380            panic!("expected FieldNotAllowed, got different error variant")
381        };
382        assert_eq!(field, "password");
383        assert_eq!(allowed.len(), 2);
384    }
385
386    #[test]
387    fn test_validate_empty_whitelist_allows_all() {
388        let validator = FilterValidator::new(); // No field restrictions
389
390        let filter = Filter {
391            field: "any_field".into(),
392            op: Operator::Eq,
393            value: Value::String("value".into()),
394        };
395
396        assert!(validator.validate(&filter).is_ok());
397    }
398
399    #[test]
400    fn test_validate_denied_operator() {
401        let validator = FilterValidator::new()
402            .allow_fields(&["name"])
403            .deny_operators(&[Operator::Regex, Operator::ILike]);
404
405        let filter = Filter {
406            field: "name".into(),
407            op: Operator::Regex,
408            value: Value::String("^A".into()),
409        };
410
411        let result = validator.validate(&filter);
412        assert!(result.is_err());
413
414        let ValidationError::OperatorDenied { operator, field } = result.unwrap_err() else {
415            panic!("expected OperatorDenied, got different error variant")
416        };
417        assert_eq!(operator, Operator::Regex);
418        assert_eq!(field, "name");
419    }
420
421    #[test]
422    fn test_validate_allowed_operator() {
423        let validator = FilterValidator::new()
424            .allow_fields(&["status"])
425            .deny_operators(&[Operator::Regex]);
426
427        let filter = Filter {
428            field: "status".into(),
429            op: Operator::Eq,
430            value: Value::String("active".into()),
431        };
432
433        assert!(validator.validate(&filter).is_ok());
434    }
435
436    #[test]
437    fn test_validate_nesting_depth() {
438        let validator = FilterValidator::new().max_depth(2);
439
440        // Depth 0 - OK
441        let filter = Filter {
442            field: "tags".into(),
443            op: Operator::In,
444            value: Value::Array(vec![Value::String("rust".into())]),
445        };
446        assert!(validator.validate(&filter).is_ok());
447
448        // Depth 3 - exceeds max
449        let filter_deep = Filter {
450            field: "deep".into(),
451            op: Operator::In,
452            value: Value::Array(vec![Value::Array(vec![Value::Array(vec![Value::String(
453                "too deep".into(),
454            )])])]),
455        };
456        let result = validator.validate(&filter_deep);
457        assert!(result.is_err());
458
459        let ValidationError::NestingTooDeep { max, actual } = result.unwrap_err() else {
460            panic!("expected NestingTooDeep, got different error variant")
461        };
462        assert_eq!(max, 2);
463        assert!(actual > max);
464    }
465
466    #[test]
467    fn test_merge_filters_success() {
468        let validator = FilterValidator::new().allow_fields(&["status", "name"]);
469
470        let trusted = vec![
471            Filter {
472                field: "org_id".into(),
473                op: Operator::Eq,
474                value: Value::Int(123),
475            },
476            Filter {
477                field: "deleted_at".into(),
478                op: Operator::Eq,
479                value: Value::Null,
480            },
481        ];
482
483        let user = vec![Filter {
484            field: "status".into(),
485            op: Operator::Eq,
486            value: Value::String("active".into()),
487        }];
488
489        let result = merge_filters(trusted, user, &validator);
490        assert!(result.is_ok());
491
492        let filters = result.unwrap();
493        assert_eq!(filters.len(), 3);
494        assert_eq!(filters[0].field, "org_id");
495        assert_eq!(filters[1].field, "deleted_at");
496        assert_eq!(filters[2].field, "status");
497    }
498
499    #[test]
500    fn test_merge_filters_validation_error() {
501        let validator = FilterValidator::new().allow_fields(&["status"]);
502
503        let trusted = vec![Filter {
504            field: "org_id".into(),
505            op: Operator::Eq,
506            value: Value::Int(123),
507        }];
508
509        // User tries to filter on disallowed field
510        let user = vec![Filter {
511            field: "password".into(),
512            op: Operator::Eq,
513            value: Value::String("hack".into()),
514        }];
515
516        let result = merge_filters(trusted, user, &validator);
517        assert!(result.is_err());
518    }
519
520    #[test]
521    fn test_merge_filters_empty_user() {
522        let validator = FilterValidator::new();
523
524        let trusted = vec![Filter {
525            field: "org_id".into(),
526            op: Operator::Eq,
527            value: Value::Int(123),
528        }];
529
530        let user = vec![];
531
532        let result = merge_filters(trusted, user, &validator);
533        assert!(result.is_ok());
534        assert_eq!(result.unwrap().len(), 1);
535    }
536
537    #[test]
538    fn test_merge_filters_empty_trusted() {
539        let validator = FilterValidator::new().allow_fields(&["name"]);
540
541        let trusted = vec![];
542        let user = vec![Filter {
543            field: "name".into(),
544            op: Operator::Eq,
545            value: Value::String("Alice".into()),
546        }];
547
548        let result = merge_filters(trusted, user, &validator);
549        assert!(result.is_ok());
550        assert_eq!(result.unwrap().len(), 1);
551    }
552
553    #[test]
554    fn test_multiple_validation_errors() {
555        let validator = FilterValidator::new()
556            .allow_fields(&["status"])
557            .deny_operators(&[Operator::Regex]);
558
559        // Disallowed field
560        let filter1 = Filter {
561            field: "password".into(),
562            op: Operator::Eq,
563            value: Value::String("x".into()),
564        };
565        assert!(validator.validate(&filter1).is_err());
566
567        // Denied operator
568        let filter2 = Filter {
569            field: "status".into(),
570            op: Operator::Regex,
571            value: Value::String("^A".into()),
572        };
573        assert!(validator.validate(&filter2).is_err());
574    }
575
576    #[test]
577    fn test_validation_error_display() {
578        let err = ValidationError::FieldNotAllowed {
579            field: "password".into(),
580            allowed: vec!["name".into(), "email".into()],
581        };
582        let msg = format!("{err}");
583        assert!(msg.contains("password"));
584        assert!(msg.contains("name"));
585
586        let err = ValidationError::OperatorDenied {
587            operator: Operator::Regex,
588            field: "name".into(),
589        };
590        let msg = format!("{err}");
591        assert!(msg.contains("Regex"));
592        assert!(msg.contains("name"));
593
594        let err = ValidationError::NestingTooDeep { max: 3, actual: 5 };
595        let msg = format!("{err}");
596        assert!(msg.contains('3'));
597        assert!(msg.contains('5'));
598    }
599
600    #[test]
601    fn test_in_operator_validation() {
602        let validator = FilterValidator::new().allow_fields(&["status"]);
603
604        let filter = Filter {
605            field: "status".into(),
606            op: Operator::In,
607            value: Value::Array(vec![
608                Value::String("active".into()),
609                Value::String("pending".into()),
610            ]),
611        };
612
613        assert!(validator.validate(&filter).is_ok());
614    }
615
616    #[test]
617    fn test_not_in_operator_validation() {
618        let validator = FilterValidator::new()
619            .allow_fields(&["status"])
620            .deny_operators(&[Operator::NotIn]);
621
622        let filter = Filter {
623            field: "status".into(),
624            op: Operator::NotIn,
625            value: Value::Array(vec![Value::String("deleted".into())]),
626        };
627
628        assert!(validator.validate(&filter).is_err());
629    }
630
631    #[test]
632    fn test_null_value_validation() {
633        let validator = FilterValidator::new().allow_fields(&["deleted_at"]);
634
635        let filter = Filter {
636            field: "deleted_at".into(),
637            op: Operator::Eq,
638            value: Value::Null,
639        };
640
641        assert!(validator.validate(&filter).is_ok());
642    }
643
644    #[test]
645    fn test_bool_value_validation() {
646        let validator = FilterValidator::new().allow_fields(&["active"]);
647
648        let filter = Filter {
649            field: "active".into(),
650            op: Operator::Eq,
651            value: Value::Bool(true),
652        };
653
654        assert!(validator.validate(&filter).is_ok());
655    }
656
657    #[test]
658    fn test_numeric_value_validation() {
659        let validator = FilterValidator::new().allow_fields(&["age", "price"]);
660
661        let filter1 = Filter {
662            field: "age".into(),
663            op: Operator::Gte,
664            value: Value::Int(18),
665        };
666        assert!(validator.validate(&filter1).is_ok());
667
668        let filter2 = Filter {
669            field: "price".into(),
670            op: Operator::Lt,
671            value: Value::Float(99.99),
672        };
673        assert!(validator.validate(&filter2).is_ok());
674    }
675
676    #[test]
677    fn test_new_denies_regex_by_default() {
678        // new() now uses secure defaults
679        let validator = FilterValidator::new().allow_fields(&["name"]);
680
681        // Regex should be denied by default
682        let filter = Filter {
683            field: "name".into(),
684            op: Operator::Regex,
685            value: Value::String("^test".into()),
686        };
687
688        let result = validator.validate(&filter);
689        assert!(result.is_err());
690
691        let ValidationError::OperatorDenied { operator, .. } = result.unwrap_err() else {
692            panic!("expected OperatorDenied, got different error variant")
693        };
694        assert_eq!(operator, Operator::Regex);
695    }
696
697    #[test]
698    fn test_permissive_allows_regex() {
699        let validator = FilterValidator::permissive().allow_fields(&["name"]);
700
701        let filter = Filter {
702            field: "name".into(),
703            op: Operator::Regex,
704            value: Value::String("^test".into()),
705        };
706
707        // permissive() allows all operators
708        assert!(validator.validate(&filter).is_ok());
709    }
710
711    #[test]
712    fn test_new_allows_safe_operators() {
713        let validator = FilterValidator::new().allow_fields(&["name", "status"]);
714
715        // Safe operators should work
716        let filter = Filter {
717            field: "status".into(),
718            op: Operator::Eq,
719            value: Value::String("active".into()),
720        };
721        assert!(validator.validate(&filter).is_ok());
722
723        // Like is also allowed (less dangerous than regex)
724        let filter = Filter {
725            field: "name".into(),
726            op: Operator::Like,
727            value: Value::String("%test%".into()),
728        };
729        assert!(validator.validate(&filter).is_ok());
730    }
731
732    #[test]
733    fn test_validate_compound_filter_deep_nesting() {
734        use crate::builder::{CompoundFilter, FilterExpr, simple};
735
736        // Create deeply nested compound filters to test depth limits
737        // Build: AND(OR(AND(filter1, filter2), filter3), filter4)
738        let innermost = CompoundFilter::and(vec![
739            simple("a", Operator::Eq, Value::Int(1)),
740            simple("b", Operator::Eq, Value::Int(2)),
741        ]);
742
743        let middle = CompoundFilter::or(vec![
744            FilterExpr::Compound(innermost),
745            simple("c", Operator::Eq, Value::Int(3)),
746        ]);
747
748        let outer = CompoundFilter::and(vec![
749            FilterExpr::Compound(middle),
750            simple("d", Operator::Eq, Value::Int(4)),
751        ]);
752
753        // Validator with limited depth should reject this structure
754        // The nesting depth here is controlled by the number of Value nesting, not compound filter depth
755        // Compound filter depth is separate from value nesting
756        let validator = FilterValidator::new();
757
758        // For simple filter validation, the compound structure itself isn't checked
759        // Each simple filter should pass individually
760        let simple_filter = Filter {
761            field: "a".into(),
762            op: Operator::Eq,
763            value: Value::Int(1),
764        };
765        assert!(validator.validate(&simple_filter).is_ok());
766
767        // Verify compound filter can be constructed without panic
768        assert_eq!(outer.filters.len(), 2);
769        assert_eq!(outer.op, crate::LogicalOp::And);
770    }
771
772    #[test]
773    fn test_validate_compound_not_single_element() {
774        use crate::builder::{CompoundFilter, simple};
775
776        // NOT should wrap exactly one filter
777        let not_filter = CompoundFilter::not(simple("deleted", Operator::Eq, Value::Bool(true)));
778
779        assert_eq!(not_filter.filters.len(), 1);
780        assert_eq!(not_filter.op, crate::LogicalOp::Not);
781    }
782
783    #[test]
784    fn test_validate_compound_empty_filters() {
785        use crate::builder::CompoundFilter;
786
787        // Edge case: Compound filter with empty filter list
788        let empty_and = CompoundFilter::and(vec![]);
789        let empty_or = CompoundFilter::or(vec![]);
790
791        // Empty compound filters should have 0 filters
792        assert!(empty_and.filters.is_empty());
793        assert!(empty_or.filters.is_empty());
794    }
795
796    #[test]
797    fn test_validate_deeply_nested_array_values() {
798        // Test value nesting depth validation
799        let validator = FilterValidator::new().max_depth(2);
800
801        // 2 levels of nesting - should pass
802        let filter_ok = Filter {
803            field: "tags".into(),
804            op: Operator::In,
805            value: Value::Array(vec![Value::Array(vec![Value::Int(1)])]),
806        };
807        assert!(validator.validate(&filter_ok).is_ok());
808
809        // 3 levels of nesting - should fail with max_depth(2)
810        let filter_too_deep = Filter {
811            field: "tags".into(),
812            op: Operator::In,
813            value: Value::Array(vec![Value::Array(vec![Value::Array(vec![Value::Int(1)])])]),
814        };
815        assert!(validator.validate(&filter_too_deep).is_err());
816    }
817
818    #[test]
819    fn test_filter_value_injection() {
820        // Test that malicious values in filters are properly parameterized
821        // (This tests the design, not execution - values go through $1, $2 placeholders)
822        let validator = FilterValidator::new().allow_fields(&["name", "email"]);
823
824        // Malicious value in string - should be allowed because it's parameterized
825        let filter = Filter {
826            field: "name".into(),
827            op: Operator::Eq,
828            value: Value::String("'; DROP TABLE users--".into()),
829        };
830        // Filter validation passes - the value is parameterized, not interpolated
831        assert!(validator.validate(&filter).is_ok());
832
833        // But the SQL builder would produce:
834        // "SELECT * FROM users WHERE name = $1"
835        // With params: ["'; DROP TABLE users--"]
836        // This is SAFE because it's parameterized!
837    }
838
839    #[test]
840    fn test_filter_field_injection() {
841        // Test that malicious field names are blocked by whitelist
842        let validator = FilterValidator::new().allow_fields(&["name", "email"]);
843
844        // Attempting to use SQL injection as field name
845        let filter = Filter {
846            field: "name; DROP TABLE users--".into(),
847            op: Operator::Eq,
848            value: Value::String("test".into()),
849        };
850        // Should fail - field not in whitelist
851        assert!(validator.validate(&filter).is_err());
852
853        // Even without whitelist, the field goes through identifier validation
854        // when the query is built
855    }
856
857    #[test]
858    fn test_operator_based_attacks() {
859        // Certain operators could be used for attacks
860        let validator = FilterValidator::new()
861            .allow_fields(&["name"])
862            .deny_operators(&[Operator::Regex]); // ReDoS prevention
863
864        // Regex operator should be denied (ReDoS risk)
865        let filter = Filter {
866            field: "name".into(),
867            op: Operator::Regex,
868            value: Value::String("^(a+)+$".into()), // ReDoS pattern
869        };
870        assert!(validator.validate(&filter).is_err());
871
872        // LIKE is safer (no backtracking)
873        let filter = Filter {
874            field: "name".into(),
875            op: Operator::Like,
876            value: Value::String("%test%".into()),
877        };
878        assert!(validator.validate(&filter).is_ok());
879    }
880}