security_rust/injection/
graphql_injection.rs1use crate::{regex_detect, AttackCategory, DetectionResult, Detector, Severity};
4use regex::Regex;
5use std::sync::LazyLock;
6
7static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
8 vec![
9 Regex::new(r"(?i)__schema").unwrap(),
10 Regex::new(r"(?i)__type\s*\{").unwrap(),
11 Regex::new(r"(?i)__typename").unwrap(),
12 Regex::new(r"\{[^{}]*\{[^{}]*\{[^{}]*\{[^{}]*\{").unwrap(),
13 ]
14});
15
16pub struct GraphQlInjectionDetector;
17
18impl Detector for GraphQlInjectionDetector {
19 fn name(&self) -> &'static str {
20 "graphql_injection"
21 }
22
23 fn detect(&self, input: &str) -> Option<DetectionResult> {
24 regex_detect(&PATTERNS, self.name(), AttackCategory::Injection, Severity::Medium, "GraphQL injection/introspection detected", input)
25 }
26}
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31
32 fn det() -> GraphQlInjectionDetector {
33 GraphQlInjectionDetector
34 }
35
36 fn assert_hit(input: &str) {
37 crate::test_helpers::assert_detected(
38 &det(),
39 input,
40 AttackCategory::Injection,
41 Severity::Medium,
42 );
43 }
44
45 #[test]
46 fn name_is_graphql_injection() {
47 assert_eq!(det().name(), "graphql_injection");
48 }
49
50 #[test]
51 fn detects_common_payloads() {
52 for input in [
53 "{ __schema { types { name } } }",
54 "query { __type { name } }",
55 "query { __typename }",
56 "{a{b{c{d{e{f}}}}}}",
57 "fragment F on __Type { name }",
58 ] {
59 assert_hit(input);
60 }
61 }
62
63 #[test]
64 fn benign_inputs_not_detected() {
65 for input in [
66 "Hello, this is a normal text input. Nothing suspicious here.",
67 "query { user(id: 1) { name } }",
68 r#"{"a": {"b": {"c": {"d": 1}}}}"#,
69 "The schema was updated today",
70 ] {
71 assert!(det().detect(input).is_none(), "false positive: {input}");
72 }
73 }
74
75 #[test]
76 fn edge_cases() {
77 assert!(det().detect("").is_none());
78 assert!(det().detect(" \t\n ").is_none());
79 assert!(det().detect("你好世界 こんにちは").is_none());
80 assert!(det().detect("{__schem}").is_none());
82 assert!(det().detect("schema").is_none());
83 assert!(det().detect("{{{{").is_none());
84 }
85
86 #[test]
87 fn obfuscated_variants_detected() {
88 for input in ["{ __SCHEMA { types } }", "__TYPENAME", "__Type { name }"] {
89 assert_hit(input);
90 }
91 }
92}