Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 352
Fix Jackson nodes introspection for request/response schema extraction#8980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,14 @@ | ||
| package com.datadog.appsec.event.data | ||
| import com.datadog.appsec.gateway.AppSecRequestContext | ||
| import com.fasterxml.jackson.databind.ObjectMapper | ||
| import com.fasterxml.jackson.databind.node.ArrayNode | ||
| import com.fasterxml.jackson.databind.node.ObjectNode | ||
| import datadog.trace.api.telemetry.WafMetricCollector | ||
| import datadog.trace.test.util.DDSpecification | ||
| import groovy.json.JsonBuilder | ||
| import groovy.json.JsonOutput | ||
| import groovy.json.JsonSlurper | ||
| import spock.lang.Shared | ||
| import java.nio.CharBuffer | ||
| @@ -14,6 +20,9 @@ class ObjectIntrospectionSpecification extends DDSpecification { | ||
| @Shared | ||
| protected static final ORIGINAL_METRIC_COLLECTOR = WafMetricCollector.get() | ||
| @Shared | ||
| protected static final MAPPER = new ObjectMapper() | ||
| AppSecRequestContext ctx = Mock(AppSecRequestContext) | ||
| WafMetricCollector wafMetricCollector = Mock(WafMetricCollector) | ||
| @@ -318,4 +327,152 @@ class ObjectIntrospectionSpecification extends DDSpecification { | ||
| 1 * wafMetricCollector.wafInputTruncated(true, false, false) | ||
| 1 * listener.onTruncation() | ||
| } | ||
| void 'jackson node types comprehensive coverage'() { | ||
| when: | ||
| final result = convert(input, ctx) | ||
| then: | ||
| result == expected | ||
| where: | ||
| input || expected | ||
| MAPPER.readTree('null') || null | ||
| MAPPER.readTree('true') || true | ||
| MAPPER.readTree('false') || false | ||
| MAPPER.readTree('42') || 42 | ||
| MAPPER.readTree('3.14') || 3.14 | ||
| MAPPER.readTree('"hello"') || 'hello' | ||
| MAPPER.readTree('[]') || [] | ||
| MAPPER.readTree('{}') || [:] | ||
| MAPPER.readTree('[1, 2, 3]') || [1, 2, 3] | ||
| MAPPER.readTree('{"key": "value"}') || [key: 'value'] | ||
| } | ||
| void 'jackson nested structures'() { | ||
| when: | ||
| final result = convert(input, ctx) | ||
| then: | ||
| result == expected | ||
| where: | ||
| input || expected | ||
| MAPPER.readTree('{"a": {"b": {"c": 123}}}') || [a: [b: [c: 123]]] | ||
| MAPPER.readTree('[[[1, 2]], [[3, 4]]]') || [[[1, 2]], [[3, 4]]] | ||
| MAPPER.readTree('{"arr": [1, null, true]}') || [arr: [1, null, true]] | ||
| MAPPER.readTree('[{"x": 1}, {"y": 2}]') || [[x: 1], [y: 2]] | ||
| } | ||
| void 'jackson edge cases'() { | ||
| when: | ||
| final result = convert(input, ctx) | ||
| then: | ||
| result == expected | ||
| where: | ||
| input || expected | ||
| MAPPER.readTree('""') || '' | ||
| MAPPER.readTree('0') || 0 | ||
| MAPPER.readTree('-1') || -1 | ||
| MAPPER.readTree('9223372036854775807') || 9223372036854775807L // Long.MAX_VALUE | ||
| MAPPER.readTree('1.7976931348623157E308') || 1.7976931348623157E308d // Double.MAX_VALUE | ||
| MAPPER.readTree('{"": "empty_key"}') || ['': 'empty_key'] | ||
| MAPPER.readTree('{"null_value": null}') || [null_value: null] | ||
| } | ||
| void 'jackson string truncation'() { | ||
| setup: | ||
| final longString = 'A' * (ObjectIntrospection.MAX_STRING_LENGTH + 1) | ||
| final jsonInput = '{"long": "' + longString + '"}' | ||
| when: | ||
| final result = convert(MAPPER.readTree(jsonInput), ctx) | ||
| then: | ||
| 1 * ctx.setWafTruncated() | ||
manuel-alvarez-alvarez marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| 1 * wafMetricCollector.wafInputTruncated(true, false, false) | ||
| result["long"].length() <= ObjectIntrospection.MAX_STRING_LENGTH | ||
| } | ||
| void 'jackson with deep nesting triggers depth limit'() { | ||
| setup: | ||
| // Create deeply nested JSON | ||
| final json = JsonOutput.toJson( | ||
| (1..(ObjectIntrospection.MAX_DEPTH + 1)).inject([:], { result, i -> [("child_$i".toString()) : result] }) | ||
| ) | ||
| when: | ||
| final result = convert(MAPPER.readTree(json), ctx) | ||
| then: | ||
| // Should truncate at max depth and set truncation flag | ||
| 1 * ctx.setWafTruncated() | ||
manuel-alvarez-alvarez marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| 1 * wafMetricCollector.wafInputTruncated(false, false, true) | ||
| countNesting(result as Map, 0) <= ObjectIntrospection.MAX_DEPTH | ||
| } | ||
| void 'jackson with large arrays triggers element limit'() { | ||
| setup: | ||
| // Create large array | ||
| final largeArray = (1..(ObjectIntrospection.MAX_ELEMENTS + 1)).toList() | ||
| final json = new JsonBuilder(largeArray).toString() | ||
| when: | ||
| final result = convert(MAPPER.readTree(json), ctx) as List | ||
| then: | ||
| // Should truncate and set truncation flag | ||
| 1 * ctx.setWafTruncated() | ||
manuel-alvarez-alvarez marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| 1 * wafMetricCollector.wafInputTruncated(false, true, false) | ||
| result.size() <= ObjectIntrospection.MAX_ELEMENTS | ||
| } | ||
| void 'jackson number type variations'() { | ||
| when: | ||
| final result = convert(input, ctx) | ||
| then: | ||
| result == expected | ||
| where: | ||
| input || expected | ||
| MAPPER.readTree('0') || 0 | ||
| MAPPER.readTree('1') || 1 | ||
| MAPPER.readTree('-1') || -1 | ||
| MAPPER.readTree('1.0') || 1.0 | ||
| MAPPER.readTree('1.5') || 1.5 | ||
| MAPPER.readTree('-1.5') || -1.5 | ||
| MAPPER.readTree('1e10') || 1e10 | ||
| MAPPER.readTree('1.23e-4') || 1.23e-4 | ||
| } | ||
| void 'jackson special string values'() { | ||
| when: | ||
| final result = convert(input, ctx) | ||
| then: | ||
| result == expected | ||
| where: | ||
| input || expected | ||
| MAPPER.readTree('"\\n"') || '\n' | ||
| MAPPER.readTree('"\\t"') || '\t' | ||
| MAPPER.readTree('"\\r"') || '\r' | ||
| MAPPER.readTree('"\\\\"') || '\\' | ||
| MAPPER.readTree('"\\"quotes\\""') || '"quotes"' | ||
| MAPPER.readTree('"unicode: \\u0041"') || 'unicode: A' | ||
| } | ||
| private static int countNesting(final Map<String, Object>object, final int levels) { | ||
| if (object.isEmpty()) { | ||
| return levels | ||
| } | ||
| final child = object.values().first() | ||
| if (child == null) { | ||
| return levels | ||
| } | ||
| return countNesting(object.values().first() as Map, levels + 1) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have a comment to remind us that this could happens with other data structures that need to be sent to the WAF