Skip to content

Commit fbc3e74

Browse files
avara1986brettlangdon
authored andcommitted
chore(iast): fix iast in the ci (#15175)
# Summary This PR extends **PR #15141**'s fork safety implementation to properly handle IAST in the CI/pytest environment. It fixes two critical issues: 1. **Threading race conditions** in FastAPI tests (Python 3.12) with anyio's multi-threaded execution 2. **Fork safety in pytest** by ensuring IAST is always disabled in child processes during test execution The solution adds a pytest-specific mode flag that enhances the existing fork handler without affecting production behavior. ## Context This PR builds on top of **PR #15141** which implemented fork safety for IAST by disabling it in multiprocess child processes. That PR successfully prevented segfaults in production multiprocessing scenarios but revealed additional issues in the CI/pytest environment.
1 parent 5f68267 commit fbc3e74

File tree

4 files changed

+31
-19
lines changed

4 files changed

+31
-19
lines changed

ddtrace/appsec/_iast/__init__.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def wrapped_function(wrapped, instance, args, kwargs):
4646
_IAST_TO_BE_LOADED = True
4747
_iast_propagation_enabled = False
4848
_fork_handler_registered = False
49+
_iast_in_pytest_mode = False
4950

5051

5152
def _disable_iast_after_fork():
@@ -87,6 +88,15 @@ def _disable_iast_after_fork():
8788
from ddtrace.appsec._iast._iast_request_context_base import is_iast_request_enabled
8889
from ddtrace.appsec._iast._taint_tracking._context import clear_all_request_context_slots
8990

91+
# In pytest mode, always disable IAST in child processes to avoid segfaults
92+
# when tests create multiprocesses (e.g., for testing fork behavior)
93+
if _iast_in_pytest_mode:
94+
log.debug("IAST fork handler: Pytest mode detected, disabling IAST in child process")
95+
clear_all_request_context_slots()
96+
IAST_CONTEXT.set(None)
97+
asm_config._iast_enabled = False
98+
return
99+
90100
if not is_iast_request_enabled():
91101
# No active context - this is an early fork (web framework worker)
92102
# IAST can be safely initialized fresh in this child process
@@ -168,6 +178,7 @@ def enable_iast_propagation():
168178
"""Add IAST AST patching in the ModuleWatchdog"""
169179
# DEV: These imports are here to avoid _ast.ast_patching import in the top level
170180
# because they are slow and affect serverless startup time
181+
171182
if asm_config._iast_propagation_enabled:
172183
from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch
173184
from ddtrace.appsec._iast._loader import _exec_iast_patched_module
@@ -183,19 +194,30 @@ def enable_iast_propagation():
183194

184195

185196
def _iast_pytest_activation():
186-
global _iast_propagation_enabled
187-
if _iast_propagation_enabled:
197+
"""Configure IAST settings for pytest execution.
198+
199+
This function sets up IAST configuration but does NOT create a request context.
200+
Request contexts should be created per-test or per-request to avoid threading issues.
201+
202+
Also sets a global flag to indicate we're in pytest mode, which ensures IAST is
203+
disabled in forked child processes to prevent segfaults when tests use multiprocessing.
204+
"""
205+
global _iast_in_pytest_mode
206+
207+
if not asm_config._iast_enabled:
188208
return
189-
os.environ["DD_IAST_ENABLED"] = os.environ.get("DD_IAST_ENABLED") or "1"
209+
210+
# Mark that we're running in pytest mode
211+
# This flag is checked by the fork handler to disable IAST in child processes
212+
_iast_in_pytest_mode = True
213+
190214
os.environ["DD_IAST_REQUEST_SAMPLING"] = os.environ.get("DD_IAST_REQUEST_SAMPLING") or "100.0"
191215
os.environ["_DD_APPSEC_DEDUPLICATION_ENABLED"] = os.environ.get("_DD_APPSEC_DEDUPLICATION_ENABLED") or "false"
192216
os.environ["DD_IAST_VULNERABILITIES_PER_REQUEST"] = os.environ.get("DD_IAST_VULNERABILITIES_PER_REQUEST") or "1000"
193-
os.environ["DD_IAST_MAX_CONCURRENT_REQUESTS"] = os.environ.get("DD_IAST_MAX_CONCURRENT_REQUESTS") or "1000"
194217

195218
asm_config._iast_request_sampling = 100.0
196219
asm_config._deduplication_enabled = False
197220
asm_config._iast_max_vulnerabilities_per_requests = 1000
198-
asm_config._iast_max_concurrent_requests = 1000
199221
oce.reconfigure()
200222

201223

ddtrace/settings/asm.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ class ASMConfig(DDConfig):
140140
+ r"ey[I-L][\w=-]+\.ey[I-L][\w=-]+(\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY"
141141
+ r"[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}",
142142
)
143+
# We never use `asm_config._iast_max_concurrent_requests` directly,
144+
# but we define it so it can be reported through telemetry, since it’s used from the C files.
143145
_iast_max_concurrent_requests = DDConfig.var(
144146
int,
145147
IAST.DD_IAST_MAX_CONCURRENT_REQUESTS,

tests/appsec/iast/aspects/conftest.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88
@pytest.fixture(autouse=True)
99
def iast_request():
1010
with override_global_config(
11-
dict(
12-
_iast_enabled=True,
13-
_iast_is_testing=True,
14-
_iast_deduplication_enabled=False,
15-
_iast_request_sampling=100.0,
16-
_iast_max_concurrent_requests=100,
17-
)
11+
dict(_iast_enabled=True, _iast_is_testing=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0)
1812
):
1913
context_id = _start_iast_context_and_oce()
2014
assert context_id is not None

tests/appsec/iast/taint_tracking/conftest.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@
99
@pytest.fixture(autouse=True)
1010
def iast_request():
1111
with override_global_config(
12-
dict(
13-
_iast_enabled=True,
14-
_iast_is_testing=True,
15-
_iast_deduplication_enabled=False,
16-
_iast_request_sampling=100.0,
17-
_iast_max_concurrent_requests=100,
18-
)
12+
dict(_iast_enabled=True, _iast_is_testing=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0)
1913
):
2014
assert debug_context_array_size() == 2
2115
_start_iast_context_and_oce()

0 commit comments

Comments
 (0)