Skip to content

Commit 6c71a6d

Browse files
committed
refactor(appsec): remove ASM and IAST import cycle
1 parent 3a825cc commit 6c71a6d

13 files changed

Lines changed: 114 additions & 90 deletions

ddtrace/appsec/_asm_request_context.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ddtrace.appsec._constants import APPSEC
1717
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
1818
from ddtrace.appsec._constants import SPAN_DATA_NAMES
19+
from ddtrace.appsec._iast_request_state import iast_suppress_context
1920
from ddtrace.appsec._metrics import UNKNOWN_VERSION
2021
from ddtrace.appsec._metrics import report_waf_run_error
2122
from ddtrace.appsec._metrics import report_waf_truncation
@@ -788,7 +789,5 @@ def asm_listen() -> None:
788789

789790
def iast_disabled_taint_sources() -> "contextlib.AbstractContextManager[None]":
790791
if asm_config._iast_enabled:
791-
from ddtrace.appsec._iast._iast_request_context_base import iast_suppress_context
792-
793792
return iast_suppress_context()
794793
return contextlib.nullcontext()

ddtrace/appsec/_exploit_prevention/stack_traces.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from typing import Optional
77

88
from ddtrace._trace.span import Span
9-
from ddtrace.appsec import _asm_request_context
109
from ddtrace.appsec._constants import STACK_TRACE
1110
from ddtrace.internal import span_bus
1211
from ddtrace.internal.settings.asm import config as asm_config
@@ -17,41 +16,39 @@
1716
) + (os.path.sep + "wrapt" + os.path.sep,)
1817

1918

19+
def _get_stack_span() -> Optional[Span]:
20+
span = span_bus.get_span()
21+
if span is not None:
22+
return span._service_entry_span
23+
return span_bus.get_root_span()
24+
25+
2026
def report_stack(
2127
message: Optional[str] = None,
2228
span: Optional[Span] = None,
2329
crop_stack: Optional[str] = None,
2430
stack_id: Optional[str] = None,
2531
namespace: str = STACK_TRACE.RASP,
2632
) -> bool:
27-
"""
28-
Report a stack trace to the current span.
29-
This is used to report stack traces for exploit prevention.
30-
Return the stack id for the reported stack trace to link it in triggers.
31-
"""
33+
"""Report an AppSec stack trace to a span."""
3234
if not asm_config._ep_stack_trace_enabled:
33-
# stack trace report disabled
3435
return False
3536
if namespace == STACK_TRACE.RASP and not (asm_config._asm_enabled and asm_config._ep_enabled):
36-
# exploit prevention stack trace with ep disabled
3737
return False
38-
if namespace == STACK_TRACE.IAST and not (asm_config._iast_enabled):
39-
# iast stack trace with iast disabled
38+
if namespace == STACK_TRACE.IAST and not asm_config._iast_enabled:
4039
return False
4140

4241
if namespace == STACK_TRACE.IAST and asm_config._iast_use_root_span:
4342
span = span_bus.get_root_span()
44-
45-
if span is None:
46-
span = _asm_request_context.get_entry_span()
43+
elif span is None:
44+
span = _get_stack_span()
4745

4846
if span is None or stack_id is None:
4947
return False
5048
appsec_traces = span._get_struct_tag(STACK_TRACE.TAG) or {}
5149
current_list = appsec_traces.get(namespace, [])
5250
total_length = len(current_list)
5351

54-
# Do not report more than the maximum number of stack traces
5552
if asm_config._ep_max_stack_traces and total_length >= asm_config._ep_max_stack_traces:
5653
return False
5754

