Skip to content

Commit b828f70

Browse files
authored
chore(waf): fix block_config compatibility (#15208)
Adds a `.get()` method to the `Block_config` class to restore dictionary-like API compatibility broken by PR #15042, fixing Lambda integration failures. PR #15042 changed `Block_config` from a dictionary to a class, introducing a breaking API change that caused Lambda integration to fail with: ``` AttributeError: 'Block_config' object has no attribute 'get' Traceback (most recent call last): File "./python/lib/python3.13/site-packages/datadog_lambda/wrapper.py", line 187, in __call__ File "./python/lib/python3.13/site-packages/datadog_lambda/asm.py", line 220, in get_asm_blocked_response AttributeError: 'Block_config' object has no attribute 'get' ``` Rollback this ST PR after the merge DataDog/system-tests@f87f4dd APPSEC-59959
1 parent 9c7121e commit b828f70

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

ddtrace/appsec/_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@ def __init__(
173173
self.location = location.replace(APPSEC.SECURITY_RESPONSE_ID, security_response_id)
174174
self.content_type: str = "application/json"
175175

176+
def get(self, method_name: str, default: Any = None) -> Any:
177+
"""
178+
Dictionary-like get method for backward compatibility with Lambda integration.
179+
180+
Returns the attribute value if it exists, otherwise returns the default value.
181+
This allows Block_config to be used in contexts that expect dictionary-like access.
182+
"""
183+
return getattr(self, method_name, default)
184+
176185

177186
class Telemetry_result:
178187
__slots__ = [

tests/appsec/appsec/test_appsec_utils.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,114 @@ def test_get_blocked_template_user_file_exists_json():
8484
assert utils._get_blocked_template("text/html", BLOCK_ID) == utils._format_template(
8585
BLOCKED_RESPONSE_HTML, BLOCK_ID
8686
)
87+
88+
89+
def test_block_config_get_existing_attribute():
90+
"""
91+
Test that get() returns existing attribute values.
92+
93+
Regression test for PR #15042 which changed Block_config from dict to class,
94+
breaking Lambda integration that expects .get() method for dictionary-like access.
95+
"""
96+
from ddtrace.appsec._utils import Block_config
97+
98+
config = Block_config(status_code=403, type="auto", grpc_status_code=10)
99+
100+
assert config.get("status_code") == 403
101+
assert config.get("type") == "auto"
102+
assert config.get("grpc_status_code") == 10
103+
104+
105+
def test_block_config_get_nonexistent_attribute_default_none():
106+
"""Test that get() returns None for nonexistent attributes when no default is provided."""
107+
from ddtrace.appsec._utils import Block_config
108+
109+
config = Block_config()
110+
111+
assert config.get("nonexistent_field") is None
112+
assert config.get("missing_attribute") is None
113+
114+
115+
def test_block_config_get_nonexistent_attribute_with_custom_default():
116+
"""Test that get() returns custom default for nonexistent attributes."""
117+
from ddtrace.appsec._utils import Block_config
118+
119+
config = Block_config()
120+
121+
assert config.get("nonexistent_field", "default_value") == "default_value"
122+
assert config.get("missing_attribute", 123) == 123
123+
assert config.get("another_missing", False) is False
124+
125+
126+
def test_block_config_get_all_standard_attributes():
127+
"""Test that get() works for all standard Block_config attributes."""
128+
from ddtrace.appsec._utils import Block_config
129+
130+
config = Block_config(
131+
type="json",
132+
status_code=404,
133+
grpc_status_code=5,
134+
security_response_id="custom-block-id",
135+
location="/custom/location",
136+
)
137+
138+
# Test all standard attributes
139+
assert config.get("block_id") == "custom-block-id"
140+
assert config.get("status_code") == 404
141+
assert config.get("grpc_status_code") == 5
142+
assert config.get("type") == "json"
143+
assert config.get("content_type") == "application/json"
144+
# Location should have the security_response_id replaced
145+
assert "/custom/location" in config.get("location")
146+
147+
148+
def test_block_config_get_method_lambda_compatibility():
149+
"""
150+
Test Lambda integration compatibility scenario.
151+
152+
This simulates the actual error from Lambda where code expects
153+
dictionary-like access: block_config.get("key", default)
154+
155+
Reproduces the error:
156+
AttributeError: 'Block_config' object has no attribute 'get'
157+
"""
158+
from ddtrace.appsec._utils import Block_config
159+
160+
# Simulate Lambda's get_asm_blocked_response usage
161+
block_config = Block_config(
162+
type="auto",
163+
status_code=403,
164+
grpc_status_code=10,
165+
security_response_id="block-001",
166+
)
167+
168+
# Lambda code does things like: block_config.get("status_code", 403)
169+
status = block_config.get("status_code", 403)
170+
assert status == 403
171+
172+
# Lambda code might check for optional fields
173+
custom_field = block_config.get("custom_field", "default")
174+
assert custom_field == "default"
175+
176+
# Verify block_id is accessible
177+
block_id = block_config.get("block_id")
178+
assert block_id == "block-001"
179+
180+
181+
def test_block_config_get_preserves_attribute_access():
182+
"""Test that adding get() doesn't break normal attribute access."""
183+
from ddtrace.appsec._utils import Block_config
184+
185+
config = Block_config(status_code=500, type="html")
186+
187+
# Normal attribute access should still work
188+
assert config.status_code == 500
189+
assert config.type == "html"
190+
191+
# get() method should return the same values
192+
assert config.get("status_code") == 500
193+
assert config.get("type") == "html"
194+
195+
# Both access methods should return identical values
196+
assert config.status_code == config.get("status_code")
197+
assert config.type == config.get("type")

0 commit comments

Comments
 (0)