Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 28 additions & 19 deletions datadog_lambda/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,34 @@ def _before(self, event, context):
def _after(self, event, context):
try:
status_code = extract_http_status_code_tag(self.trigger_tags, self.response)

if self.span:
if config.appsec_enabled and not self.blocking_response:
asm_start_response(
self.span,
status_code,
self.event_source,
response=self.response,
)
self.blocking_response = get_asm_blocked_response(self.event_source)

if self.blocking_response:
status_code = str(self.blocking_response.get("statusCode"))
if config.capture_payload_enabled and self.response:
tag_object(
self.span, "function.blocked_response", self.response
)
self.response = self.blocking_response

if config.capture_payload_enabled:
tag_object(self.span, "function.request", event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a request to change but I wonder if we should still tag the original self.response? maybe like tage it "function.original_request"? Does being blocked means we don't want the customer to see the original response whatsoever?

Copy link
Contributor Author

@florentinl florentinl Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't particularly want to hide it from the customer, the question is rather: would it be useful to have it ? The security signal will already have a detail of which security rule was matched to trigger the block. Having the response might be handy for debugging.

The request can be blocked at multiple moments. We would only have an "original response" when we block after looking at the response of the handler.
In this case, I can tag a "function.blocked_response" to link it more explicitly with the blocking feature.

tag_object(self.span, "function.response", self.response)

if status_code:
self.span.set_tag("http.status_code", status_code)

self.span.finish()

if status_code:
self.trigger_tags["http.status_code"] = status_code
mark_trace_as_error_for_5xx_responses(context, status_code, self.span)
Expand All @@ -298,25 +326,6 @@ def _after(self, event, context):
if should_trace_cold_start:
trace_ctx = tracer.current_trace_context()

if self.span:
if config.capture_payload_enabled:
tag_object(self.span, "function.request", event)
tag_object(self.span, "function.response", self.response)

if status_code:
self.span.set_tag("http.status_code", status_code)

if config.appsec_enabled and not self.blocking_response:
asm_start_response(
self.span,
status_code,
self.event_source,
response=self.response,
)
self.blocking_response = get_asm_blocked_response(self.event_source)

self.span.finish()

if self.inferred_span:
if status_code:
self.inferred_span.set_tag("http.status_code", status_code)
Expand Down
28 changes: 21 additions & 7 deletions tests/test_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,9 @@ def setUp(self):
self.mock_get_asm_blocking_response = patcher.start()
self.addCleanup(patcher.stop)

with open("tests/event_samples/api-gateway.json") as f:
self.api_gateway_request = json.loads(f.read())

self.fake_blocking_response = {
"statusCode": "403",
"headers": {
Expand All @@ -706,7 +709,7 @@ def test_blocking_before(self):

lambda_handler = wrapper.datadog_lambda_wrapper(mock_handler)

response = lambda_handler({}, get_mock_context())
response = lambda_handler(self.api_gateway_request, get_mock_context())
self.assertEqual(response, self.fake_blocking_response)

mock_handler.assert_not_called()
Expand All @@ -715,27 +718,31 @@ def test_blocking_before(self):
self.mock_asm_start_request.assert_called_once()
self.mock_asm_start_response.assert_not_called()

assert lambda_handler.span.get_tag("http.status_code") == "403"

def test_blocking_during(self):
self.mock_get_asm_blocking_response.return_value = None

@wrapper.datadog_lambda_wrapper
def lambda_handler(event, context):
self.mock_get_asm_blocking_response.return_value = (
self.fake_blocking_response
)
raise wrapper.BlockingException()

response = lambda_handler({}, get_mock_context())
lambda_handler = wrapper.datadog_lambda_wrapper(lambda_handler)

response = lambda_handler(self.api_gateway_request, get_mock_context())
self.assertEqual(response, self.fake_blocking_response)

self.mock_asm_set_context.assert_called_once()
self.mock_asm_start_request.assert_called_once()
self.mock_asm_start_response.assert_not_called()

assert lambda_handler.span.get_tag("http.status_code") == "403"

def test_blocking_after(self):
self.mock_get_asm_blocking_response.return_value = None

@wrapper.datadog_lambda_wrapper
def lambda_handler(event, context):
self.mock_get_asm_blocking_response.return_value = (
self.fake_blocking_response
Expand All @@ -745,13 +752,17 @@ def lambda_handler(event, context):
"body": "This should not be returned",
}

response = lambda_handler({}, get_mock_context())
lambda_handler = wrapper.datadog_lambda_wrapper(lambda_handler)

response = lambda_handler(self.api_gateway_request, get_mock_context())
self.assertEqual(response, self.fake_blocking_response)

self.mock_asm_set_context.assert_called_once()
self.mock_asm_start_request.assert_called_once()
self.mock_asm_start_response.assert_called_once()

assert lambda_handler.span.get_tag("http.status_code") == "403"

def test_no_blocking_appsec_disabled(self):
os.environ["DD_APPSEC_ENABLED"] = "false"

Expand All @@ -764,14 +775,17 @@ def test_no_blocking_appsec_disabled(self):
"body": "This should be returned",
}

@wrapper.datadog_lambda_wrapper
def lambda_handler(event, context):
return expected_response

response = lambda_handler({}, get_mock_context())
lambda_handler = wrapper.datadog_lambda_wrapper(lambda_handler)

response = lambda_handler(self.api_gateway_request, get_mock_context())
self.assertEqual(response, expected_response)

self.mock_get_asm_blocking_response.assert_not_called()
self.mock_asm_set_context.assert_not_called()
self.mock_asm_start_request.assert_not_called()
self.mock_asm_start_response.assert_not_called()

assert lambda_handler.span.get_tag("http.status_code") == "200"
Loading