Skip to content

Commit be1b1b1

Browse files
authored
feat(iast): security controls (#13655)
Handle IAST security controls custom validation and sanitization methods ## Overview The `DD_IAST_SECURITY_CONTROLS_CONFIGURATION` environment variable allows you to specify custom sanitizers and validators that IAST should recognize when analyzing your application for security vulnerabilities. ## Format The configuration uses the following format: ``` CONTROL_TYPE:VULNERABILITY_TYPES:MODULE:METHOD[:PARAMETER_POSITIONS] ``` Multiple security controls are separated by semicolons (`;`). ### Fields 1. **CONTROL_TYPE**: Either `INPUT_VALIDATOR` or `SANITIZER` 2. **VULNERABILITY_TYPES**: Comma-separated list of vulnerability types or `*` for all types 3. **MODULE**: Python module path (e.g., `shlex`, `django.utils.http`) 4. **METHOD**: Method name to instrument 5. **PARAMETER_POSITIONS** (Optional): Zero-based parameter positions to validate (INPUT_VALIDATOR only) ### Vulnerability Types Supported vulnerability types: - `COMMAND_INJECTION` / `CMDI` - `CODE_INJECTION` - `SQL_INJECTION` / `SQLI` - `XSS` - `HEADER_INJECTION` - `PATH_TRAVERSAL` - `SSRF` - `UNVALIDATED_REDIRECT` - `INSECURE_COOKIE` - `NO_HTTPONLY_COOKIE` - `NO_SAMESITE_COOKIE` - `WEAK_CIPHER` - `WEAK_HASH` - `WEAK_RANDOMNESS` - `STACKTRACE_LEAK` Use `*` to apply to all vulnerability types. ## Examples ### Basic Examples #### Input Validator for Command Injection ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION:shlex:quote" ``` #### Sanitizer for XSS ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="SANITIZER:XSS:html:escape" ``` #### Multiple Vulnerability Types ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION,XSS:custom.validator:validate_input" ``` #### All Vulnerability Types ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="SANITIZER:*:custom.sanitizer:sanitize_all" ``` ### Advanced Examples #### Multiple Security Controls ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION:shlex:quote;SANITIZER:XSS:html:escape;SANITIZER:SQLI:custom.db:escape_sql" ``` #### Validator with Specific Parameter Positions ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION:custom.validator:validate:0,2" ``` This validates only the 1st and 3rd parameters (0-based indexing). #### Complex Configuration ```bash export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION,XSS:security.validators:validate_user_input:0,1;SANITIZER:SQLI:database.utils:escape_sql_string;SANITIZER:*:security.sanitizers:clean_all_inputs" ``` ## How It Works ### Input Validators - **Purpose**: Mark input parameters as safe after validation - **When to use**: When your function validates input and returns a boolean or throws an exception - **Effect**: Parameters are marked as secure for the specified vulnerability types - **Parameter positions**: Optionally specify which parameters to mark (0-based index) ### Sanitizers - **Purpose**: Mark return values as safe after sanitization - **When to use**: When your function cleans/escapes input and returns the sanitized value - **Effect**: Return value is marked as secure for the specified vulnerability types ## Integration with Existing Controls Your custom security controls work alongside the built-in IAST security controls: - `shlex.quote` (Command injection sanitizer) - `html.escape` (XSS sanitizer) - Database escape functions (SQL injection sanitizers) - Django validators (Various validators) - And more... ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent e2776d5 commit be1b1b1

File tree

18 files changed

+929
-21
lines changed

18 files changed

+929
-21
lines changed

ddtrace/appsec/_common_module_patches.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def try_unwrap(module, name):
328328
original = _DD_ORIGINAL_ATTRIBUTES[(parent, attribute)]
329329
apply_patch(parent, attribute, original)
330330
del _DD_ORIGINAL_ATTRIBUTES[(parent, attribute)]
331-
except ModuleNotFoundError:
331+
except (ModuleNotFoundError, AttributeError):
332332
log.debug("ERROR unwrapping %s.%s ", module, name)
333333

334334

ddtrace/appsec/_iast/_patch_modules.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
The module uses wrapt's function wrapping capabilities to intercept calls to security-sensitive
1111
functions and enable taint tracking and vulnerability detection.
1212
"""
13-
13+
import functools
1414
from typing import Callable
15+
from typing import Optional
1516
from typing import Set
1617
from typing import Text
1718

@@ -21,6 +22,12 @@
2122
from ddtrace.appsec._common_module_patches import try_wrap_function_wrapper
2223
from ddtrace.appsec._common_module_patches import wrap_object
2324
from ddtrace.appsec._iast._logs import iast_instrumentation_wrapt_debug_log
25+
from ddtrace.appsec._iast.secure_marks import SecurityControl
26+
from ddtrace.appsec._iast.secure_marks import get_security_controls_from_env
27+
from ddtrace.appsec._iast.secure_marks.configuration import SC_SANITIZER
28+
from ddtrace.appsec._iast.secure_marks.configuration import SC_VALIDATOR
29+
from ddtrace.appsec._iast.secure_marks.sanitizers import create_sanitizer
30+
from ddtrace.appsec._iast.secure_marks.validators import create_validator
2431
from ddtrace.internal.logger import get_logger
2532
from ddtrace.settings.asm import config as asm_config
2633

@@ -181,3 +188,55 @@ def _testing_unpatch_iast():
181188
"""
182189
iast_funcs = WrapFunctonsForIAST()
183190
iast_funcs.testing_unpatch()
191+
192+
193+
def _apply_custom_security_controls(iast_funcs: Optional[WrapFunctonsForIAST] = None):
194+
"""Apply custom security controls from DD_IAST_SECURITY_CONTROLS_CONFIGURATION environment variable."""
195+
try:
196+
if iast_funcs is None:
197+
iast_funcs = WrapFunctonsForIAST()
198+
security_controls = get_security_controls_from_env()
199+
200+
if not security_controls:
201+
log.debug("No custom security controls configured")
202+
return
203+
204+
log.debug("Applying %s custom security controls", len(security_controls))
205+
206+
for control in security_controls:
207+
try:
208+
_apply_security_control(iast_funcs, control)
209+
except Exception:
210+
log.warning("Failed to apply security control %s", control, exc_info=True)
211+
return iast_funcs
212+
except Exception:
213+
log.warning("Failed to load custom security controls", exc_info=True)
214+
215+
216+
def _apply_security_control(iast_funcs: WrapFunctonsForIAST, control: SecurityControl):
217+
"""Apply a single security control configuration.
218+
219+
Args:
220+
control: SecurityControl object containing the configuration
221+
"""
222+
# Create the appropriate wrapper function
223+
if control.control_type == SC_SANITIZER:
224+
wrapper_func = functools.partial(create_sanitizer, control.vulnerability_types)
225+
elif control.control_type == SC_VALIDATOR:
226+
wrapper_func = functools.partial(create_validator, control.vulnerability_types, control.parameters)
227+
else:
228+
log.warning("Unknown control type: %s", control.control_type)
229+
return
230+
231+
iast_funcs.wrap_function(
232+
control.module_path,
233+
control.method_name,
234+
wrapper_func,
235+
)
236+
log.debug(
237+
"Configured %s for %s.%s (vulnerabilities: %s)",
238+
control.control_type,
239+
control.module_path,
240+
control.method_name,
241+
[v.name for v in control.vulnerability_types],
242+
)

ddtrace/appsec/_iast/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from wrapt import when_imported
2424

2525
from ddtrace.appsec._iast._patch_modules import WrapFunctonsForIAST
26+
from ddtrace.appsec._iast._patch_modules import _apply_custom_security_controls
2627
from ddtrace.appsec._iast.secure_marks import cmdi_sanitizer
2728
from ddtrace.appsec._iast.secure_marks import path_traversal_sanitizer
2829
from ddtrace.appsec._iast.secure_marks import sqli_sanitizer
@@ -72,6 +73,9 @@ def patch_iast(patch_modules=IAST_PATCH):
7273
when_imported("hashlib")(_on_import_factory(module, "ddtrace.appsec._iast.taint_sinks.%s", raise_errors=False))
7374

7475
iast_funcs = WrapFunctonsForIAST()
76+
77+
_apply_custom_security_controls(iast_funcs)
78+
7579
# CMDI sanitizers
7680
iast_funcs.wrap_function("shlex", "quote", cmdi_sanitizer)
7781

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# IAST Security Controls Configuration
2+
3+
This document explains how to configure custom security controls for IAST using the `DD_IAST_SECURITY_CONTROLS_CONFIGURATION` environment variable.
4+
5+
## Overview
6+
7+
The `DD_IAST_SECURITY_CONTROLS_CONFIGURATION` environment variable allows you to specify custom sanitizers and validators that IAST should recognize when analyzing your application for security vulnerabilities.
8+
9+
## Format
10+
11+
The configuration uses the following format:
12+
13+
```
14+
CONTROL_TYPE:VULNERABILITY_TYPES:MODULE:METHOD[:PARAMETER_POSITIONS]
15+
```
16+
17+
Multiple security controls are separated by semicolons (`;`).
18+
19+
### Fields
20+
21+
1. **CONTROL_TYPE**: Either `INPUT_VALIDATOR` or `SANITIZER`
22+
2. **VULNERABILITY_TYPES**: Comma-separated list of vulnerability types or `*` for all types
23+
3. **MODULE**: Python module path (e.g., `shlex`, `django.utils.http`)
24+
4. **METHOD**: Method name to instrument
25+
5. **PARAMETER_POSITIONS** (Optional): Zero-based parameter positions to validate (INPUT_VALIDATOR only)
26+
27+
### Vulnerability Types
28+
29+
Supported vulnerability types:
30+
- `COMMAND_INJECTION` / `CMDI`
31+
- `CODE_INJECTION`
32+
- `SQL_INJECTION` / `SQLI`
33+
- `XSS`
34+
- `HEADER_INJECTION`
35+
- `PATH_TRAVERSAL`
36+
- `SSRF`
37+
- `UNVALIDATED_REDIRECT`
38+
- `INSECURE_COOKIE`
39+
- `NO_HTTPONLY_COOKIE`
40+
- `NO_SAMESITE_COOKIE`
41+
- `WEAK_CIPHER`
42+
- `WEAK_HASH`
43+
- `WEAK_RANDOMNESS`
44+
- `STACKTRACE_LEAK`
45+
46+
Use `*` to apply to all vulnerability types.
47+
48+
## Examples
49+
50+
### Basic Examples
51+
52+
#### Input Validator for Command Injection
53+
```bash
54+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION:shlex:quote"
55+
```
56+
57+
#### Sanitizer for XSS
58+
```bash
59+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="SANITIZER:XSS:html:escape"
60+
```
61+
62+
#### Multiple Vulnerability Types
63+
```bash
64+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION,XSS:custom.validator:validate_input"
65+
```
66+
67+
#### All Vulnerability Types
68+
```bash
69+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="SANITIZER:*:custom.sanitizer:sanitize_all"
70+
```
71+
72+
### Advanced Examples
73+
74+
#### Multiple Security Controls
75+
```bash
76+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION:shlex:quote;SANITIZER:XSS:html:escape;SANITIZER:SQLI:custom.db:escape_sql"
77+
```
78+
79+
#### Validator with Specific Parameter Positions
80+
```bash
81+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION:custom.validator:validate:0,2"
82+
```
83+
This validates only the 1st and 3rd parameters (0-based indexing).
84+
85+
#### Complex Configuration
86+
```bash
87+
export DD_IAST_SECURITY_CONTROLS_CONFIGURATION="INPUT_VALIDATOR:COMMAND_INJECTION,XSS:security.validators:validate_user_input:0,1;SANITIZER:SQLI:database.utils:escape_sql_string;SANITIZER:*:security.sanitizers:clean_all_inputs"
88+
```
89+
90+
## How It Works
91+
92+
### Input Validators
93+
- **Purpose**: Mark input parameters as safe after validation
94+
- **When to use**: When your function validates input and returns a boolean or throws an exception
95+
- **Effect**: Parameters are marked as secure for the specified vulnerability types
96+
- **Parameter positions**: Optionally specify which parameters to mark (0-based index)
97+
98+
### Sanitizers
99+
- **Purpose**: Mark return values as safe after sanitization
100+
- **When to use**: When your function cleans/escapes input and returns the sanitized value
101+
- **Effect**: Return value is marked as secure for the specified vulnerability types
102+
103+
## Integration with Existing Controls
104+
105+
Your custom security controls work alongside the built-in IAST security controls:
106+
107+
- `shlex.quote` (Command injection sanitizer)
108+
- `html.escape` (XSS sanitizer)
109+
- Database escape functions (SQL injection sanitizers)
110+
- Django validators (Various validators)
111+
- And more...
112+
113+
## Error Handling
114+
115+
If there are errors in the configuration:
116+
- Invalid configurations are logged and skipped
117+
- The application continues to run with built-in security controls
118+
- Check application logs for configuration warnings/errors

ddtrace/appsec/_iast/secure_marks/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33
This package provides functions to mark values as secure from specific vulnerabilities.
44
It includes both sanitizers (which transform and secure values) and validators (which
55
verify values are secure).
6+
7+
It also provides configuration capabilities for custom security controls via the
8+
DD_IAST_SECURITY_CONTROLS_CONFIGURATION environment variable.
69
"""
710

11+
from .configuration import VULNERABILITY_TYPE_MAPPING
12+
from .configuration import SecurityControl
13+
from .configuration import get_security_controls_from_env
14+
from .configuration import parse_security_controls_config
815
from .sanitizers import cmdi_sanitizer
916
from .sanitizers import path_traversal_sanitizer
1017
from .sanitizers import sqli_sanitizer
@@ -22,4 +29,9 @@
2229
"path_traversal_validator",
2330
"sqli_validator",
2431
"cmdi_validator",
32+
# Configuration
33+
"get_security_controls_from_env",
34+
"parse_security_controls_config",
35+
"SecurityControl",
36+
"VULNERABILITY_TYPE_MAPPING",
2537
]

0 commit comments

Comments
 (0)