From 87a54ace75b126bc54f0c9c8887b16ef2bbcd295 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Thu, 29 Oct 2026 14:08:26 +0100 Subject: [PATCH 1/9] Add Python Lambda SCA reachability fixtures --- docs/understand/weblogs/end-to-end_weblog.md | 2 ++ manifests/python_lambda.yml | 2 +- tests/appsec/test_sca_reachability.py | 6 +++++ .../docker/python_lambda/function/handler.py | 22 +++++++++++++++++++ .../python_lambda/function/requirements.txt | 4 ++-- .../scripts/libraries_and_scenarios_rules.yml | 2 +- 6 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/understand/weblogs/end-to-end_weblog.md b/docs/understand/weblogs/end-to-end_weblog.md index d5cc0cdf893..d258274ed91 100644 --- a/docs/understand/weblogs/end-to-end_weblog.md +++ b/docs/understand/weblogs/end-to-end_weblog.md @@ -889,6 +889,8 @@ first occurrence is reported in the `reached` array. The Java Lambda weblogs also implement both SCA endpoints, using Junrar 7.5.5 kept as a separate JAR. The vulnerable class is preloaded during handler initialization. +The Python Lambda weblogs implement both SCA endpoints with requests 2.31.0. They call `Session.send` from two distinct handlers against a closed loopback port, so no external service is required. + ### GET /log/library This endpoint facilitates logging a message using a logging library. It is primarily designed for testing log injection functionality. Weblog apps must log using JSON format. diff --git a/manifests/python_lambda.yml b/manifests/python_lambda.yml index ef76c50362c..3affdc315bf 100644 --- a/manifests/python_lambda.yml +++ b/manifests/python_lambda.yml @@ -266,7 +266,7 @@ manifest: tests/appsec/test_reports.py::Test_Info: v7.112.0 tests/appsec/test_reports.py::Test_RequestHeaders: v7.112.0 tests/appsec/test_reports.py::Test_StatusCode: v7.112.0 - tests/appsec/test_sca_reachability.py: incomplete_test_app (Lambda SCA dependency fixtures and vulnerable-call endpoints are not implemented) + tests/appsec/test_sca_reachability.py: missing_feature (requires Lambda SCA reachability support in dd-trace-py and datadog-lambda-python) tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v8.113.0 tests/appsec/test_traces.py::Test_AppSecEventSpanTags: v7.112.0 tests/appsec/test_traces.py::Test_AppSecEventSpanTags::test_header_collection: bug (APPSEC-58202) diff --git a/tests/appsec/test_sca_reachability.py b/tests/appsec/test_sca_reachability.py index aef5a54ef42..b2287c63b0e 100644 --- a/tests/appsec/test_sca_reachability.py +++ b/tests/appsec/test_sca_reachability.py @@ -33,6 +33,12 @@ "path": "com.datadoghq.system_tests.springboot.ScaReachability", "symbol": "junrarHit", }, + "python_lambda": { + "cve_id": ["CVE-2024-35195", "GHSA-652x-xj99-gmcc"], + "vulnerable_dep": "requests", + "path": "handler.py", + "symbol": "sca_requests_vulnerable_call", + }, "java_lambda": { "cve_id": ["GHSA-hf5p-q87m-crj7"], "vulnerable_dep": "junrar", diff --git a/utils/build/docker/python_lambda/function/handler.py b/utils/build/docker/python_lambda/function/handler.py index d507f927a20..7227da81760 100644 --- a/utils/build/docker/python_lambda/function/handler.py +++ b/utils/build/docker/python_lambda/function/handler.py @@ -47,6 +47,28 @@ _TRACK_CUSTOM_APPSEC_EVENT_NAME = "system_tests_appsec_event" +@app.get("/sca/vulnerable-call") +def sca_requests_vulnerable_call() -> str: + """Call the vulnerable Session.send target without an external dependency.""" + with requests.Session() as session: + try: + session.send(requests.Request("GET", "http://127.0.0.1:1").prepare(), timeout=1) + except requests.exceptions.ConnectionError: + pass + return "OK" + + +@app.get("/sca/vulnerable-call-alt") +def sca_requests_vulnerable_call_alt() -> str: + """Use a distinct caller for the same target to check first-hit deduplication.""" + with requests.Session() as session: + try: + session.send(requests.Request("GET", "http://127.0.0.1:1").prepare(), timeout=1) + except requests.exceptions.ConnectionError: + pass + return "OK" + + def version_info(): return { "status": "ok", diff --git a/utils/build/docker/python_lambda/function/requirements.txt b/utils/build/docker/python_lambda/function/requirements.txt index b3abb7c5aee..f13f7fc198c 100644 --- a/utils/build/docker/python_lambda/function/requirements.txt +++ b/utils/build/docker/python_lambda/function/requirements.txt @@ -1,3 +1,3 @@ aws-lambda-powertools==3.17.0 -requests==2.32.5 -xmltodict==1.0.2 \ No newline at end of file +requests==2.31.0 +xmltodict==1.0.2 diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index 806149e4c5d..cb73c0b2b40 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -589,7 +589,7 @@ patterns: tests/appsec/test_sca_reachability.py: scenario_groups: null - libraries: [java, java_lambda, python] + libraries: [java, java_lambda, python, python_lambda] ################################# Default rules ################################ # The rules in this section are only applied if no other rule is applicable. From 655d6829b28d1bba294293f866e6737fa0021773 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Thu, 29 Oct 2026 14:32:22 +0100 Subject: [PATCH 2/9] Use a synthetic advisory for Python Lambda SCA tests --- docs/understand/weblogs/end-to-end_weblog.md | 2 -- tests/appsec/sca_python_lambda.json | 10 ++++++++++ tests/appsec/test_sca_reachability.py | 2 +- utils/_context/_scenarios/aws_lambda.py | 9 +++++++++ utils/build/docker/python_lambda/function/handler.py | 2 +- .../docker/python_lambda/function/requirements.txt | 4 ++-- utils/scripts/libraries_and_scenarios_rules.yml | 5 +++++ 7 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tests/appsec/sca_python_lambda.json diff --git a/docs/understand/weblogs/end-to-end_weblog.md b/docs/understand/weblogs/end-to-end_weblog.md index d258274ed91..d5cc0cdf893 100644 --- a/docs/understand/weblogs/end-to-end_weblog.md +++ b/docs/understand/weblogs/end-to-end_weblog.md @@ -889,8 +889,6 @@ first occurrence is reported in the `reached` array. The Java Lambda weblogs also implement both SCA endpoints, using Junrar 7.5.5 kept as a separate JAR. The vulnerable class is preloaded during handler initialization. -The Python Lambda weblogs implement both SCA endpoints with requests 2.31.0. They call `Session.send` from two distinct handlers against a closed loopback port, so no external service is required. - ### GET /log/library This endpoint facilitates logging a message using a logging library. It is primarily designed for testing log injection functionality. Weblog apps must log using JSON format. diff --git a/tests/appsec/sca_python_lambda.json b/tests/appsec/sca_python_lambda.json new file mode 100644 index 00000000000..81ebe582fd5 --- /dev/null +++ b/tests/appsec/sca_python_lambda.json @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "id": "system-tests-python-lambda-sca", + "dependency_name": "requests", + "package_versions": [">=0"], + "targets": ["requests.sessions:Session.send"] + } + ] +} diff --git a/tests/appsec/test_sca_reachability.py b/tests/appsec/test_sca_reachability.py index b2287c63b0e..04c5f29494e 100644 --- a/tests/appsec/test_sca_reachability.py +++ b/tests/appsec/test_sca_reachability.py @@ -34,7 +34,7 @@ "symbol": "junrarHit", }, "python_lambda": { - "cve_id": ["CVE-2024-35195", "GHSA-652x-xj99-gmcc"], + "cve_id": ["system-tests-python-lambda-sca"], "vulnerable_dep": "requests", "path": "handler.py", "symbol": "sca_requests_vulnerable_call", diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 30919185d00..057ec500540 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -220,6 +220,15 @@ def __init__(self) -> None: scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], ) + def configure(self, config: pytest.Config) -> None: + super().configure(config) + if self.lambda_weblog.image.labels.get("system-tests-library") == "python_lambda": + # Inject a synthetic advisory without coupling requests upgrades to a real CVE. + self.lambda_weblog.volumes["./tests/appsec/sca_python_lambda.json"] = { + "bind": "/opt/python/lib/python3.13/site-packages/ddtrace/appsec/sca/_cve_data.json", + "mode": "ro", + } + class AppsecLambdaRasp(_LambdaScenario): def __init__(self) -> None: diff --git a/utils/build/docker/python_lambda/function/handler.py b/utils/build/docker/python_lambda/function/handler.py index 7227da81760..875f4be39e4 100644 --- a/utils/build/docker/python_lambda/function/handler.py +++ b/utils/build/docker/python_lambda/function/handler.py @@ -49,7 +49,7 @@ @app.get("/sca/vulnerable-call") def sca_requests_vulnerable_call() -> str: - """Call the vulnerable Session.send target without an external dependency.""" + """Exercise the synthetic advisory's Session.send target.""" with requests.Session() as session: try: session.send(requests.Request("GET", "http://127.0.0.1:1").prepare(), timeout=1) diff --git a/utils/build/docker/python_lambda/function/requirements.txt b/utils/build/docker/python_lambda/function/requirements.txt index f13f7fc198c..b3abb7c5aee 100644 --- a/utils/build/docker/python_lambda/function/requirements.txt +++ b/utils/build/docker/python_lambda/function/requirements.txt @@ -1,3 +1,3 @@ aws-lambda-powertools==3.17.0 -requests==2.31.0 -xmltodict==1.0.2 +requests==2.32.5 +xmltodict==1.0.2 \ No newline at end of file diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index cb73c0b2b40..3b3c9a11cde 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -591,6 +591,11 @@ patterns: scenario_groups: null libraries: [java, java_lambda, python, python_lambda] + tests/appsec/sca_python_lambda.json: + scenario_groups: null + scenarios: [APPSEC_LAMBDA_SCA] + libraries: python_lambda + ################################# Default rules ################################ # The rules in this section are only applied if no other rule is applicable. # They are considered safe to override in previous rules. They should match a From 0520be985be1a5cfa007421f4fce1023e941a0da Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Thu, 29 Oct 2026 14:44:49 +0100 Subject: [PATCH 3/9] Share SCA advisory fixtures across languages --- tests/appsec/sca_advisories.json | 34 +++++++++++++++++++ tests/appsec/sca_python_lambda.json | 10 ------ tests/appsec/test_sca_reachability.py | 2 +- utils/_context/_scenarios/aws_lambda.py | 12 +++++-- .../scripts/libraries_and_scenarios_rules.yml | 2 +- 5 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 tests/appsec/sca_advisories.json delete mode 100644 tests/appsec/sca_python_lambda.json diff --git a/tests/appsec/sca_advisories.json b/tests/appsec/sca_advisories.json new file mode 100644 index 00000000000..3c9d0fdc82e --- /dev/null +++ b/tests/appsec/sca_advisories.json @@ -0,0 +1,34 @@ +{ + "python": { + "targets": [ + { + "id": "system-tests-sca", + "dependency_name": "requests", + "package_versions": [ + ">=0" + ], + "targets": [ + "requests.sessions:Session.send" + ] + } + ] + }, + "java": { + "version": 1, + "entries": [ + { + "vuln_id": "system-tests-sca", + "artifact": "com.github.junrar:junrar", + "version_ranges": [ + ">=0" + ], + "symbols": [ + { + "class": "com/github/junrar/LocalFolderExtractor", + "method": "createDirectory" + } + ] + } + ] + } +} diff --git a/tests/appsec/sca_python_lambda.json b/tests/appsec/sca_python_lambda.json deleted file mode 100644 index 81ebe582fd5..00000000000 --- a/tests/appsec/sca_python_lambda.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "targets": [ - { - "id": "system-tests-python-lambda-sca", - "dependency_name": "requests", - "package_versions": [">=0"], - "targets": ["requests.sessions:Session.send"] - } - ] -} diff --git a/tests/appsec/test_sca_reachability.py b/tests/appsec/test_sca_reachability.py index 04c5f29494e..e494e3eddfc 100644 --- a/tests/appsec/test_sca_reachability.py +++ b/tests/appsec/test_sca_reachability.py @@ -34,7 +34,7 @@ "symbol": "junrarHit", }, "python_lambda": { - "cve_id": ["system-tests-python-lambda-sca"], + "cve_id": ["system-tests-sca"], "vulnerable_dep": "requests", "path": "handler.py", "symbol": "sca_requests_vulnerable_call", diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 057ec500540..47d0f2d94b6 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -1,3 +1,6 @@ +import json +from pathlib import Path + import pytest from utils import interfaces from utils.interfaces._core import ProxyBasedInterfaceValidator @@ -222,9 +225,12 @@ def __init__(self) -> None: def configure(self, config: pytest.Config) -> None: super().configure(config) - if self.lambda_weblog.image.labels.get("system-tests-library") == "python_lambda": - # Inject a synthetic advisory without coupling requests upgrades to a real CVE. - self.lambda_weblog.volumes["./tests/appsec/sca_python_lambda.json"] = { + if not self.replay and self.lambda_weblog.image.labels.get("system-tests-library") == "python_lambda": + # Each tracer receives its native payload from the shared catalog. + catalog = json.loads(Path("tests/appsec/sca_advisories.json").read_text(encoding="utf-8")) + payload_path = Path(self.host_log_folder) / "sca_advisories.json" + payload_path.write_text(json.dumps(catalog["python"]), encoding="utf-8") + self.lambda_weblog.volumes[str(payload_path)] = { "bind": "/opt/python/lib/python3.13/site-packages/ddtrace/appsec/sca/_cve_data.json", "mode": "ro", } diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index 3b3c9a11cde..31f60a3c861 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -591,7 +591,7 @@ patterns: scenario_groups: null libraries: [java, java_lambda, python, python_lambda] - tests/appsec/sca_python_lambda.json: + tests/appsec/sca_advisories.json: scenario_groups: null scenarios: [APPSEC_LAMBDA_SCA] libraries: python_lambda From 67cd20396b6580335778852a5321eadf783e2e3d Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Thu, 29 Oct 2026 15:24:50 +0100 Subject: [PATCH 4/9] test: inject Lambda SCA advisories through scenario arguments --- tests/appsec/sca_advisories.json | 56 +++++++++++-------------- utils/_context/_scenarios/aws_lambda.py | 22 +++------- 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/tests/appsec/sca_advisories.json b/tests/appsec/sca_advisories.json index 3c9d0fdc82e..186d812b022 100644 --- a/tests/appsec/sca_advisories.json +++ b/tests/appsec/sca_advisories.json @@ -1,34 +1,26 @@ { - "python": { - "targets": [ - { - "id": "system-tests-sca", - "dependency_name": "requests", - "package_versions": [ - ">=0" - ], - "targets": [ - "requests.sessions:Session.send" - ] - } - ] - }, - "java": { - "version": 1, - "entries": [ - { - "vuln_id": "system-tests-sca", - "artifact": "com.github.junrar:junrar", - "version_ranges": [ - ">=0" - ], - "symbols": [ - { - "class": "com/github/junrar/LocalFolderExtractor", - "method": "createDirectory" - } - ] - } - ] - } + "targets": [ + { + "id": "system-tests-sca", + "lang": "python", + "dependency_name": "requests", + "package_versions": [ + ">=0" + ], + "targets": [ + "requests.sessions:Session.send" + ] + }, + { + "id": "system-tests-sca", + "lang": "maven", + "dependency_name": "com.github.junrar:junrar", + "package_versions": [ + ">=0" + ], + "targets": [ + "com.github.junrar:LocalFolderExtractor.createDirectory" + ] + } + ] } diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 47d0f2d94b6..3f784bdf2cc 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -1,6 +1,3 @@ -import json -from pathlib import Path - import pytest from utils import interfaces from utils.interfaces._core import ProxyBasedInterfaceValidator @@ -218,23 +215,16 @@ class AppsecLambdaSca(_LambdaScenario): def __init__(self) -> None: super().__init__( "APPSEC_LAMBDA_SCA", - weblog_env={"DD_APPSEC_ENABLED": "false", "DD_APPSEC_SCA_ENABLED": "true"}, + weblog_env={ + "DD_APPSEC_ENABLED": "false", + "DD_APPSEC_SCA_ENABLED": "true", + "_DD_APPSEC_SCA_DATA_FILE": "/sca_advisories.json", + }, + weblog_volumes={"./tests/appsec/sca_advisories.json": {"bind": "/sca_advisories.json", "mode": "ro"}}, doc="SCA dependency metadata and runtime reachability in Lambda with AppSec disabled", scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], ) - def configure(self, config: pytest.Config) -> None: - super().configure(config) - if not self.replay and self.lambda_weblog.image.labels.get("system-tests-library") == "python_lambda": - # Each tracer receives its native payload from the shared catalog. - catalog = json.loads(Path("tests/appsec/sca_advisories.json").read_text(encoding="utf-8")) - payload_path = Path(self.host_log_folder) / "sca_advisories.json" - payload_path.write_text(json.dumps(catalog["python"]), encoding="utf-8") - self.lambda_weblog.volumes[str(payload_path)] = { - "bind": "/opt/python/lib/python3.13/site-packages/ddtrace/appsec/sca/_cve_data.json", - "mode": "ro", - } - class AppsecLambdaRasp(_LambdaScenario): def __init__(self) -> None: From eff08382fe571aa6488521a44d71fb29dd3775ec Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Thu, 29 Oct 2026 15:30:39 +0100 Subject: [PATCH 5/9] test: inject SCA fixtures through Lambda test startup --- utils/_context/_scenarios/aws_lambda.py | 1 - utils/build/docker/python_lambda/function/app.sh | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 3f784bdf2cc..4a0aad1a59f 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -218,7 +218,6 @@ def __init__(self) -> None: weblog_env={ "DD_APPSEC_ENABLED": "false", "DD_APPSEC_SCA_ENABLED": "true", - "_DD_APPSEC_SCA_DATA_FILE": "/sca_advisories.json", }, weblog_volumes={"./tests/appsec/sca_advisories.json": {"bind": "/sca_advisories.json", "mode": "ro"}}, doc="SCA dependency metadata and runtime reachability in Lambda with AppSec disabled", diff --git a/utils/build/docker/python_lambda/function/app.sh b/utils/build/docker/python_lambda/function/app.sh index eb958bd98f3..96c28071310 100644 --- a/utils/build/docker/python_lambda/function/app.sh +++ b/utils/build/docker/python_lambda/function/app.sh @@ -4,6 +4,22 @@ set -eu export DD_LAMBDA_HANDLER=handler.lambda_handler +# Only the SCA scenario mounts this catalog. Install its Python entries before +# starting the Lambda wrapper, which initializes reachability instrumentation. +if [ -f /sca_advisories.json ]; then + python3 - <<'PY' +import json +from pathlib import Path + +catalog = json.loads(Path("/sca_advisories.json").read_text()) +targets = [entry for entry in catalog["targets"] if entry["lang"] == "python"] +data_paths = list(Path("/opt/python").glob("lib/python*/site-packages/ddtrace/appsec/sca/_cve_data.json")) +if len(data_paths) != 1: + raise RuntimeError(f"Expected one bundled SCA catalog, found {data_paths}") +data_paths[0].write_text(json.dumps({"targets": targets})) +PY +fi + socat TCP-LISTEN:8127,reuseaddr,fork,bind=0.0.0.0 TCP:127.0.0.1:8126 & exec /lambda-entrypoint.sh datadog_lambda.handler.handler From b4d33adf8d3734e96e686b53758fa1db5738fe01 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Tue, 3 Nov 2026 10:10:27 +0100 Subject: [PATCH 6/9] test: split Lambda SCA advisories by language --- .../java/advisories.json} | 11 ----------- tests/appsec/sca/python/advisories.json | 15 +++++++++++++++ utils/_context/_scenarios/aws_lambda.py | 11 ++++++++++- utils/build/docker/python_lambda/function/app.sh | 8 +++----- utils/scripts/libraries_and_scenarios_rules.yml | 7 ++++++- 5 files changed, 34 insertions(+), 18 deletions(-) rename tests/appsec/{sca_advisories.json => sca/java/advisories.json} (55%) create mode 100644 tests/appsec/sca/python/advisories.json diff --git a/tests/appsec/sca_advisories.json b/tests/appsec/sca/java/advisories.json similarity index 55% rename from tests/appsec/sca_advisories.json rename to tests/appsec/sca/java/advisories.json index 186d812b022..3757b497945 100644 --- a/tests/appsec/sca_advisories.json +++ b/tests/appsec/sca/java/advisories.json @@ -1,16 +1,5 @@ { "targets": [ - { - "id": "system-tests-sca", - "lang": "python", - "dependency_name": "requests", - "package_versions": [ - ">=0" - ], - "targets": [ - "requests.sessions:Session.send" - ] - }, { "id": "system-tests-sca", "lang": "maven", diff --git a/tests/appsec/sca/python/advisories.json b/tests/appsec/sca/python/advisories.json new file mode 100644 index 00000000000..7893eeb1cab --- /dev/null +++ b/tests/appsec/sca/python/advisories.json @@ -0,0 +1,15 @@ +{ + "targets": [ + { + "id": "system-tests-sca", + "lang": "python", + "dependency_name": "requests", + "package_versions": [ + ">=0" + ], + "targets": [ + "requests.sessions:Session.send" + ] + } + ] +} diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 4a0aad1a59f..319787728f7 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -219,11 +219,20 @@ def __init__(self) -> None: "DD_APPSEC_ENABLED": "false", "DD_APPSEC_SCA_ENABLED": "true", }, - weblog_volumes={"./tests/appsec/sca_advisories.json": {"bind": "/sca_advisories.json", "mode": "ro"}}, doc="SCA dependency metadata and runtime reachability in Lambda with AppSec disabled", scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], ) + def configure(self, config: pytest.Config) -> None: + super().configure(config) + + language = self.lambda_weblog.library_name.removesuffix("_lambda") + if language in ("python", "java"): + self.lambda_weblog.volumes[f"./tests/appsec/sca/{language}/advisories.json"] = { + "bind": "/sca_advisories.json", + "mode": "ro", + } + class AppsecLambdaRasp(_LambdaScenario): def __init__(self) -> None: diff --git a/utils/build/docker/python_lambda/function/app.sh b/utils/build/docker/python_lambda/function/app.sh index 96c28071310..1efe2a62901 100644 --- a/utils/build/docker/python_lambda/function/app.sh +++ b/utils/build/docker/python_lambda/function/app.sh @@ -4,19 +4,17 @@ set -eu export DD_LAMBDA_HANDLER=handler.lambda_handler -# Only the SCA scenario mounts this catalog. Install its Python entries before +# Only the SCA scenario mounts this language's catalog. Install it before # starting the Lambda wrapper, which initializes reachability instrumentation. if [ -f /sca_advisories.json ]; then python3 - <<'PY' -import json from pathlib import Path +from shutil import copyfile -catalog = json.loads(Path("/sca_advisories.json").read_text()) -targets = [entry for entry in catalog["targets"] if entry["lang"] == "python"] data_paths = list(Path("/opt/python").glob("lib/python*/site-packages/ddtrace/appsec/sca/_cve_data.json")) if len(data_paths) != 1: raise RuntimeError(f"Expected one bundled SCA catalog, found {data_paths}") -data_paths[0].write_text(json.dumps({"targets": targets})) +copyfile("/sca_advisories.json", data_paths[0]) PY fi diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index 31f60a3c861..eee675e0f73 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -591,11 +591,16 @@ patterns: scenario_groups: null libraries: [java, java_lambda, python, python_lambda] - tests/appsec/sca_advisories.json: + tests/appsec/sca/python/advisories.json: scenario_groups: null scenarios: [APPSEC_LAMBDA_SCA] libraries: python_lambda + tests/appsec/sca/java/advisories.json: + scenario_groups: null + scenarios: [APPSEC_LAMBDA_SCA] + libraries: java_lambda + ################################# Default rules ################################ # The rules in this section are only applied if no other rule is applicable. # They are considered safe to override in previous rules. They should match a From c8ae46f97bb893ac9a7558dbd341dad9d1d7ec2e Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Tue, 3 Nov 2026 10:15:18 +0100 Subject: [PATCH 7/9] test: mount native Python SCA catalog directly --- tests/appsec/sca/python/advisories.json | 7 ++++++- utils/_context/_scenarios/aws_lambda.py | 8 ++++++-- utils/build/docker/python_lambda/function/app.sh | 14 -------------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/appsec/sca/python/advisories.json b/tests/appsec/sca/python/advisories.json index 7893eeb1cab..4835edf62d2 100644 --- a/tests/appsec/sca/python/advisories.json +++ b/tests/appsec/sca/python/advisories.json @@ -9,7 +9,12 @@ ], "targets": [ "requests.sessions:Session.send" - ] + ], + "vulnerability": { + "id": "system-tests-sca", + "severity": "LOW", + "description": "Synthetic requests target for system-tests SCA reachability coverage." + } } ] } diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 319787728f7..150fbc24d0e 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -227,9 +227,13 @@ def configure(self, config: pytest.Config) -> None: super().configure(config) language = self.lambda_weblog.library_name.removesuffix("_lambda") - if language in ("python", "java"): + catalog_paths = { + "python": "/opt/python/lib/python3.13/site-packages/ddtrace/appsec/sca/_cve_data.json", + "java": "/sca_advisories.json", + } + if language in catalog_paths: self.lambda_weblog.volumes[f"./tests/appsec/sca/{language}/advisories.json"] = { - "bind": "/sca_advisories.json", + "bind": catalog_paths[language], "mode": "ro", } diff --git a/utils/build/docker/python_lambda/function/app.sh b/utils/build/docker/python_lambda/function/app.sh index 1efe2a62901..eb958bd98f3 100644 --- a/utils/build/docker/python_lambda/function/app.sh +++ b/utils/build/docker/python_lambda/function/app.sh @@ -4,20 +4,6 @@ set -eu export DD_LAMBDA_HANDLER=handler.lambda_handler -# Only the SCA scenario mounts this language's catalog. Install it before -# starting the Lambda wrapper, which initializes reachability instrumentation. -if [ -f /sca_advisories.json ]; then - python3 - <<'PY' -from pathlib import Path -from shutil import copyfile - -data_paths = list(Path("/opt/python").glob("lib/python*/site-packages/ddtrace/appsec/sca/_cve_data.json")) -if len(data_paths) != 1: - raise RuntimeError(f"Expected one bundled SCA catalog, found {data_paths}") -copyfile("/sca_advisories.json", data_paths[0]) -PY -fi - socat TCP-LISTEN:8127,reuseaddr,fork,bind=0.0.0.0 TCP:127.0.0.1:8126 & exec /lambda-entrypoint.sh datadog_lambda.handler.handler From b6ecd07db53cb32a7a513fea57f7dd8c129c075a Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Tue, 3 Nov 2026 10:27:36 +0100 Subject: [PATCH 8/9] test: share synthetic SCA catalogs across regular and Lambda weblogs --- tests/appsec/sca/java/advisories.json | 17 ++++++----- tests/appsec/test_sca_reachability.py | 9 +++--- utils/_context/_scenarios/_core/sca.py | 29 +++++++++++++++++++ utils/_context/_scenarios/appsec.py | 5 ++++ utils/_context/_scenarios/aws_lambda.py | 13 ++------- .../springboot/ScaReachability.java | 2 +- .../com/datadoghq/lambda/ScaReachability.java | 2 +- utils/build/docker/python/flask/app.py | 4 +-- utils/build/docker/python/flask/app.sh | 5 ---- utils/build/docker/python/flask/uwsgi_app.sh | 5 ---- .../scripts/libraries_and_scenarios_rules.yml | 16 ++++++---- 11 files changed, 64 insertions(+), 43 deletions(-) create mode 100644 utils/_context/_scenarios/_core/sca.py diff --git a/tests/appsec/sca/java/advisories.json b/tests/appsec/sca/java/advisories.json index 3757b497945..9ef569b28f6 100644 --- a/tests/appsec/sca/java/advisories.json +++ b/tests/appsec/sca/java/advisories.json @@ -1,14 +1,17 @@ { - "targets": [ + "version": 1, + "entries": [ { - "id": "system-tests-sca", - "lang": "maven", - "dependency_name": "com.github.junrar:junrar", - "package_versions": [ + "vuln_id": "system-tests-sca", + "artifact": "com.github.junrar:junrar", + "version_ranges": [ ">=0" ], - "targets": [ - "com.github.junrar:LocalFolderExtractor.createDirectory" + "symbols": [ + { + "class": "com/github/junrar/LocalFolderExtractor", + "method": "createDirectory" + } ] } ] diff --git a/tests/appsec/test_sca_reachability.py b/tests/appsec/test_sca_reachability.py index e494e3eddfc..a5e5ae1b5da 100644 --- a/tests/appsec/test_sca_reachability.py +++ b/tests/appsec/test_sca_reachability.py @@ -18,17 +18,16 @@ # Only populate entries once the language's tracer supports SCA reachability # and the expected values are confirmed. Missing languages gracefully # degrade: structural assertions still run, but value comparisons are skipped. -# `cve_id` is a list to support retrocompatibility across tracer versions that -# may emit different identifiers (e.g. CVE id vs GHSA id) for the same advisory. +# Regular and Lambda scenarios use the same synthetic advisory for each language. _LANG_CONFIG: dict[str, dict[str, Any]] = { "python": { - "cve_id": ["CVE-2024-35195", "GHSA-652x-xj99-gmcc"], + "cve_id": ["system-tests-sca"], "vulnerable_dep": "requests", "path": "app.py", "symbol": "sca_requests_vulnerable_call", }, "java": { - "cve_id": ["GHSA-hf5p-q87m-crj7"], + "cve_id": ["system-tests-sca"], "vulnerable_dep": "junrar", "path": "com.datadoghq.system_tests.springboot.ScaReachability", "symbol": "junrarHit", @@ -40,7 +39,7 @@ "symbol": "sca_requests_vulnerable_call", }, "java_lambda": { - "cve_id": ["GHSA-hf5p-q87m-crj7"], + "cve_id": ["system-tests-sca"], "vulnerable_dep": "junrar", "path": "com.datadoghq.lambda.ScaReachability", "symbol": "junrarHit", diff --git a/utils/_context/_scenarios/_core/sca.py b/utils/_context/_scenarios/_core/sca.py new file mode 100644 index 00000000000..98227b46c16 --- /dev/null +++ b/utils/_context/_scenarios/_core/sca.py @@ -0,0 +1,29 @@ +from utils._context.containers import TestedContainer + + +def configure_sca_advisories(container: TestedContainer) -> None: + library = container.image.labels["system-tests-library"] + language = library.removesuffix("_lambda") + + if language == "python": + if library == "python_lambda": + package_root = "/opt/python/lib/python3.13/site-packages" + elif any(volume["bind"] == "/volumes/dd-trace-py" for volume in container.volumes.values()): + package_root = "/volumes/dd-trace-py" + else: + assert container.image.env is not None + python_version = ".".join(container.image.env["PYTHON_VERSION"].split(".")[:2]) + package_root = f"/usr/local/lib/python{python_version}/site-packages" + catalog_path = f"{package_root}/ddtrace/appsec/sca/_cve_data.json" + elif language == "java": + catalog_path = "/sca/sca_cves.json" + # The agent checks bootstrap resources before its bundled classpath resources. + java_options = ( + container.environment.get("JAVA_TOOL_OPTIONS", (container.image.env or {}).get("JAVA_TOOL_OPTIONS", "")) + or "" + ) + container.environment["JAVA_TOOL_OPTIONS"] = f"{java_options} -Xbootclasspath/a:/sca".strip() + else: + return + + container.volumes[f"./tests/appsec/sca/{language}/advisories.json"] = {"bind": catalog_path, "mode": "ro"} diff --git a/utils/_context/_scenarios/appsec.py b/utils/_context/_scenarios/appsec.py index 65f869deea3..07cb79c581e 100644 --- a/utils/_context/_scenarios/appsec.py +++ b/utils/_context/_scenarios/appsec.py @@ -1,6 +1,7 @@ import pytest from ._core import DdTraceEndToEndScenario, EndToEndScenario, scenario_groups +from ._core.sca import configure_sca_advisories from utils._context.constants import WeblogCategory from utils._context.containers import PostgresContainer @@ -152,6 +153,10 @@ def __init__(self) -> None: scenario_groups=[scenario_groups.appsec], ) + def configure(self, config: pytest.Config) -> None: + super().configure(config) + configure_sca_advisories(self.weblog_infra.library_container) + class IastDeduplication(DdTraceEndToEndScenario): def __init__(self) -> None: diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 150fbc24d0e..a1846cd1f50 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -2,6 +2,7 @@ from utils import interfaces from utils.interfaces._core import ProxyBasedInterfaceValidator from ._core import DockerScenario, scenario_groups, scenario_groups as all_scenario_groups, ScenarioGroup +from ._core.sca import configure_sca_advisories from utils._context.constants import WeblogCategory from utils._context.containers import LambdaProxyContainer, LambdaWeblogContainer, InternalServerContainer from utils._logger import logger @@ -225,17 +226,7 @@ def __init__(self) -> None: def configure(self, config: pytest.Config) -> None: super().configure(config) - - language = self.lambda_weblog.library_name.removesuffix("_lambda") - catalog_paths = { - "python": "/opt/python/lib/python3.13/site-packages/ddtrace/appsec/sca/_cve_data.json", - "java": "/sca_advisories.json", - } - if language in catalog_paths: - self.lambda_weblog.volumes[f"./tests/appsec/sca/{language}/advisories.json"] = { - "bind": catalog_paths[language], - "mode": "ro", - } + configure_sca_advisories(self.lambda_weblog) class AppsecLambdaRasp(_LambdaScenario): diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/ScaReachability.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/ScaReachability.java index 95c3bdc1f4a..ebbf485dad1 100644 --- a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/ScaReachability.java +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/ScaReachability.java @@ -15,7 +15,7 @@ public class ScaReachability { @PostConstruct private void preloadVulnerableClass() { // Force LocalFolderExtractor class load during Spring startup so the SCA transformer can - // register GHSA-hf5p-q87m-crj7 with reached=[] before any HTTP request triggers junrarHit(). + // register system-tests-sca with reached=[] before any HTTP request triggers junrarHit(). // Without this, the class loads lazily on the first request, so registerCve() and recordHit() // fire in the same request with no heartbeat window between them. try { diff --git a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/ScaReachability.java b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/ScaReachability.java index dbc63c55f6b..6740a6c03f5 100644 --- a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/ScaReachability.java +++ b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/ScaReachability.java @@ -9,7 +9,7 @@ public class ScaReachability { public static void preloadVulnerableClass() { // Force LocalFolderExtractor class load during Lambda initialization so the SCA transformer can - // register GHSA-hf5p-q87m-crj7 with reached=[] before any HTTP request triggers junrarHit(). + // register system-tests-sca with reached=[] before any HTTP request triggers junrarHit(). // Without this, the class loads lazily on the first request, so registerCve() and recordHit() // fire in the same request with no heartbeat window between them. try { diff --git a/utils/build/docker/python/flask/app.py b/utils/build/docker/python/flask/app.py index 869250381f4..b0c8d8cf54f 100644 --- a/utils/build/docker/python/flask/app.py +++ b/utils/build/docker/python/flask/app.py @@ -2386,7 +2386,7 @@ async def llm(): @app.route("/sca/vulnerable-call") def sca_requests_vulnerable_call(): - """Trigger a call to requests.sessions.Session.send (CVE-2024-35195 target).""" + """Trigger the synthetic advisory's requests.sessions.Session.send target.""" session = requests.Session() try: session.get("http://localhost:1") @@ -2397,7 +2397,7 @@ def sca_requests_vulnerable_call(): @app.route("/sca/vulnerable-call-alt") def sca_requests_vulnerable_call_alt(): - """Alternate call site for same CVE-2024-35195 target (first-hit-wins test).""" + """Alternate call site for the same synthetic advisory (first-hit-wins test).""" session = requests.Session() try: session.post("http://localhost:1", data="x") diff --git a/utils/build/docker/python/flask/app.sh b/utils/build/docker/python/flask/app.sh index a636fdb0a57..9d23abdb9bc 100755 --- a/utils/build/docker/python/flask/app.sh +++ b/utils/build/docker/python/flask/app.sh @@ -1,10 +1,5 @@ #!/bin/bash -if [[ ${DD_APPSEC_SCA_ENABLED:-} = "1" || ${DD_APPSEC_SCA_ENABLED:-} = "true" ]]; then - # We need a requirement with a CVE to test SCA features - python -m pip install requests==2.31.0 -fi - echo "--- PIP FREEZE ---" python -m pip freeze echo "------------------" diff --git a/utils/build/docker/python/flask/uwsgi_app.sh b/utils/build/docker/python/flask/uwsgi_app.sh index e10e7b505a9..1a148a3794c 100755 --- a/utils/build/docker/python/flask/uwsgi_app.sh +++ b/utils/build/docker/python/flask/uwsgi_app.sh @@ -1,10 +1,5 @@ #!/bin/bash -if [[ ${DD_APPSEC_SCA_ENABLED:-} = "1" || ${DD_APPSEC_SCA_ENABLED:-} = "true" ]]; then - # We need a requirement with a CVE to test SCA features - python -m pip install requests==2.31.0 -fi - echo "--- PIP FREEZE ---" python -m pip freeze echo "------------------" diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index eee675e0f73..6a7b31f06c7 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -228,6 +228,10 @@ patterns: scenario_groups: docker_fixtures utils/_context/_scenarios/_core/endtoend.py: scenario_groups: end_to_end + utils/_context/_scenarios/_core/sca.py: + scenario_groups: null + scenarios: [APPSEC_LAMBDA_SCA, RUNTIME_SCA_REACHABILITY] + libraries: [java, java_lambda, python, python_lambda] utils/_context/_scenarios/_core/*: # base classes shared by many scenarios: runs everything utils/_context/_scenarios/*/*: # any other subfolder: not a definition file, runs everything utils/_context/_scenarios/open_telemetry.py: # its scenarios only run with OTel libraries @@ -591,15 +595,15 @@ patterns: scenario_groups: null libraries: [java, java_lambda, python, python_lambda] - tests/appsec/sca/python/advisories.json: + tests/appsec/sca/java/advisories.json: scenario_groups: null - scenarios: [APPSEC_LAMBDA_SCA] - libraries: python_lambda + scenarios: [APPSEC_LAMBDA_SCA, RUNTIME_SCA_REACHABILITY] + libraries: [java, java_lambda] - tests/appsec/sca/java/advisories.json: + tests/appsec/sca/python/advisories.json: scenario_groups: null - scenarios: [APPSEC_LAMBDA_SCA] - libraries: java_lambda + scenarios: [APPSEC_LAMBDA_SCA, RUNTIME_SCA_REACHABILITY] + libraries: [python, python_lambda] ################################# Default rules ################################ # The rules in this section are only applied if no other rule is applicable. From b6b4e693edcbe3ae8ba3b75f5dce228589df3051 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Tue, 3 Nov 2026 10:40:21 +0100 Subject: [PATCH 9/9] refactor: match SCA advisory setup on library --- utils/_context/_scenarios/_core/sca.py | 38 ++++++++++++-------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/utils/_context/_scenarios/_core/sca.py b/utils/_context/_scenarios/_core/sca.py index 98227b46c16..a5ecf8aaccf 100644 --- a/utils/_context/_scenarios/_core/sca.py +++ b/utils/_context/_scenarios/_core/sca.py @@ -3,27 +3,25 @@ def configure_sca_advisories(container: TestedContainer) -> None: library = container.image.labels["system-tests-library"] - language = library.removesuffix("_lambda") - - if language == "python": - if library == "python_lambda": - package_root = "/opt/python/lib/python3.13/site-packages" - elif any(volume["bind"] == "/volumes/dd-trace-py" for volume in container.volumes.values()): - package_root = "/volumes/dd-trace-py" - else: + match library: + case "python_lambda": + catalog_path = "/opt/python/lib/python3.13/site-packages/ddtrace/appsec/sca/_cve_data.json" + case "python" if any(volume["bind"] == "/volumes/dd-trace-py" for volume in container.volumes.values()): + catalog_path = "/volumes/dd-trace-py/ddtrace/appsec/sca/_cve_data.json" + case "python": assert container.image.env is not None python_version = ".".join(container.image.env["PYTHON_VERSION"].split(".")[:2]) - package_root = f"/usr/local/lib/python{python_version}/site-packages" - catalog_path = f"{package_root}/ddtrace/appsec/sca/_cve_data.json" - elif language == "java": - catalog_path = "/sca/sca_cves.json" - # The agent checks bootstrap resources before its bundled classpath resources. - java_options = ( - container.environment.get("JAVA_TOOL_OPTIONS", (container.image.env or {}).get("JAVA_TOOL_OPTIONS", "")) - or "" - ) - container.environment["JAVA_TOOL_OPTIONS"] = f"{java_options} -Xbootclasspath/a:/sca".strip() - else: - return + catalog_path = f"/usr/local/lib/python{python_version}/site-packages/ddtrace/appsec/sca/_cve_data.json" + case "java" | "java_lambda": + catalog_path = "/sca/sca_cves.json" + # The agent checks bootstrap resources before its bundled classpath resources. + java_options = ( + container.environment.get("JAVA_TOOL_OPTIONS", (container.image.env or {}).get("JAVA_TOOL_OPTIONS", "")) + or "" + ) + container.environment["JAVA_TOOL_OPTIONS"] = f"{java_options} -Xbootclasspath/a:/sca".strip() + case _: + return + language = library.removesuffix("_lambda") container.volumes[f"./tests/appsec/sca/{language}/advisories.json"] = {"bind": catalog_path, "mode": "ro"}