From 44c29a8b246b222aefd74b2366d7e825f02cfecc Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Wed, 21 Oct 2026 13:02:54 +0200 Subject: [PATCH 01/10] Activate Java Lambda AppSec and API Security system tests --- manifests/java_lambda.yml | 122 ++++++++++++++---- .../java/com/datadoghq/lambda/Handler.java | 21 ++- 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/manifests/java_lambda.yml b/manifests/java_lambda.yml index 83478d4eba8..2e76c0619ad 100644 --- a/manifests/java_lambda.yml +++ b/manifests/java_lambda.yml @@ -1,14 +1,76 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/DataDog/system-tests/refs/heads/main/utils/manifest/schema.json --- manifest: - tests/appsec/api_security/test_schemas.py: missing_feature + tests/appsec/api_security/test_schemas.py::Test_Scanners: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Cookies: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_FormUrlEncoded_Body: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Headers: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Json_Body: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Path_Parameters: + - weblog_declaration: + "*": v1.67.0 + java-alb: irrelevant (application-load-balancer event type does not support path params) + java-alb-multi: irrelevant (application-load-balancer event type does not support path params) + java-function-url: irrelevant (function-url event type does not support path params) + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Query_Parameters: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Response_Body: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Response_Headers: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Response_on_Block: missing_feature tests/appsec/rasp/test_api10.py: missing_feature - tests/appsec/rasp/test_cmdi.py: missing_feature - tests/appsec/rasp/test_lfi.py: missing_feature - tests/appsec/rasp/test_shi.py: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_BodyJson: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_BodyUrlEncoded: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_BodyXml: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Capability: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Mandatory_SpanTags::test_cmdi_span_tags: v1.67.0 + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Optional_SpanTags::test_cmdi_span_tags: v1.67.0 + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Rules_Version: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_StackTrace: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Telemetry: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Telemetry_V2: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Telemetry_Variant_Tag: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_UrlQuery: missing_feature + tests/appsec/rasp/test_cmdi.py::Test_Cmdi_Waf_Version: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_BodyJson: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_BodyUrlEncoded: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_BodyXml: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_Capability: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_Mandatory_SpanTags::test_lfi_span_tags: v1.67.0 + tests/appsec/rasp/test_lfi.py::Test_Lfi_Optional_SpanTags::test_lfi_span_tags: v1.67.0 + tests/appsec/rasp/test_lfi.py::Test_Lfi_RC_CustomAction: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_Rules_Version: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_StackTrace::test_lfi_stack_trace: v1.67.0 + tests/appsec/rasp/test_lfi.py::Test_Lfi_Telemetry::test_lfi_telemetry: v1.67.0 + tests/appsec/rasp/test_lfi.py::Test_Lfi_Telemetry_Multiple_Exploits: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_Telemetry_V2: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_UrlQuery: missing_feature + tests/appsec/rasp/test_lfi.py::Test_Lfi_Waf_Version: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_BodyJson: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_BodyUrlEncoded: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_BodyXml: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_Capability: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_Mandatory_SpanTags::test_shi_span_tags: v1.67.0 + tests/appsec/rasp/test_shi.py::Test_Shi_Optional_SpanTags::test_shi_span_tags: v1.67.0 + tests/appsec/rasp/test_shi.py::Test_Shi_Rules_Version: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_StackTrace::test_shi_stack_trace: v1.67.0 + tests/appsec/rasp/test_shi.py::Test_Shi_Telemetry::test_shi_telemetry: v1.67.0 + tests/appsec/rasp/test_shi.py::Test_Shi_Telemetry_V2: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_Telemetry_Variant_Tag::test_shi_telemetry: v1.67.0 + tests/appsec/rasp/test_shi.py::Test_Shi_UrlQuery: missing_feature + tests/appsec/rasp/test_shi.py::Test_Shi_Waf_Version: missing_feature tests/appsec/rasp/test_sqli.py: missing_feature - tests/appsec/rasp/test_ssrf.py: missing_feature - tests/appsec/test_alpha.py::Test_Basic: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_BodyJson: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_BodyUrlEncoded: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_BodyXml: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Capability: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Mandatory_SpanTags::test_ssrf_span_tags: v1.67.0 + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Optional_SpanTags::test_ssrf_span_tags: v1.67.0 + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Rules_Version: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_StackTrace::test_ssrf_stack_trace: v1.67.0 + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Telemetry::test_ssrf_telemetry: v1.67.0 + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Telemetry_V2: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_UrlQuery: missing_feature + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Waf_Version: missing_feature + tests/appsec/test_alpha.py::Test_Basic: v1.67.0 tests/appsec/test_blocking_addresses.py::Test_Blocking_client_ip: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_client_ip_with_K8_private_ip: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_client_ip_with_forwarded: missing_feature @@ -25,39 +87,45 @@ manifest: java-alb: irrelevant (application-load-balancer event type does not support path params) java-alb-multi: irrelevant (application-load-balancer event type does not support path params) java-function-url: irrelevant (function-url event type does not support path params) - tests/appsec/test_blocking_addresses.py::Test_Blocking_request_query: missing_feature + tests/appsec/test_blocking_addresses.py::Test_Blocking_request_query::test_blocking: missing_feature + tests/appsec/test_blocking_addresses.py::Test_Blocking_request_query::test_blocking_before: missing_feature + tests/appsec/test_blocking_addresses.py::Test_Blocking_request_query::test_blocking_case_sensitive: missing_feature + tests/appsec/test_blocking_addresses.py::Test_Blocking_request_query::test_non_blocking: missing_feature + tests/appsec/test_blocking_addresses.py::Test_Blocking_request_query::test_non_blocking_case_sensitive: v1.67.0 tests/appsec/test_blocking_addresses.py::Test_Blocking_request_uri: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_response_headers: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_response_status: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_user_id: missing_feature tests/appsec/test_blocking_addresses.py::Test_Suspicious_Request_Blocking: missing_feature - tests/appsec/test_conf.py::Test_ConfigurationVariables_New_Obfuscation: missing_feature + tests/appsec/test_conf.py::Test_ConfigurationVariables_New_Obfuscation: v1.67.0 tests/appsec/test_extended_request_body_collection.py::Test_ExtendedRequestBodyCollection: missing_feature - tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Endpoint_Preprocessor: missing_feature - tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor: missing_feature - tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Session_Preprocessor: missing_feature + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Endpoint_Preprocessor: v1.67.0 + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Endpoint_Preprocessor::test_fingerprinting_endpoint_blocking: missing_feature + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor: v1.67.0 + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor::test_fingerprinting_header_blocking: missing_feature + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor::test_fingerprinting_network_blocking: missing_feature + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Session_Preprocessor: v1.67.0 + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Session_Preprocessor::test_session_blocking: missing_feature tests/appsec/test_inferred_spans.py::Test_Lambda_Inferred_Span_Tags: - weblog_declaration: "*": irrelevant (only api gateway inferred spans are specified for now) java-apigw-http: missing_feature java-apigw-rest: missing_feature tests/appsec/test_only_python.py::Test_ImportError: irrelevant (specific tests for python tracer) - tests/appsec/test_reports.py::Test_AttackTimestamp: missing_feature - tests/appsec/test_reports.py::Test_ExtraTagsFromRule: missing_feature - tests/appsec/test_reports.py::Test_Info: missing_feature - tests/appsec/test_reports.py::Test_RequestHeaders: missing_feature - tests/appsec/test_reports.py::Test_StatusCode: missing_feature - tests/appsec/test_reports.py::Test_TagsFromRule: missing_feature - tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: missing_feature - tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_no_event: missing_feature - tests/appsec/test_traces.py::Test_AppSecEventSpanTags: missing_feature - tests/appsec/test_traces.py::Test_AppSecObfuscator: missing_feature - tests/appsec/test_traces.py::Test_CollectDefaultRequestHeader: missing_feature - tests/appsec/test_traces.py::Test_CollectRespondHeaders: missing_feature - tests/appsec/test_traces.py::Test_CollectRespondHeaders::test_header_collection: missing_feature - tests/appsec/test_traces.py::Test_ExternalWafRequestsIdentification: missing_feature - tests/appsec/test_traces.py::Test_RetainTraces: missing_feature - tests/appsec/test_versions.py::Test_Events: missing_feature + tests/appsec/test_reports.py::Test_AttackTimestamp: v1.67.0 + tests/appsec/test_reports.py::Test_ExtraTagsFromRule: v1.67.0 + tests/appsec/test_reports.py::Test_Info: v1.67.0 + tests/appsec/test_reports.py::Test_RequestHeaders: v1.67.0 + tests/appsec/test_reports.py::Test_StatusCode: v1.67.0 + tests/appsec/test_reports.py::Test_TagsFromRule: v1.67.0 + tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v1.67.0 + tests/appsec/test_traces.py::Test_AppSecEventSpanTags: v1.67.0 + tests/appsec/test_traces.py::Test_AppSecObfuscator: v1.67.0 + tests/appsec/test_traces.py::Test_CollectDefaultRequestHeader: v1.67.0 + tests/appsec/test_traces.py::Test_CollectRespondHeaders: v1.67.0 + tests/appsec/test_traces.py::Test_ExternalWafRequestsIdentification: v1.67.0 + tests/appsec/test_traces.py::Test_RetainTraces: v1.67.0 + tests/appsec/test_versions.py::Test_Events: v1.67.0 tests/appsec/waf/test_blocking.py::Test_Blocking: missing_feature tests/appsec/waf/test_blocking.py::Test_Blocking_strip_response_headers: missing_feature tests/appsec/waf/test_blocking.py::Test_CustomBlockingResponse: missing_feature diff --git a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java index 3051fd4dcd1..afec16732e9 100644 --- a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java +++ b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java @@ -254,9 +254,12 @@ private Object handleRoot(RequestContext ctx) { } private Object handleHeaders(RequestContext ctx) { + String body = "OK"; Map responseHeaders = new HashMap<>(); + responseHeaders.put("Content-Type", "text/plain"); + responseHeaders.put("Content-Length", Integer.toString(body.getBytes(StandardCharsets.UTF_8).length)); responseHeaders.put("Content-Language", "en-US"); - return createResponse(ctx, "OK", 200, responseHeaders); + return createResponse(ctx, body, 200, responseHeaders); } private Object handleHealthcheck(RequestContext ctx) { @@ -290,7 +293,14 @@ private Object handleTagValue(RequestContext ctx) { // Track custom AppSec event trackCustomEvent(tagValue); - return createResponse(ctx, "Value tagged", statusCode); + Map responseHeaders = new HashMap<>(ctx.queryParams); + if ("POST".equals(ctx.method) && tagValue.startsWith("payload_in_response_body")) { + Map responseBody = new HashMap<>(); + responseBody.put("payload", parseFormData(ctx.body)); + return createJsonResponse(ctx, responseBody, statusCode, responseHeaders); + } + + return createResponse(ctx, "Value tagged", statusCode, responseHeaders); } private Object handleUserLoginSuccessEvent(RequestContext ctx) { @@ -591,8 +601,13 @@ private Object createResponse(RequestContext ctx, String body, int statusCode, M } private Object createJsonResponse(RequestContext ctx, Object data, int statusCode) { + return createJsonResponse(ctx, data, statusCode, null); + } + + private Object createJsonResponse(RequestContext ctx, Object data, int statusCode, + Map extraHeaders) { String body = gson.toJson(data); - Map headers = new HashMap<>(); + Map headers = extraHeaders != null ? new HashMap<>(extraHeaders) : new HashMap<>(); headers.put("Content-Type", "application/json"); return createResponse(ctx, body, statusCode, headers); } From 7702f56fe806238b66046032a77d9c31dbfda715 Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Wed, 21 Oct 2026 14:30:13 +0200 Subject: [PATCH 02/10] Fix Lambda proxy events and Java weblog payload handling --- .../java/com/datadoghq/lambda/Handler.java | 60 +++++++++++++++---- utils/build/docker/lambda_proxy/alb.py | 16 +++-- utils/build/docker/lambda_proxy/main.py | 7 +++ 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java index afec16732e9..35470965b19 100644 --- a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java +++ b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java @@ -53,7 +53,7 @@ public class Handler implements RequestHandler { private static final String TRACK_CUSTOM_APPSEC_EVENT_NAME = "system_tests_appsec_event"; private static final String TRACK_USER = "system_tests_user"; - private enum ResponseType { API_GATEWAY_REST, API_GATEWAY_HTTP } + private enum ResponseType { API_GATEWAY_REST, API_GATEWAY_HTTP, ALB_MULTI } /** * Request context containing response type and request data @@ -116,7 +116,7 @@ private Object handleAPIGatewayRestEvent(APIGatewayProxyRequestEvent event) { event.getHttpMethod(), event.getQueryStringParameters(), event.getHeaders(), - event.getBody(), + decodeBody(event.getBody(), event.getIsBase64Encoded()), event.getPathParameters() ); return routeRequest(ctx); @@ -133,25 +133,52 @@ private Object handleAPIGatewayHttpEvent(APIGatewayV2HTTPEvent event) { event.getRequestContext().getHttp().getMethod(), queryParams, event.getHeaders(), - event.getBody(), + decodeBody(event.getBody(), event.getIsBase64Encoded()), event.getPathParameters() ); return routeRequest(ctx); } private Object handleALBEvent(ApplicationLoadBalancerRequestEvent event) { + boolean multiValue = event.getMultiValueHeaders() != null; + Map headers = multiValue ? + flattenHeaders(event.getMultiValueHeaders()) : event.getHeaders(); + Map queryParams = event.getQueryStringParameters(); + if (multiValue && event.getMultiValueQueryStringParameters() != null) { + queryParams = new HashMap<>(); + for (Map.Entry> entry : event.getMultiValueQueryStringParameters().entrySet()) { + List values = entry.getValue(); + if (values != null && !values.isEmpty()) { + queryParams.put(entry.getKey(), values.get(values.size() - 1)); + } + } + } RequestContext ctx = new RequestContext( - ResponseType.API_GATEWAY_REST, + multiValue ? ResponseType.ALB_MULTI : ResponseType.API_GATEWAY_REST, event.getPath(), event.getHttpMethod(), - event.getQueryStringParameters(), - event.getHeaders(), - event.getBody(), + queryParams, + headers, + decodeBody(event.getBody(), event.getIsBase64Encoded()), null ); return routeRequest(ctx); } + private Map flattenHeaders(Map> multiValueHeaders) { + Map headers = new HashMap<>(); + for (Map.Entry> entry : multiValueHeaders.entrySet()) { + String separator = "cookie".equalsIgnoreCase(entry.getKey()) ? "; " : ", "; + headers.put(entry.getKey(), String.join(separator, entry.getValue())); + } + return headers; + } + + private String decodeBody(String body, Boolean base64Encoded) { + return body != null && Boolean.TRUE.equals(base64Encoded) ? + new String(Base64.getDecoder().decode(body), StandardCharsets.UTF_8) : body; + } + private Object handleMapEvent(Map eventMap) { // Check for healthcheck if (eventMap.containsKey("healthcheck") && Boolean.TRUE.equals(eventMap.get("healthcheck"))) { @@ -595,7 +622,16 @@ private Object createResponse(RequestContext ctx, String body, int statusCode, M APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(statusCode); response.setBody(body); - response.setHeaders(headers != null ? headers : new HashMap<>()); + response.setIsBase64Encoded(false); + if (ctx.responseType == ResponseType.ALB_MULTI) { + Map> multiValueHeaders = new HashMap<>(); + if (headers != null) { + headers.forEach((name, value) -> multiValueHeaders.put(name, Collections.singletonList(value))); + } + response.setMultiValueHeaders(multiValueHeaders); + } else { + response.setHeaders(headers != null ? headers : new HashMap<>()); + } return response; } } @@ -644,8 +680,12 @@ private Object createResponseWithCookies(RequestContext ctx, String body, int st .map(e -> e.getKey() + "=" + e.getValue() + "; Path=/; HttpOnly") .collect(Collectors.toList()); multiValueHeaders.put("Set-Cookie", setCookieValues); - - response.setHeaders(headers); + if (ctx.responseType == ResponseType.ALB_MULTI) { + headers.forEach((name, value) -> multiValueHeaders.put(name, Collections.singletonList(value))); + } else { + response.setHeaders(headers); + } + response.setIsBase64Encoded(false); response.setMultiValueHeaders(multiValueHeaders); return response; } diff --git a/utils/build/docker/lambda_proxy/alb.py b/utils/build/docker/lambda_proxy/alb.py index 54f4d5f0107..e1903d64c85 100644 --- a/utils/build/docker/lambda_proxy/alb.py +++ b/utils/build/docker/lambda_proxy/alb.py @@ -46,15 +46,23 @@ def build_alb_event(request: Request, multi: bool) -> dict: "isBase64Encoded": is_b64, } + multi_value_headers = defaultdict(list) + for k, v in request.headers.to_wsgi_list(): + multi_value_headers[k.lower()].append(v) + + # ALB supplies the client address and listener information on every invocation. + client_ip = request.remote_addr or "127.0.0.1" + forwarded_for = multi_value_headers.get("x-forwarded-for", []) + multi_value_headers["x-forwarded-for"] = [", ".join([*forwarded_for, client_ip])] + multi_value_headers.setdefault("x-forwarded-proto", [request.scheme]) + multi_value_headers.setdefault("x-forwarded-port", [str(request.environ["SERVER_PORT"])]) + if multi: event["multiValueQueryStringParameters"] = request.args.to_dict(flat=False) - multi_value_headers = defaultdict(list) - for k, v in request.headers.to_wsgi_list(): - multi_value_headers[k].append(v) event["multiValueHeaders"] = dict(multi_value_headers) else: event["queryStringParameters"] = request.args.to_dict(flat=True) - event["headers"] = dict(request.headers) + event["headers"] = {k: v[-1] for k, v in multi_value_headers.items()} return event diff --git a/utils/build/docker/lambda_proxy/main.py b/utils/build/docker/lambda_proxy/main.py index 17e5dee2dbf..49b56c5efa3 100644 --- a/utils/build/docker/lambda_proxy/main.py +++ b/utils/build/docker/lambda_proxy/main.py @@ -66,6 +66,10 @@ def invoke_lambda_function_api_gateway_http(): path = PathConverter.convert_path_to_api_gateway(request.path) route_key = LocalApigwService._v2_route_key(request.method, path, is_default_route=False) converted_event = construct_v2_event_http(request, PORT, binary_types=BINARY_TYPES, route_key=route_key) + # Payload v2 carries cookies in its dedicated field, not in both representations. + converted_event["headers"] = { + name: value for name, value in converted_event["headers"].items() if name.lower() != "cookie" + } converted_event["requestContext"]["http"]["userAgent"] = request.user_agent.string response = post( @@ -94,6 +98,9 @@ def invoke_lambda_function_function_url_service(): stage_name="$default", route_key="$default", ) + converted_event["headers"] = { + name: value for name, value in converted_event["headers"].items() if name.lower() != "cookie" + } rc: dict[str, str] = converted_event["requestContext"] rc["domainName"] = "abcde" From 8f13eba53e399b5da1ee34ed57bda99c1c829f70 Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Thu, 22 Oct 2026 15:24:13 +0200 Subject: [PATCH 03/10] Preserve Lambda request headers in the test proxy --- utils/build/docker/lambda_proxy/alb.py | 7 ------- utils/build/docker/lambda_proxy/main.py | 7 ------- 2 files changed, 14 deletions(-) diff --git a/utils/build/docker/lambda_proxy/alb.py b/utils/build/docker/lambda_proxy/alb.py index e1903d64c85..a14a260b3bb 100644 --- a/utils/build/docker/lambda_proxy/alb.py +++ b/utils/build/docker/lambda_proxy/alb.py @@ -50,13 +50,6 @@ def build_alb_event(request: Request, multi: bool) -> dict: for k, v in request.headers.to_wsgi_list(): multi_value_headers[k.lower()].append(v) - # ALB supplies the client address and listener information on every invocation. - client_ip = request.remote_addr or "127.0.0.1" - forwarded_for = multi_value_headers.get("x-forwarded-for", []) - multi_value_headers["x-forwarded-for"] = [", ".join([*forwarded_for, client_ip])] - multi_value_headers.setdefault("x-forwarded-proto", [request.scheme]) - multi_value_headers.setdefault("x-forwarded-port", [str(request.environ["SERVER_PORT"])]) - if multi: event["multiValueQueryStringParameters"] = request.args.to_dict(flat=False) event["multiValueHeaders"] = dict(multi_value_headers) diff --git a/utils/build/docker/lambda_proxy/main.py b/utils/build/docker/lambda_proxy/main.py index 49b56c5efa3..17e5dee2dbf 100644 --- a/utils/build/docker/lambda_proxy/main.py +++ b/utils/build/docker/lambda_proxy/main.py @@ -66,10 +66,6 @@ def invoke_lambda_function_api_gateway_http(): path = PathConverter.convert_path_to_api_gateway(request.path) route_key = LocalApigwService._v2_route_key(request.method, path, is_default_route=False) converted_event = construct_v2_event_http(request, PORT, binary_types=BINARY_TYPES, route_key=route_key) - # Payload v2 carries cookies in its dedicated field, not in both representations. - converted_event["headers"] = { - name: value for name, value in converted_event["headers"].items() if name.lower() != "cookie" - } converted_event["requestContext"]["http"]["userAgent"] = request.user_agent.string response = post( @@ -98,9 +94,6 @@ def invoke_lambda_function_function_url_service(): stage_name="$default", route_key="$default", ) - converted_event["headers"] = { - name: value for name, value in converted_event["headers"].items() if name.lower() != "cookie" - } rc: dict[str, str] = converted_event["requestContext"] rc["domainName"] = "abcde" From 43351087c86dc01f6a4d199adc0a7d2139100950 Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Mon, 26 Oct 2026 10:18:44 +0100 Subject: [PATCH 04/10] Gate Lambda tests requiring fixes on Java 1.68.0-SNAPSHOT --- manifests/java_lambda.yml | 104 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/manifests/java_lambda.yml b/manifests/java_lambda.yml index c10d2fbd416..c3fe6d25e49 100644 --- a/manifests/java_lambda.yml +++ b/manifests/java_lambda.yml @@ -1,10 +1,21 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/DataDog/system-tests/refs/heads/main/utils/manifest/schema.json --- manifest: - tests/appsec/api_security/test_schemas.py::Test_Scanners: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Scanners: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi, java-apigw-http, java-function-url": v1.68.0-SNAPSHOT tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Cookies: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Cookies::test_request_method: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/api_security/test_schemas.py::Test_Schema_Request_FormUrlEncoded_Body: v1.67.0 tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Headers: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Headers::test_request_method: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Json_Body: v1.67.0 tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Path_Parameters: - weblog_declaration: @@ -13,6 +24,10 @@ manifest: java-alb-multi: irrelevant (application-load-balancer event type does not support path params) java-function-url: irrelevant (function-url event type does not support path params) tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Query_Parameters: v1.67.0 + tests/appsec/api_security/test_schemas.py::Test_Schema_Request_Query_Parameters::test_request_method: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/api_security/test_schemas.py::Test_Schema_Response_Body: v1.67.0 tests/appsec/api_security/test_schemas.py::Test_Schema_Response_Headers: v1.67.0 tests/appsec/api_security/test_schemas.py::Test_Schema_Response_on_Block: missing_feature @@ -38,7 +53,10 @@ manifest: tests/appsec/rasp/test_lfi.py::Test_Lfi_Optional_SpanTags::test_lfi_span_tags: v1.67.0 tests/appsec/rasp/test_lfi.py::Test_Lfi_RC_CustomAction: missing_feature tests/appsec/rasp/test_lfi.py::Test_Lfi_Rules_Version: missing_feature - tests/appsec/rasp/test_lfi.py::Test_Lfi_StackTrace::test_lfi_stack_trace: v1.67.0 + tests/appsec/rasp/test_lfi.py::Test_Lfi_StackTrace::test_lfi_stack_trace: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/rasp/test_lfi.py::Test_Lfi_Telemetry::test_lfi_telemetry: v1.67.0 tests/appsec/rasp/test_lfi.py::Test_Lfi_Telemetry_Multiple_Exploits: missing_feature tests/appsec/rasp/test_lfi.py::Test_Lfi_Telemetry_V2: missing_feature @@ -51,7 +69,10 @@ manifest: tests/appsec/rasp/test_shi.py::Test_Shi_Mandatory_SpanTags::test_shi_span_tags: v1.67.0 tests/appsec/rasp/test_shi.py::Test_Shi_Optional_SpanTags::test_shi_span_tags: v1.67.0 tests/appsec/rasp/test_shi.py::Test_Shi_Rules_Version: missing_feature - tests/appsec/rasp/test_shi.py::Test_Shi_StackTrace::test_shi_stack_trace: v1.67.0 + tests/appsec/rasp/test_shi.py::Test_Shi_StackTrace::test_shi_stack_trace: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/rasp/test_shi.py::Test_Shi_Telemetry::test_shi_telemetry: v1.67.0 tests/appsec/rasp/test_shi.py::Test_Shi_Telemetry_V2: missing_feature tests/appsec/rasp/test_shi.py::Test_Shi_Telemetry_Variant_Tag::test_shi_telemetry: v1.67.0 @@ -65,12 +86,27 @@ manifest: tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Mandatory_SpanTags::test_ssrf_span_tags: v1.67.0 tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Optional_SpanTags::test_ssrf_span_tags: v1.67.0 tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Rules_Version: missing_feature - tests/appsec/rasp/test_ssrf.py::Test_Ssrf_StackTrace::test_ssrf_stack_trace: v1.67.0 + tests/appsec/rasp/test_ssrf.py::Test_Ssrf_StackTrace::test_ssrf_stack_trace: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Telemetry::test_ssrf_telemetry: v1.67.0 tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Telemetry_V2: missing_feature tests/appsec/rasp/test_ssrf.py::Test_Ssrf_UrlQuery: missing_feature tests/appsec/rasp/test_ssrf.py::Test_Ssrf_Waf_Version: missing_feature tests/appsec/test_alpha.py::Test_Basic: v1.67.0 + tests/appsec/test_alpha.py::Test_Basic::test_headers: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_alpha.py::Test_Basic::test_no_cookies: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_alpha.py::Test_Basic::test_uri: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_blocking_addresses.py::Test_Blocking_client_ip: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_client_ip_with_K8_private_ip: missing_feature tests/appsec/test_blocking_addresses.py::Test_Blocking_client_ip_with_forwarded: missing_feature @@ -98,11 +134,23 @@ manifest: tests/appsec/test_blocking_addresses.py::Test_Blocking_user_id: missing_feature tests/appsec/test_blocking_addresses.py::Test_Suspicious_Request_Blocking: missing_feature tests/appsec/test_conf.py::Test_ConfigurationVariables_New_Obfuscation: v1.67.0 + tests/appsec/test_conf.py::Test_ConfigurationVariables_New_Obfuscation::test_partial_obfuscation_parameter_value: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_extended_request_body_collection.py::Test_ExtendedRequestBodyCollection: missing_feature tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Endpoint_Preprocessor: v1.67.0 tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Endpoint_Preprocessor::test_fingerprinting_endpoint_blocking: missing_feature + tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Endpoint_Preprocessor::test_fingerprinting_endpoint_non_blocking: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor: v1.67.0 tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor::test_fingerprinting_header_blocking: missing_feature + ? tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor::test_fingerprinting_header_non_blocking + : - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Header_And_Network_Preprocessor::test_fingerprinting_network_blocking: missing_feature tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Session_Preprocessor: v1.67.0 tests/appsec/test_fingerprinting.py::Test_Fingerprinting_Session_Preprocessor::test_session_blocking: missing_feature @@ -113,18 +161,66 @@ manifest: java-apigw-rest: missing_feature tests/appsec/test_only_python.py::Test_ImportError: irrelevant (specific tests for python tracer) tests/appsec/test_reports.py::Test_AttackTimestamp: v1.67.0 + tests/appsec/test_reports.py::Test_AttackTimestamp::test_basic: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_reports.py::Test_ExtraTagsFromRule: v1.67.0 + tests/appsec/test_reports.py::Test_ExtraTagsFromRule::test_tool_name: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_reports.py::Test_Info: v1.67.0 tests/appsec/test_reports.py::Test_RequestHeaders: v1.67.0 tests/appsec/test_reports.py::Test_StatusCode: v1.67.0 tests/appsec/test_reports.py::Test_TagsFromRule: v1.67.0 + tests/appsec/test_reports.py::Test_TagsFromRule::test_category: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_reports.py::Test_TagsFromRule::test_type: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v1.67.0 + tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_event: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_no_event: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_no_keep_event: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_no_keep_no_event: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_traces.py::Test_AppSecEventSpanTags: v1.67.0 + tests/appsec/test_traces.py::Test_AppSecEventSpanTags::test_header_collection: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_traces.py::Test_AppSecObfuscator: v1.67.0 + tests/appsec/test_traces.py::Test_AppSecObfuscator::test_appsec_obfuscator_key: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_traces.py::Test_AppSecObfuscator::test_appsec_obfuscator_value: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_traces.py::Test_CollectDefaultRequestHeader: v1.67.0 tests/appsec/test_traces.py::Test_CollectRespondHeaders: v1.67.0 tests/appsec/test_traces.py::Test_ExternalWafRequestsIdentification: v1.67.0 tests/appsec/test_traces.py::Test_RetainTraces: v1.67.0 + tests/appsec/test_traces.py::Test_RetainTraces::test_appsec_event_span_tags: + - weblog_declaration: + "*": v1.67.0 + "java-alb, java-alb-multi": v1.68.0-SNAPSHOT tests/appsec/test_versions.py::Test_Events: v1.67.0 tests/appsec/waf/test_blocking.py::Test_Blocking: missing_feature tests/appsec/waf/test_blocking.py::Test_Blocking_strip_response_headers: missing_feature From a0cd2bb747d94f14d750a2c7c487a95e459a493f Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Mon, 26 Oct 2026 17:40:40 +0100 Subject: [PATCH 05/10] Add Java Lambda SCA system-test coverage --- .github/workflows/run-end-to-end.yml | 3 ++ docs/understand/weblogs/end-to-end_weblog.md | 2 + manifests/java_lambda.yml | 5 ++ tests/appsec/test_sca_reachability.py | 16 ++++++- utils/_context/_scenarios/__init__.py | 6 +++ .../java_lambda/java-alb-multi.Dockerfile | 1 + .../docker/java_lambda/java-alb.Dockerfile | 1 + .../java_lambda/java-apigw-http.Dockerfile | 1 + .../java_lambda/java-apigw-rest.Dockerfile | 1 + .../java_lambda/java-function-url.Dockerfile | 1 + utils/build/docker/java_lambda/pom.xml | 28 +++++++++++ .../java/com/datadoghq/lambda/Handler.java | 8 ++++ .../com/datadoghq/lambda/ScaReachability.java | 46 +++++++++++++++++++ utils/build/docker/lambda_proxy/main.py | 2 + .../scripts/libraries_and_scenarios_rules.yml | 2 +- 15 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/ScaReachability.java diff --git a/.github/workflows/run-end-to-end.yml b/.github/workflows/run-end-to-end.yml index a9b7c4a8bf4..0d8ba7b0333 100644 --- a/.github/workflows/run-end-to-end.yml +++ b/.github/workflows/run-end-to-end.yml @@ -529,6 +529,9 @@ jobs: - name: Run APPSEC_LAMBDA_API_SECURITY scenario if: steps.build.outcome == 'success' && !cancelled() && contains(inputs.scenarios, '"APPSEC_LAMBDA_API_SECURITY"') run: ./run.sh APPSEC_LAMBDA_API_SECURITY + - name: Run APPSEC_LAMBDA_SCA scenario + if: steps.build.outcome == 'success' && !cancelled() && contains(inputs.scenarios, '"APPSEC_LAMBDA_SCA"') + run: ./run.sh APPSEC_LAMBDA_SCA - name: Run APPSEC_LAMBDA_RASP scenario if: steps.build.outcome == 'success' && !cancelled() && contains(inputs.scenarios, '"APPSEC_LAMBDA_RASP"') run: ./run.sh APPSEC_LAMBDA_RASP diff --git a/docs/understand/weblogs/end-to-end_weblog.md b/docs/understand/weblogs/end-to-end_weblog.md index 23fa0d6127b..55c4244e6ee 100644 --- a/docs/understand/weblogs/end-to-end_weblog.md +++ b/docs/understand/weblogs/end-to-end_weblog.md @@ -887,6 +887,8 @@ Alternate call site for the same vulnerable function targeted by `/sca/vulnerabl test first-hit-wins deduplication: when the same CVE is triggered from two different call sites, only the 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. + ### 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/java_lambda.yml b/manifests/java_lambda.yml index c3fe6d25e49..3942a2e1a9c 100644 --- a/manifests/java_lambda.yml +++ b/manifests/java_lambda.yml @@ -182,6 +182,11 @@ manifest: - weblog_declaration: "*": v1.67.0 "java-alb, java-alb-multi": v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_After_Vulnerable_Call: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_Registered_At_Load_Time: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Deduplication: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Dependencies_Have_Metadata: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_First_Hit_Wins: v1.68.0-SNAPSHOT tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v1.67.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_event: - weblog_declaration: diff --git a/tests/appsec/test_sca_reachability.py b/tests/appsec/test_sca_reachability.py index 04e1fdcf30b..0afea863e7e 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", }, + "java_lambda": { + "cve_id": ["GHSA-hf5p-q87m-crj7"], + "vulnerable_dep": "junrar", + "path": "com.datadoghq.lambda.ScaReachability", + "symbol": "junrarHit", + }, } @@ -112,6 +118,7 @@ def _get_deps_with_reachability_metadata() -> set[str]: @rfc(SCA_REACHABILITY_RFC) @features.runtime_sca_reachability @scenarios.runtime_sca_reachability +@scenarios.appsec_lambda_sca class Test_SCA_Reachability_Dependencies_Have_Metadata: """When DD_APPSEC_SCA_ENABLED=true, vulnerable dependencies have reachability metadata.""" @@ -129,6 +136,7 @@ def test_dependencies_have_metadata_key(self) -> None: @rfc(SCA_REACHABILITY_RFC) @features.runtime_sca_reachability @scenarios.runtime_sca_reachability +@scenarios.appsec_lambda_sca class Test_SCA_Reachability_CVE_Registered_At_Load_Time: """CVEs are registered at startup with reached=[] before any vulnerable call.""" @@ -148,12 +156,14 @@ def test_cve_registered_at_load_time(self) -> None: @rfc(SCA_REACHABILITY_RFC) @features.runtime_sca_reachability @scenarios.runtime_sca_reachability +@scenarios.appsec_lambda_sca class Test_SCA_Reachability_CVE_After_Vulnerable_Call: """Calling a vulnerable function reports CVE with caller info in reached array.""" def setup_cve_metadata_after_vulnerable_call(self) -> None: self.r0 = weblog.get("/") self.r1 = weblog.get("/sca/vulnerable-call") + assert self.r1.status_code == 200 def test_cve_metadata_after_vulnerable_call(self) -> None: cve_entries = _get_dependency_cve_metadata(_vulnerable_dep(), _cve_id()) @@ -186,13 +196,16 @@ def test_cve_metadata_after_vulnerable_call(self) -> None: @rfc(SCA_REACHABILITY_RFC) @features.runtime_sca_reachability @scenarios.runtime_sca_reachability +@scenarios.appsec_lambda_sca class Test_SCA_Reachability_First_Hit_Wins: """Two call sites for same CVE: only first hit is reported (max 1 reached per entry).""" def setup_same_cve_first_hit_wins(self) -> None: self.r0 = weblog.get("/") self.r1 = weblog.get("/sca/vulnerable-call") + assert self.r1.status_code == 200 self.r2 = weblog.get("/sca/vulnerable-call-alt") + assert self.r2.status_code == 200 def test_same_cve_first_hit_wins(self) -> None: cve_entries = _get_dependency_cve_metadata(_vulnerable_dep(), _cve_id()) @@ -234,13 +247,14 @@ def test_same_cve_first_hit_wins(self) -> None: @rfc(SCA_REACHABILITY_RFC) @features.runtime_sca_reachability @scenarios.runtime_sca_reachability +@scenarios.appsec_lambda_sca class Test_SCA_Reachability_Deduplication: """Repeated calls from same site for same CVE: still max 1 reached entry.""" def setup_deduplication_repeated_calls(self) -> None: self.r0 = weblog.get("/") for _ in range(5): - weblog.get("/sca/vulnerable-call") + assert weblog.get("/sca/vulnerable-call").status_code == 200 def test_deduplication_repeated_calls(self) -> None: cve_entries = _get_dependency_cve_metadata(_vulnerable_dep(), _cve_id()) diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index f8af39d6e0c..25cf843185b 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -1603,6 +1603,12 @@ class _Scenarios: """, scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], ) + appsec_lambda_sca = LambdaScenario( + "APPSEC_LAMBDA_SCA", + weblog_env={"DD_APPSEC_SCA_ENABLED": "true"}, + doc="SCA dependency metadata and runtime reachability in Lambda", + scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], + ) appsec_lambda_rasp = AppSecLambdaRaspScenario("APPSEC_LAMBDA_RASP") appsec_lambda_inferred_spans = LambdaScenario( "APPSEC_LAMBDA_INFERRED_SPANS", diff --git a/utils/build/docker/java_lambda/java-alb-multi.Dockerfile b/utils/build/docker/java_lambda/java-alb-multi.Dockerfile index 4d19c09b0b5..74685897f23 100644 --- a/utils/build/docker/java_lambda/java-alb-multi.Dockerfile +++ b/utils/build/docker/java_lambda/java-alb-multi.Dockerfile @@ -15,6 +15,7 @@ RUN /binaries/install_ddtrace.sh # Copy built JAR and extract to Lambda task root COPY --from=builder /build/target/java-lambda-weblog-*.jar /tmp/app.jar RUN cd ${LAMBDA_TASK_ROOT} && jar xf /tmp/app.jar && rm /tmp/app.jar +COPY --from=builder /build/target/sca-libs/*.jar ${LAMBDA_TASK_ROOT}/lib/ # Copy Datadog agent and entrypoint RUN cp /dd-tracer/dd-java-agent.jar ${LAMBDA_TASK_ROOT}/dd-java-agent.jar diff --git a/utils/build/docker/java_lambda/java-alb.Dockerfile b/utils/build/docker/java_lambda/java-alb.Dockerfile index 8b7fadb89ad..14728d91b06 100644 --- a/utils/build/docker/java_lambda/java-alb.Dockerfile +++ b/utils/build/docker/java_lambda/java-alb.Dockerfile @@ -15,6 +15,7 @@ RUN /binaries/install_ddtrace.sh # Copy built JAR and extract to Lambda task root COPY --from=builder /build/target/java-lambda-weblog-*.jar /tmp/app.jar RUN cd ${LAMBDA_TASK_ROOT} && jar xf /tmp/app.jar && rm /tmp/app.jar +COPY --from=builder /build/target/sca-libs/*.jar ${LAMBDA_TASK_ROOT}/lib/ # Copy Datadog agent and entrypoint RUN cp /dd-tracer/dd-java-agent.jar ${LAMBDA_TASK_ROOT}/dd-java-agent.jar diff --git a/utils/build/docker/java_lambda/java-apigw-http.Dockerfile b/utils/build/docker/java_lambda/java-apigw-http.Dockerfile index 8a3913f234e..0b92cb657cc 100644 --- a/utils/build/docker/java_lambda/java-apigw-http.Dockerfile +++ b/utils/build/docker/java_lambda/java-apigw-http.Dockerfile @@ -15,6 +15,7 @@ RUN /binaries/install_ddtrace.sh # Copy built JAR and extract to Lambda task root COPY --from=builder /build/target/java-lambda-weblog-*.jar /tmp/app.jar RUN cd ${LAMBDA_TASK_ROOT} && jar xf /tmp/app.jar && rm /tmp/app.jar +COPY --from=builder /build/target/sca-libs/*.jar ${LAMBDA_TASK_ROOT}/lib/ # Copy Datadog agent and entrypoint RUN cp /dd-tracer/dd-java-agent.jar ${LAMBDA_TASK_ROOT}/dd-java-agent.jar diff --git a/utils/build/docker/java_lambda/java-apigw-rest.Dockerfile b/utils/build/docker/java_lambda/java-apigw-rest.Dockerfile index d91b15ad8e0..2719e780e3f 100644 --- a/utils/build/docker/java_lambda/java-apigw-rest.Dockerfile +++ b/utils/build/docker/java_lambda/java-apigw-rest.Dockerfile @@ -15,6 +15,7 @@ RUN /binaries/install_ddtrace.sh # Copy built JAR and extract to Lambda task root COPY --from=builder /build/target/java-lambda-weblog-*.jar /tmp/app.jar RUN cd ${LAMBDA_TASK_ROOT} && jar xf /tmp/app.jar && rm /tmp/app.jar +COPY --from=builder /build/target/sca-libs/*.jar ${LAMBDA_TASK_ROOT}/lib/ # Copy Datadog agent and entrypoint RUN cp /dd-tracer/dd-java-agent.jar ${LAMBDA_TASK_ROOT}/dd-java-agent.jar diff --git a/utils/build/docker/java_lambda/java-function-url.Dockerfile b/utils/build/docker/java_lambda/java-function-url.Dockerfile index 6ac4014575f..e9b31e0ef99 100644 --- a/utils/build/docker/java_lambda/java-function-url.Dockerfile +++ b/utils/build/docker/java_lambda/java-function-url.Dockerfile @@ -15,6 +15,7 @@ RUN /binaries/install_ddtrace.sh # Copy built JAR and extract to Lambda task root COPY --from=builder /build/target/java-lambda-weblog-*.jar /tmp/app.jar RUN cd ${LAMBDA_TASK_ROOT} && jar xf /tmp/app.jar && rm /tmp/app.jar +COPY --from=builder /build/target/sca-libs/*.jar ${LAMBDA_TASK_ROOT}/lib/ # Copy Datadog agent and entrypoint RUN cp /dd-tracer/dd-java-agent.jar ${LAMBDA_TASK_ROOT}/dd-java-agent.jar diff --git a/utils/build/docker/java_lambda/pom.xml b/utils/build/docker/java_lambda/pom.xml index 4d9ee8f3916..a15bab66620 100644 --- a/utils/build/docker/java_lambda/pom.xml +++ b/utils/build/docker/java_lambda/pom.xml @@ -93,16 +93,44 @@ sqlite-jdbc 3.47.1.0 + + + com.github.junrar + junrar + 7.5.5 + + + org.apache.maven.plugins + maven-dependency-plugin + 3.8.1 + + + copy-sca-dependency + package + copy-dependencies + + com.github.junrar + junrar + ${project.build.directory}/sca-libs + + + + org.apache.maven.plugins maven-shade-plugin 3.2.4 false + + + com.github.junrar:junrar + + diff --git a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java index 35470965b19..05c460ff0fa 100644 --- a/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java +++ b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/Handler.java @@ -53,6 +53,10 @@ public class Handler implements RequestHandler { private static final String TRACK_CUSTOM_APPSEC_EVENT_NAME = "system_tests_appsec_event"; private static final String TRACK_USER = "system_tests_user"; + static { + ScaReachability.preloadVulnerableClass(); + } + private enum ResponseType { API_GATEWAY_REST, API_GATEWAY_HTTP, ALB_MULTI } /** @@ -241,6 +245,10 @@ private Object routeRequest(RequestContext ctx) { // Route to handlers if (ctx.path.equals("/") || ctx.path.isEmpty()) { return handleRoot(ctx); + } else if (ctx.path.equals("/sca/vulnerable-call") && "GET".equals(ctx.method)) { + return createResponse(ctx, ScaReachability.junrarHit(), 200); + } else if (ctx.path.equals("/sca/vulnerable-call-alt") && "GET".equals(ctx.method)) { + return createResponse(ctx, ScaReachability.junrarHitAlt(), 200); } else if (ctx.path.equals("/headers") && "GET".equals(ctx.method)) { return handleHeaders(ctx); } else if (ctx.path.equals("/healthcheck") && "GET".equals(ctx.method)) { 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 new file mode 100644 index 00000000000..4d9d8a94ce1 --- /dev/null +++ b/utils/build/docker/java_lambda/src/main/java/com/datadoghq/lambda/ScaReachability.java @@ -0,0 +1,46 @@ +package com.datadoghq.lambda; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +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(). + // 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 { + Class.forName("com.github.junrar.LocalFolderExtractor"); + } catch (Exception ignored) {} + } + + public static String junrarHit() { + try { + Class cls = Class.forName("com.github.junrar.LocalFolderExtractor"); + Constructor ctor = cls.getDeclaredConstructors()[0]; + ctor.setAccessible(true); + Object instance = ctor.newInstance(new File("/tmp")); + Class fileHeaderClass = Class.forName("com.github.junrar.rarfile.FileHeader"); + Method method = cls.getDeclaredMethod("createDirectory", fileHeaderClass); + method.setAccessible(true); + method.invoke(instance, (Object) null); + } catch (Exception ignored) {} + return "OK"; + } + + public static String junrarHitAlt() { + try { + Class cls = Class.forName("com.github.junrar.LocalFolderExtractor"); + Constructor ctor = cls.getDeclaredConstructors()[0]; + ctor.setAccessible(true); + Object instance = ctor.newInstance(new File("/tmp")); + Class fileHeaderClass = Class.forName("com.github.junrar.rarfile.FileHeader"); + Method method = cls.getDeclaredMethod("createDirectory", fileHeaderClass); + method.setAccessible(true); + method.invoke(instance, (Object) null); + } catch (Exception ignored) {} + return "OK"; + } +} diff --git a/utils/build/docker/lambda_proxy/main.py b/utils/build/docker/lambda_proxy/main.py index 17e5dee2dbf..7588166391d 100644 --- a/utils/build/docker/lambda_proxy/main.py +++ b/utils/build/docker/lambda_proxy/main.py @@ -167,6 +167,8 @@ def invoke_lambda_function_application_load_balancer_multi_value_headers(): exit(1) ROUTES = [ + ("/sca/vulnerable-call", ["GET"]), + ("/sca/vulnerable-call-alt", ["GET"]), ("/", ["GET", "POST", "OPTIONS"]), ("/finger_print", ["GET"]), ("/headers", ["GET"]), diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index a020d593c67..edeaf3f285b 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -608,7 +608,7 @@ patterns: tests/appsec/test_sca_reachability.py: scenario_groups: null - libraries: [java, python] + libraries: [java, java_lambda, python] ################################# Default rules ################################ # The rules in this section are only applied if no other rule is applicable. From b11338a711321113204fe0fe09208b7d4528d14d Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Tue, 27 Oct 2026 10:41:08 +0100 Subject: [PATCH 06/10] Fix Lambda SCA assertions and test with AppSec disabled --- tests/appsec/test_sca_reachability.py | 11 ++++++----- utils/_context/_scenarios/__init__.py | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/appsec/test_sca_reachability.py b/tests/appsec/test_sca_reachability.py index 0afea863e7e..aef5a54ef42 100644 --- a/tests/appsec/test_sca_reachability.py +++ b/tests/appsec/test_sca_reachability.py @@ -163,9 +163,9 @@ class Test_SCA_Reachability_CVE_After_Vulnerable_Call: def setup_cve_metadata_after_vulnerable_call(self) -> None: self.r0 = weblog.get("/") self.r1 = weblog.get("/sca/vulnerable-call") - assert self.r1.status_code == 200 def test_cve_metadata_after_vulnerable_call(self) -> None: + assert self.r1.status_code == 200 cve_entries = _get_dependency_cve_metadata(_vulnerable_dep(), _cve_id()) assert len(cve_entries) >= 1, f"{_cve_id()} not found in {_vulnerable_dep()} metadata" @@ -203,11 +203,11 @@ class Test_SCA_Reachability_First_Hit_Wins: def setup_same_cve_first_hit_wins(self) -> None: self.r0 = weblog.get("/") self.r1 = weblog.get("/sca/vulnerable-call") - assert self.r1.status_code == 200 self.r2 = weblog.get("/sca/vulnerable-call-alt") - assert self.r2.status_code == 200 def test_same_cve_first_hit_wins(self) -> None: + assert self.r1.status_code == 200 + assert self.r2.status_code == 200 cve_entries = _get_dependency_cve_metadata(_vulnerable_dep(), _cve_id()) assert len(cve_entries) >= 1, f"{_cve_id()} not found in {_vulnerable_dep()} metadata" @@ -253,10 +253,11 @@ class Test_SCA_Reachability_Deduplication: def setup_deduplication_repeated_calls(self) -> None: self.r0 = weblog.get("/") - for _ in range(5): - assert weblog.get("/sca/vulnerable-call").status_code == 200 + self.responses = [weblog.get("/sca/vulnerable-call") for _ in range(5)] def test_deduplication_repeated_calls(self) -> None: + for response in self.responses: + assert response.status_code == 200 cve_entries = _get_dependency_cve_metadata(_vulnerable_dep(), _cve_id()) assert len(cve_entries) >= 1, f"{_cve_id()} not found in {_vulnerable_dep()} metadata" diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index 25cf843185b..ff1fe325ddd 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -1605,8 +1605,8 @@ class _Scenarios: ) appsec_lambda_sca = LambdaScenario( "APPSEC_LAMBDA_SCA", - weblog_env={"DD_APPSEC_SCA_ENABLED": "true"}, - doc="SCA dependency metadata and runtime reachability in Lambda", + weblog_env={"DD_APPSEC_ENABLED": "false", "DD_APPSEC_SCA_ENABLED": "true"}, + doc="SCA dependency metadata and runtime reachability in Lambda with AppSec disabled", scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], ) appsec_lambda_rasp = AppSecLambdaRaspScenario("APPSEC_LAMBDA_RASP") From 35b779abc8e9ac62552b5a60da8fdc41f8898f62 Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Tue, 27 Oct 2026 13:01:09 +0100 Subject: [PATCH 07/10] Restore Lambda SCA scenario and test activation after merge --- manifests/java_lambda.yml | 5 +++++ utils/_context/_scenarios/__init__.py | 2 ++ utils/_context/_scenarios/aws_lambda.py | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/manifests/java_lambda.yml b/manifests/java_lambda.yml index adfe282c35d..cf432260099 100644 --- a/manifests/java_lambda.yml +++ b/manifests/java_lambda.yml @@ -132,6 +132,11 @@ manifest: tests/appsec/test_reports.py::Test_TagsFromRule: v1.67.0 tests/appsec/test_reports.py::Test_TagsFromRule::test_category: v1.68.0 tests/appsec/test_reports.py::Test_TagsFromRule::test_type: v1.68.0 + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_After_Vulnerable_Call: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_Registered_At_Load_Time: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Deduplication: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Dependencies_Have_Metadata: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_First_Hit_Wins: v1.68.0-SNAPSHOT tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v1.67.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_event: v1.68.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_no_event: v1.68.0 diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index c79e6396e7a..d6c9620f57d 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -56,6 +56,7 @@ AppsecLambdaDefault, AppsecLambdaInferredSpans, AppsecLambdaRasp, + AppsecLambdaSca, ) from .appsec_rasp import ( AppsecRasp, @@ -399,6 +400,7 @@ class _Scenarios: appsec_lambda_default = AppsecLambdaDefault() appsec_lambda_blocking = AppsecLambdaBlocking() appsec_lambda_api_security = AppsecLambdaApiSecurity() + appsec_lambda_sca = AppsecLambdaSca() appsec_lambda_rasp = AppsecLambdaRasp() appsec_lambda_inferred_spans = AppsecLambdaInferredSpans() otel_collector = OtelCollectorScenario() diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 145ffc735ff..eaa76c02fb4 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -212,6 +212,16 @@ def __init__(self) -> None: ) +class AppsecLambdaSca(_LambdaScenario): + def __init__(self) -> None: + super().__init__( + "APPSEC_LAMBDA_SCA", + weblog_env={"DD_APPSEC_ENABLED": "false", "DD_APPSEC_SCA_ENABLED": "true"}, + doc="SCA dependency metadata and runtime reachability in Lambda with AppSec disabled", + scenario_groups=[scenario_groups.appsec, scenario_groups.appsec_lambda], + ) + + class AppsecLambdaRasp(_LambdaScenario): def __init__(self) -> None: super().__init__( From e348b6f6dfc1872b7ac2c9bb81f4c7d98d98e8bc Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Tue, 27 Oct 2026 13:16:51 +0100 Subject: [PATCH 08/10] Enable Java Lambda SCA tests from released 1.68.0 --- manifests/java_lambda.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manifests/java_lambda.yml b/manifests/java_lambda.yml index cf432260099..4dc485300fa 100644 --- a/manifests/java_lambda.yml +++ b/manifests/java_lambda.yml @@ -132,11 +132,11 @@ manifest: tests/appsec/test_reports.py::Test_TagsFromRule: v1.67.0 tests/appsec/test_reports.py::Test_TagsFromRule::test_category: v1.68.0 tests/appsec/test_reports.py::Test_TagsFromRule::test_type: v1.68.0 - tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_After_Vulnerable_Call: v1.68.0-SNAPSHOT - tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_Registered_At_Load_Time: v1.68.0-SNAPSHOT - tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Deduplication: v1.68.0-SNAPSHOT - tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Dependencies_Have_Metadata: v1.68.0-SNAPSHOT - tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_First_Hit_Wins: v1.68.0-SNAPSHOT + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_After_Vulnerable_Call: v1.68.0 + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_CVE_Registered_At_Load_Time: v1.68.0 + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Deduplication: v1.68.0 + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_Dependencies_Have_Metadata: v1.68.0 + tests/appsec/test_sca_reachability.py::Test_SCA_Reachability_First_Hit_Wins: v1.68.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v1.67.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_event: v1.68.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_keep_no_event: v1.68.0 From 9b8ba148e7015b9b5b6968e3e84d1fdd5310efd0 Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Wed, 28 Oct 2026 11:56:49 +0100 Subject: [PATCH 09/10] Declare SCA reachability gaps in non-Java Lambda manifests --- manifests/nodejs_lambda.yml | 1 + manifests/python_lambda.yml | 1 + manifests/ruby_lambda.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/manifests/nodejs_lambda.yml b/manifests/nodejs_lambda.yml index 3b0b4007d34..7920240c596 100644 --- a/manifests/nodejs_lambda.yml +++ b/manifests/nodejs_lambda.yml @@ -139,6 +139,7 @@ manifest: tests/appsec/test_reports.py::Test_RequestHeaders: '>=12.142.0' # TODO: a lower version might be supported tests/appsec/test_reports.py::Test_StatusCode: missing_feature tests/appsec/test_reports.py::Test_TagsFromRule: '>=12.142.0' # TODO: a lower version might be supported + tests/appsec/test_sca_reachability.py: missing_feature (SCA runtime reachability is not implemented) tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: # TODO: a lower version might be supported - weblog_declaration: '*': missing_feature diff --git a/manifests/python_lambda.yml b/manifests/python_lambda.yml index 7bff96a9e68..8ffd07dc00b 100644 --- a/manifests/python_lambda.yml +++ b/manifests/python_lambda.yml @@ -266,6 +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_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/manifests/ruby_lambda.yml b/manifests/ruby_lambda.yml index cd183c66b17..8e805da030b 100644 --- a/manifests/ruby_lambda.yml +++ b/manifests/ruby_lambda.yml @@ -68,6 +68,7 @@ manifest: tests/appsec/test_reports.py::Test_Info: v3.29.0 tests/appsec/test_reports.py::Test_RequestHeaders: v3.29.0 tests/appsec/test_reports.py::Test_StatusCode: v3.29.0 + tests/appsec/test_sca_reachability.py: missing_feature (SCA runtime reachability is not implemented) tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules: v3.29.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_no_keep_event: v3.30.0 tests/appsec/test_trace_tagging.py::Test_TraceTaggingRules::test_rule_with_attributes_no_keep_no_event: v3.30.0 From 81127bb68877696624d4e173781394a69b1c7b3d Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Thu, 29 Oct 2026 11:52:18 +0100 Subject: [PATCH 10/10] Address Java Lambda SCA reachability review feedback --- .../com/datadoghq/lambda/ScaReachability.java | 85 +++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) 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 4d9d8a94ce1..dbc63c55f6b 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 @@ -2,45 +2,60 @@ import java.io.File; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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(). - // 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 { - Class.forName("com.github.junrar.LocalFolderExtractor"); - } catch (Exception ignored) {} - } + 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(). + // 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 { + Class.forName("com.github.junrar.LocalFolderExtractor"); + } catch (Exception ignored) {} + } - public static String junrarHit() { - try { - Class cls = Class.forName("com.github.junrar.LocalFolderExtractor"); - Constructor ctor = cls.getDeclaredConstructors()[0]; - ctor.setAccessible(true); - Object instance = ctor.newInstance(new File("/tmp")); - Class fileHeaderClass = Class.forName("com.github.junrar.rarfile.FileHeader"); - Method method = cls.getDeclaredMethod("createDirectory", fileHeaderClass); - method.setAccessible(true); - method.invoke(instance, (Object) null); - } catch (Exception ignored) {} - return "OK"; - } + public static String junrarHit() { + try { + Class cls = Class.forName("com.github.junrar.LocalFolderExtractor"); + Constructor ctor = cls.getDeclaredConstructors()[0]; + ctor.setAccessible(true); + Object instance = ctor.newInstance(new File("/tmp")); + Class fileHeaderClass = Class.forName("com.github.junrar.rarfile.FileHeader"); + Method method = cls.getDeclaredMethod("createDirectory", fileHeaderClass); + method.setAccessible(true); + method.invoke(instance, (Object) null); + } catch (InvocationTargetException e) { + if (!(e.getCause() instanceof NullPointerException)) { + throw new IllegalStateException("Unexpected exception from Junrar", e.getCause()); + } + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to invoke Junrar", e); + } + return "OK"; + } - public static String junrarHitAlt() { - try { - Class cls = Class.forName("com.github.junrar.LocalFolderExtractor"); - Constructor ctor = cls.getDeclaredConstructors()[0]; - ctor.setAccessible(true); - Object instance = ctor.newInstance(new File("/tmp")); - Class fileHeaderClass = Class.forName("com.github.junrar.rarfile.FileHeader"); - Method method = cls.getDeclaredMethod("createDirectory", fileHeaderClass); - method.setAccessible(true); - method.invoke(instance, (Object) null); - } catch (Exception ignored) {} - return "OK"; - } + // Keep the reflective invocation in each method: a shared helper would report the same caller + // symbol for both endpoints, preventing First_Hit_Wins from distinguishing the call sites. + public static String junrarHitAlt() { + try { + Class cls = Class.forName("com.github.junrar.LocalFolderExtractor"); + Constructor ctor = cls.getDeclaredConstructors()[0]; + ctor.setAccessible(true); + Object instance = ctor.newInstance(new File("/tmp")); + Class fileHeaderClass = Class.forName("com.github.junrar.rarfile.FileHeader"); + Method method = cls.getDeclaredMethod("createDirectory", fileHeaderClass); + method.setAccessible(true); + method.invoke(instance, (Object) null); + } catch (InvocationTargetException e) { + if (!(e.getCause() instanceof NullPointerException)) { + throw new IllegalStateException("Unexpected exception from Junrar", e.getCause()); + } + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to invoke Junrar", e); + } + return "OK"; + } }