@@ -62,7 +59,6 @@ def report_stack(
6259
if frame.frame.f_code.co_name == crop_stack:
6360
crop_index = i + 1
6461
break
65-
# Strip any remaining ddtrace/wrapt frames from the top of the stack
6662
while crop_index < len(stack) and any(d in stack[crop_index].filename for d in _INTERNAL_FRAMES):
6763
crop_index += 1
6864
if crop_index:

ddtrace/appsec/_iast/_iast_request_context_base.py

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,25 @@
1-
import contextlib
2-
import contextvars
31
from typing import Optional
42

53
from ddtrace.appsec._constants import IAST
64
from ddtrace.appsec._constants import IAST_SPAN_TAGS
75
from ddtrace.appsec._iast._iast_env import IASTEnvironment
86
from ddtrace.appsec._iast._iast_env import _get_iast_env
97
from ddtrace.appsec._iast._overhead_control_engine import oce
10-
from ddtrace.appsec._iast._taint_tracking._context import debug_num_tainted_objects
8+
from ddtrace.appsec._iast._request_taint_state import _num_objects_tainted_in_request
119
from ddtrace.appsec._iast._taint_tracking._context import finish_request_context
1210
from ddtrace.appsec._iast._taint_tracking._context import start_request_context
1311
from ddtrace.appsec._iast.sampling.vulnerability_detection import update_global_vulnerability_limit
12+
from ddtrace.appsec._iast_request_state import IAST_CONTEXT
13+
from ddtrace.appsec._iast_request_state import _get_iast_context_id
14+
from ddtrace.appsec._iast_request_state import iast_suppress_context as iast_suppress_context # noqa: F401
15+
from ddtrace.appsec._iast_request_state import is_iast_request_enabled as is_iast_request_enabled
1416
from ddtrace.internal import core
1517
from ddtrace.internal.logger import get_logger
1618
from ddtrace.internal.settings.asm import config as asm_config
1719

1820

1921
log = get_logger(__name__)
2022

21-
# Stopgap module for providing ASM context for the blocking features wrapping some contextvars.
22-
23-
IAST_CONTEXT: contextvars.ContextVar[Optional[int]] = contextvars.ContextVar("iast_var", default=None)
24-
25-
# Keep source suppression separate from IAST_CONTEXT. Clearing the
26-
# request context id disables request-scoped taint queries and propagation and
27-
# can send no-context queries through unsafe native fallback paths.
28-
_IAST_TAINT_SOURCES_SUPPRESSED: contextvars.ContextVar[bool] = contextvars.ContextVar(
29-
"iast_taint_sources_suppressed", default=False
30-
)
31-
32-
33-
@contextlib.contextmanager
34-
def iast_suppress_context():
35-
"""Temporarily disable IAST taint *source* generation for the current context."""
36-
token = _IAST_TAINT_SOURCES_SUPPRESSED.set(True)
37-
try:
38-
yield
39-
finally:
40-
_IAST_TAINT_SOURCES_SUPPRESSED.reset(token)
41-
42-
43-
def _is_iast_taint_source_enabled() -> bool:
44-
return not _IAST_TAINT_SOURCES_SUPPRESSED.get()
45-
4623

4724
def _set_span_tag_iast_request_tainted(span):
4825
total_objects_tainted = _num_objects_tainted_in_request()
@@ -102,11 +79,6 @@ def _iast_start_request(span=None) -> Optional[int]:
10279
return context_id
10380

10481

105-
def _get_iast_context_id() -> Optional[int]:
106-
"""Retrieve the current IAST context identifier from the ContextVar."""
107-
return IAST_CONTEXT.get()
108-
109-
11082
def _iast_finish_request(span=None, shoud_update_global_vulnerability_limit: bool = True) -> bool:
11183
"""Finalize the IAST request context and optionally update global limits.
11284
@@ -129,22 +101,6 @@ def _iast_finish_request(span=None, shoud_update_global_vulnerability_limit: boo
129101
return False
130102

131103

132-
def is_iast_request_enabled() -> bool:
133-
"""Check whether IAST is currently operating within an active request context."""
134-
return _get_iast_context_id() is not None
135-
136-
137-
def _num_objects_tainted_in_request() -> int:
138-
"""Get the count of tainted objects tracked in the active IAST request context.
139-
140-
Useful for span metrics and internal telemetry.
141-
"""
142-
context_id = _get_iast_context_id()
143-
if context_id is not None:
144-
return debug_num_tainted_objects(context_id)
145-
return 0
146-
147-
148104
def get_hash_object_tracking_len():
149105
env = _get_iast_env()
150106
if env:

ddtrace/appsec/_iast/_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from ddtrace.appsec._constants import TELEMETRY_INFORMATION_VERBOSITY
66
from ddtrace.appsec._constants import TELEMETRY_MANDATORY_VERBOSITY
77
from ddtrace.appsec._deduplications import deduplication
8-
from ddtrace.appsec._iast._iast_request_context_base import _num_objects_tainted_in_request
8+
from ddtrace.appsec._iast._request_taint_state import _num_objects_tainted_in_request
99
from ddtrace.appsec._iast._taint_tracking import OriginType
1010
from ddtrace.appsec._iast._taint_tracking import origin_to_str
1111
from ddtrace.appsec._iast._utils import _is_iast_debug_enabled
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from ddtrace.appsec._iast._taint_tracking._context import debug_num_tainted_objects
2+
from ddtrace.appsec._iast_request_state import _get_iast_context_id
3+
4+
5+
def _num_objects_tainted_in_request() -> int:
6+
"""Return the number of objects tainted in the current request."""
7+
context_id = _get_iast_context_id()
8+
if context_id is not None:
9+
num_tainted: int = debug_num_tainted_objects(context_id)
10+
return num_tainted
11+
return 0

ddtrace/appsec/_iast/_span_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ddtrace.appsec._constants import IAST_SPAN_TAGS
22
from ddtrace.appsec._iast._iast_env import _get_iast_env
3-
from ddtrace.appsec._iast._iast_request_context_base import _num_objects_tainted_in_request
43
from ddtrace.appsec._iast._metrics import _metric_key_as_snake_case
4+
from ddtrace.appsec._iast._request_taint_state import _num_objects_tainted_in_request
55

66

77
def _set_span_tag_iast_request_tainted(span):

ddtrace/appsec/_iast/_taint_tracking/__init__.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,35 +43,24 @@
4343
from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import shift_taint_ranges # noqa: F401
4444
from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import str_to_origin # noqa: F401
4545
from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import taint_range as TaintRange # noqa: F401
46+
from ddtrace.appsec._iast_request_state import _get_iast_context_id
4647
from ddtrace.internal.logger import get_logger
4748

4849

4950
log = get_logger(__name__)
5051

51-
# AIDEV-NOTE: _get_iast_context_id is imported lazily — a top-level import here
52-
# circularly bootstraps via _iast_request_context_base -> _taint_tracking._context
53-
# -> _taint_tracking/__init__.py. The cached module-global avoids the per-call
54-
# import dance on this hot path.
55-
_CACHE_GET_IAST_CONTEXT_ID = None
52+
# AIDEV-NOTE: Resolve the active native context through the lightweight request-state
53+
# module. Keeping lifecycle imports out of taint tracking avoids circular initialization
54+
# and makes this hot path a direct ContextVar lookup.
5655

5756

5857
def _current_iast_context_id() -> Optional[int]:
59-
global _CACHE_GET_IAST_CONTEXT_ID
60-
if _CACHE_GET_IAST_CONTEXT_ID is None:
61-
from ddtrace.appsec._iast._iast_request_context_base import _get_iast_context_id
62-
63-
_CACHE_GET_IAST_CONTEXT_ID = _get_iast_context_id
64-
return _CACHE_GET_IAST_CONTEXT_ID()
58+
return _get_iast_context_id()
6559

6660

6761
def get_ranges(string_input: Any, context_id: Optional[int] = None) -> Any:
6862
if context_id is None:
69-
global _CACHE_GET_IAST_CONTEXT_ID
70-
if _CACHE_GET_IAST_CONTEXT_ID is None:
71-
from ddtrace.appsec._iast._iast_request_context_base import _get_iast_context_id
72-
73-
_CACHE_GET_IAST_CONTEXT_ID = _get_iast_context_id
74-
context_id = _CACHE_GET_IAST_CONTEXT_ID()
63+
context_id = _get_iast_context_id()
7564
if context_id is None:
7665
return []
7766
return _native_get_ranges(string_input, context_id)

ddtrace/appsec/_iast/_taint_tracking/_taint_objects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
from ddtrace.appsec._constants import IAST
66
from ddtrace.appsec._constants import IAST_SPAN_TAGS
7-
from ddtrace.appsec._iast._iast_request_context_base import _get_iast_context_id
8-
from ddtrace.appsec._iast._iast_request_context_base import _is_iast_taint_source_enabled
97
from ddtrace.appsec._iast._logs import iast_propagation_debug_log
108
from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_source
119
from ddtrace.appsec._iast._span_metrics import increment_iast_span_metric
1210
from ddtrace.appsec._iast._taint_tracking import OriginType
1311
from ddtrace.appsec._iast._taint_tracking import TaintRange
1412
from ddtrace.appsec._iast._taint_tracking import set_ranges
1513
from ddtrace.appsec._iast._taint_tracking._taint_objects_base import _taint_pyobject_base
14+
from ddtrace.appsec._iast_request_state import _get_iast_context_id
15+
from ddtrace.appsec._iast_request_state import _is_iast_taint_source_enabled
1616
from ddtrace.internal.logger import get_logger
1717

1818

ddtrace/appsec/_iast/_taint_tracking/_taint_objects_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from typing import Any
22

33
from ddtrace.appsec._constants import IAST
4-
from ddtrace.appsec._iast._iast_request_context_base import _get_iast_context_id
54
from ddtrace.appsec._iast._logs import iast_propagation_debug_log
65
from ddtrace.appsec._iast._logs import iast_propagation_error_log
76
from ddtrace.appsec._iast._taint_tracking import OriginType
87
from ddtrace.appsec._iast._taint_tracking import get_ranges
98
from ddtrace.appsec._iast._taint_tracking import origin_to_str
109
from ddtrace.appsec._iast._taint_tracking import taint_pyobject
1110
from ddtrace.appsec._iast._taint_tracking._context import is_in_taint_map
11+
from ddtrace.appsec._iast_request_state import _get_iast_context_id
1212

1313

1414
def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, source_origin=None, contextid=None) -> Any:

ddtrace/appsec/_iast/_taint_tracking/aspects.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from typing import Union
1717

1818
from ddtrace.appsec._constants import IAST
19-
from ddtrace.appsec._iast._iast_request_context_base import is_iast_request_enabled
2019
from ddtrace.appsec._iast._logs import iast_propagation_error_log
2120
from ddtrace.appsec._iast._taint_tracking import TagMappingMode
2221
from ddtrace.appsec._iast._taint_tracking import TaintRange
@@ -50,6 +49,7 @@
5049
from ddtrace.appsec._iast._taint_tracking._taint_objects_base import get_tainted_ranges
5150
from ddtrace.appsec._iast._taint_tracking._taint_objects_base import is_pyobject_tainted
5251
from ddtrace.appsec._iast._taint_utils import taint_structure
52+
from ddtrace.appsec._iast_request_state import is_iast_request_enabled
5353

5454

5555
TEXT_TYPES = Union[str, bytes, bytearray]

0 commit comments

Comments
 (0)