Add HTTP span tags, content-type body parsing, and unsupported-event marking to AWS Lambda AppSec - #12078
Add HTTP span tags, content-type body parsing, and unsupported-event marking to AWS Lambda AppSec#12078claponcet wants to merge 6 commits into
Conversation
|
🎯 Code Coverage (details) 🔗 Commit SHA: 2ae5d3d | Docs | Datadog PR Page | Give us feedback! |
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
…marking to AWS Lambda AppSec Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c handler Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4fcba55 to
99f3883
Compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ae5d3d5d7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| String url = | ||
| (host != null && !host.isEmpty()) | ||
| ? URIUtils.buildURL(scheme, host, port, eventData.path) | ||
| : eventData.path; | ||
| span.setTag(Tags.HTTP_URL, url); |
There was a problem hiding this comment.
Strip raw queries before setting http.url
When a Lambda event's path already includes a query string (the request-side URI adapter supports and splits this case), this builds http.url from eventData.path verbatim, so a path like /login?password=hunter2 is stored directly in http.url instead of putting the query in http.query.string for QueryObfuscator to redact and append. This regresses the new query-less URL contract for those events; split eventData.path before building the span URL or reuse the same path/query parsing used for the AppSec URI adapter.
Useful? React with 👍 / 👎.
| int port = forwardedPort(eventData.headers); | ||
| String url = | ||
| (host != null && !host.isEmpty()) | ||
| ? URIUtils.buildURL(scheme, host, port, eventData.path) |
There was a problem hiding this comment.
Avoid appending a second port to Host
When Lambda/ALB forwards a Host header that already includes a port, such as api.example.com:8443, and also includes X-Forwarded-Port: 8443, this passes the raw Host plus the parsed forwarded port to buildURL, producing an invalid http.url like https://api.example.com:8443:8443/path. Strip any port from the Host value before passing a separate port, or skip the forwarded port when Host already carries one.
Useful? React with 👍 / 👎.
| if (!isSupportedHttpEvent(eventData)) { | ||
| span.setMetric(TAG_UNSUPPORTED_EVENT_TYPE, 1); | ||
| } else { | ||
| applyHttpSpanTags(span, eventData); |
There was a problem hiding this comment.
Set HTTP tags before requestEnded
For Lambda requests with API Security enabled, this applies the newly extracted http.route/http.url only after the requestEnded callback above has already run, but GatewayBridge.onRequestEnded samples API Security from spanInfo.getTags() and requires those tags for route/endpoint inference. As a result Lambda requests still won't be sampled for API Security based on the route/url added here; apply the HTTP span tags before invoking requestEndedCallback.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The new HTTP metadata is not faithful for common Lambda paths: API Gateway v2 repeated or encoded queries are rewritten, and handler exceptions bypass the only method that applies these tags. This leaves successful traces with corrupted URLs and failed invocations without request metadata.
📊 Validated against 6 scenarios · Open Bits AI session
🤖 Datadog Autotest · Commit 2ae5d3d · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| span.setTag(Tags.HTTP_URL, url); | ||
|
|
||
| if (Config.get().isHttpServerTagQueryString()) { | ||
| String query = buildQueryString(eventData.queryParameters); |
There was a problem hiding this comment.
Preserve API Gateway v2 raw queries
API Gateway v2 users receive semantically incorrect http.url values, breaking exact trace search/grouping and misrepresenting repeated parameters.
Assertion details
- Input: An API Gateway v2 event with rawQueryString=value=one&value=two and the AWS-flattened queryStringParameters value of one,two; encoded-space and encoded-slash variants trigger the same issue.
- Expected:
http.query.string, and therefore the query re-appended to http.url, should preserve the event's rawQueryString exactly. - Actual:
applyHttpSpanTags ignores rawQueryString and rebuilds the query from queryStringParameters, producing value=one%2Ctwo; it also canonicalizes value=hello%20world to value=hello+world and %2f to %2F. Fixing this requires carrying rawQueryString through LambdaEventData for v2 events, using it for HTTP tagging (and preferably the WAF URI), retaining map reconstruction only as a fallback for event formats without a raw field, and adding repeated/encoded-query cases to LambdaAppSecHandlerTest.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| int port = forwardedPort(eventData.headers); | ||
| String url = | ||
| (host != null && !host.isEmpty()) | ||
| ? URIUtils.buildURL(scheme, host, port, eventData.path) |
There was a problem hiding this comment.
Avoid duplicating ports in http.url
Requests through non-default listeners are tagged with malformed URLs, impairing URL-based trace filtering and grouping.
Assertion details
- Input: An ALB/API Gateway-style request with Host=api.example.com:8443 and X-Forwarded-Port=8443.
- Expected:
The span URL should be https://api.example.com:8443/pets/123. - Actual:
The Host value is passed unchanged to URIUtils.buildURL together with forwarded port 8443, producing https://api.example.com:8443:8443/pets/123. A complete fix must parse the Host authority before combining it with X-Forwarded-Port, including bracketed IPv6 handling, and add a host-with-port test; this is not a safe single-line replacement.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| if (!isSupportedHttpEvent(eventData)) { | ||
| span.setMetric(TAG_UNSUPPORTED_EVENT_TYPE, 1); | ||
| } else { | ||
| applyHttpSpanTags(span, eventData); |
There was a problem hiding this comment.
Finalize request metadata on handler errors
Every failed HTTP Lambda invocation lacks the request metadata needed to diagnose and group the error trace.
Assertion details
- Input: A recognized HTTP Lambda event whose handler throws before returning a response.
- Expected:
The errored serverless span should still receive http.method/http.url/http.route/http.useragent and the AppSec request should be finalized; only response processing should be skipped. - Actual:
All new HTTP tags are deferred to processRequestEnd, but LambdaHandlerInstrumentation calls notifyAppSecEnd only when throwable is null, so this method never runs for failed handlers. The complete fix spans the unchanged instrumentation and core API: separate response processing from request finalization or invoke request finalization from the throwable path, then extend the existing streaming-handler error test with HTTP input and tag assertions.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
What Does This Do
Extends the AWS Lambda AppSec request/response handling (
LambdaAppSecHandler) to close milestone-1 gaps against the Python (datadog-lambda-python) and extension implementations:http.url(query-less, soQueryObfuscatorcan redact and re-append it),http.route,http.useragent,http.method, andhttp.query.stringfrom the event at request-end, mirroringHttpServerDecorator(which Lambda has no equivalent of).http.routefrom API Gateway v1resourceand v2routeKey(method prefix stripped;$defaultand Function-URL routes omitted).application/x-www-form-urlencoded→ structured map, JSON content-types → parsed (malformed JSON dropped, matching the extension), missing content-type → best-effort JSON with raw-string fallback, everything else (incl. multipart) → raw string.extractLowercasedStringMaphelper._dd.appsec.unsupported_event_typemarker: set on the serverless span for non-HTTP events (parsed-but-unknown triggers and non-ByteArrayInputStream/POJO events), mutually exclusive with_dd.appsec.enabled— matching the cross-tracer system-test contract.domainNameon anhttp-shaped event now resolves toAPI_GATEWAY_V2_HTTPrather than defaulting toLAMBDA_URL(consistent with the Rust extension and Python layer).Motivation
Aligns Java Lambda AppSec behavior with the reference implementations so the
java_lambdasystem-test suite can close gaps currently markedmissing_featurerelative topython_lambda.Additional Notes
Behavior verified by unit tests in
LambdaAppSecHandlerTest(127 tests). Multipart bodies are intentionally forwarded as the raw string rather than structurally decomposed (the raw payload stays scannable by string-based WAF rules); structured multipart parsing is noted as follow-up work.Jira ticket: [APPSEC-XXXX]