Skip to content

Commit 32632a8

Browse files
authored
fix(iast): invalid line number on insecure cookie report (#6887)
Insecure Cookies report should not send line number and empty file ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] 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) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that.
1 parent dde2e0c commit 32632a8

File tree

6 files changed

+38
-20
lines changed

6 files changed

+38
-20
lines changed

ddtrace/appsec/iast/_taint_tracking/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"active_map_addreses_size",
7171
"create_context",
7272
"str_to_origin",
73+
"origin_to_str",
7374
"common_replace",
7475
"as_formatted_evidence",
7576
"parse_params",

ddtrace/appsec/iast/_utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import json
12
import re
23
import string
34
import sys
45
from typing import TYPE_CHECKING
56

7+
import attr
8+
69
from ddtrace.internal.logger import get_logger
710
from ddtrace.settings import _config as config
811

@@ -72,3 +75,17 @@ def _scrub_get_tokens_positions(text, tokens):
7275

7376
token_positions.sort()
7477
return token_positions
78+
79+
80+
def _iast_report_to_str(data):
81+
from ._taint_tracking import OriginType
82+
from ._taint_tracking import origin_to_str
83+
84+
class OriginTypeEncoder(json.JSONEncoder):
85+
def default(self, obj):
86+
if isinstance(obj, OriginType):
87+
# if the obj is uuid, we simply return the value of uuid
88+
return origin_to_str(obj)
89+
return json.JSONEncoder.default(self, obj)
90+
91+
return json.dumps(attr.asdict(data, filter=lambda attr, x: x is not None), cls=OriginTypeEncoder)

ddtrace/appsec/iast/processor.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
from typing import TYPE_CHECKING
32

43
import attr
@@ -7,6 +6,7 @@
76
from ddtrace.appsec._constants import IAST
87
from ddtrace.appsec.iast import oce
98
from ddtrace.appsec.iast._metrics import _set_metric_iast_request_tainted
9+
from ddtrace.appsec.iast._utils import _iast_report_to_str
1010
from ddtrace.appsec.iast._utils import _is_iast_enabled
1111
from ddtrace.appsec.trace_utils import _asm_manual_keep
1212
from ddtrace.constants import ORIGIN_KEY
@@ -56,19 +56,9 @@ def on_span_finish(self, span):
5656
data = core.get_item(IAST.CONTEXT_KEY, span=span)
5757

5858
if data:
59-
from ddtrace.appsec.iast._taint_tracking import OriginType # noqa: F401
60-
from ddtrace.appsec.iast._taint_tracking._native.taint_tracking import origin_to_str # noqa: F401
61-
62-
class OriginTypeEncoder(json.JSONEncoder):
63-
def default(self, obj):
64-
if isinstance(obj, OriginType):
65-
# if the obj is uuid, we simply return the value of uuid
66-
return origin_to_str(obj)
67-
return json.JSONEncoder.default(self, obj)
68-
6959
span.set_tag_str(
7060
IAST.JSON,
71-
json.dumps(attr.asdict(data, filter=lambda attr, x: x is not None), cls=OriginTypeEncoder),
61+
_iast_report_to_str(data),
7262
)
7363
_asm_manual_keep(span)
7464

ddtrace/appsec/iast/reporter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ def __eq__(self, other):
5555

5656
@attr.s(eq=True, hash=True)
5757
class Location(object):
58-
path = attr.ib(type=str) # type: str
59-
line = attr.ib(type=int) # type: int
6058
spanId = attr.ib(type=int, eq=False, hash=False, repr=False) # type: int
59+
path = attr.ib(type=str, default=None) # type: Optional[str]
60+
line = attr.ib(type=int, default=None) # type: Optional[int]
6161

6262

6363
@attr.s(eq=True, hash=True)

ddtrace/appsec/iast/taint_sinks/_base.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ def report(cls, evidence_value="", sources=None):
104104
)
105105
return None
106106

107-
file_name = ""
108-
line_number = 0
107+
file_name = None
108+
line_number = None
109109

110110
skip_location = getattr(cls, "skip_location", False)
111111
if not skip_location:
@@ -119,6 +119,9 @@ def report(cls, evidence_value="", sources=None):
119119
if file_name.startswith(CWD):
120120
file_name = os.path.relpath(file_name, start=CWD)
121121

122+
if not cls.is_not_reported(file_name, line_number):
123+
return
124+
122125
if _is_evidence_value_parts(evidence_value):
123126
evidence = Evidence(valueParts=evidence_value)
124127
# Evidence is a string in weak cipher, weak hash and weak randomness
@@ -128,10 +131,6 @@ def report(cls, evidence_value="", sources=None):
128131
log.debug("Unexpected evidence_value type: %s", type(evidence_value))
129132
evidence = Evidence(value="")
130133

131-
if not cls.is_not_reported(file_name, line_number):
132-
# not not reported = reported
133-
return None
134-
135134
_set_metric_iast_executed_sink(cls.vulnerability_type)
136135

137136
report = core.get_item(IAST.CONTEXT_KEY, span=span)

tests/appsec/iast/taint_sinks/test_insecure_cookie.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import pytest
2+
13
from ddtrace.appsec._constants import IAST
4+
from ddtrace.appsec.iast._utils import _iast_report_to_str
5+
from ddtrace.appsec.iast._utils import _is_python_version_supported as python_supported_by_iast
26
from ddtrace.appsec.iast.constants import VULN_INSECURE_COOKIE
37
from ddtrace.appsec.iast.constants import VULN_NO_HTTPONLY_COOKIE
48
from ddtrace.appsec.iast.constants import VULN_NO_SAMESITE_COOKIE
@@ -14,12 +18,19 @@ def test_insecure_cookies(iast_span_defaults):
1418
assert list(span_report.vulnerabilities)[0].evidence.value == "foo=bar"
1519

1620

21+
@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST")
1722
def test_nohttponly_cookies(iast_span_defaults):
1823
cookies = {"foo": "bar;secure"}
1924
asm_check_cookies(cookies)
2025
span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults)
2126
assert list(span_report.vulnerabilities)[0].type == VULN_NO_HTTPONLY_COOKIE
2227
assert list(span_report.vulnerabilities)[0].evidence.value == "foo=bar;secure"
28+
assert list(span_report.vulnerabilities)[0].location.line is None
29+
assert list(span_report.vulnerabilities)[0].location.path is None
30+
str_report = _iast_report_to_str(span_report)
31+
# Double check to verify we're not sending an empty key
32+
assert '"line"' not in str_report
33+
assert '"path"' not in str_report
2334

2435

2536
def test_nosamesite_cookies_missing(iast_span_defaults):

0 commit comments

Comments
 (0)