Skip to content

Add HTTP span tags, content-type body parsing, and unsupported-event marking to AWS Lambda AppSec - #12078

Open
claponcet wants to merge 6 commits into
masterfrom
clara.poncet/lambda-appsec-milestone1-gaps
Open

Add HTTP span tags, content-type body parsing, and unsupported-event marking to AWS Lambda AppSec#12078
claponcet wants to merge 6 commits into
masterfrom
clara.poncet/lambda-appsec-milestone1-gaps

Conversation

@claponcet

Copy link
Copy Markdown
Contributor

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 span tags: derives http.url (query-less, so QueryObfuscator can redact and re-append it), http.route, http.useragent, http.method, and http.query.string from the event at request-end, mirroring HttpServerDecorator (which Lambda has no equivalent of).
  • Route extraction: http.route from API Gateway v1 resource and v2 routeKey (method prefix stripped; $default and Function-URL routes omitted).
  • Content-type body parsing: strict dispatch — 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.
  • Case-insensitive headers: request header keys are lowercased (as response headers already were), via a shared extractLowercasedStringMap helper.
  • _dd.appsec.unsupported_event_type marker: 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.
  • Trigger detection: a missing/non-string domainName on an http-shaped event now resolves to API_GATEWAY_V2_HTTP rather than defaulting to LAMBDA_URL (consistent with the Rust extension and Python layer).

Motivation

Aligns Java Lambda AppSec behavior with the reference implementations so the java_lambda system-test suite can close gaps currently marked missing_feature relative to python_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]

@claponcet claponcet added type: feature Enhancements and improvements inst: aws lambda AWS Lambda instrumentation tag: ai generated Largely based on code generated by an AI or LLM labels Jul 27, 2026
@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Jul 27, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 81.75%
Overall Coverage: 58.04% (+0.26%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 2ae5d3d | Docs | Datadog PR Page | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.86 s 14.84 s [-0.9%; +1.3%] (no difference)
startup:insecure-bank:tracing:Agent 13.66 s 13.67 s [-0.8%; +0.7%] (no difference)
startup:petclinic:appsec:Agent 16.33 s 16.71 s [-6.6%; +2.1%] (no difference)
startup:petclinic:iast:Agent 16.80 s 16.87 s [-1.1%; +0.3%] (no difference)
startup:petclinic:profiling:Agent 16.79 s 16.79 s [-1.1%; +1.1%] (no difference)
startup:petclinic:sca:Agent 16.70 s 15.44 s [+2.1%; +14.1%] (unstable)
startup:petclinic:tracing:Agent 16.02 s 16.15 s [-1.9%; +0.3%] (no difference)

Commit: 2ae5d3d5 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

claponcet and others added 3 commits July 29, 2026 17:36
…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>
@claponcet
claponcet force-pushed the clara.poncet/lambda-appsec-milestone1-gaps branch from 4fcba55 to 99f3883 Compare July 29, 2026 15:44
@claponcet
claponcet marked this pull request as ready for review July 30, 2026 17:14
@claponcet
claponcet requested review from a team as code owners July 30, 2026 17:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +220 to +224
String url =
(host != null && !host.isEmpty())
? URIUtils.buildURL(scheme, host, port, eventData.path)
: eventData.path;
span.setTag(Tags.HTTP_URL, url);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +219 to +222
int port = forwardedPort(eventData.headers);
String url =
(host != null && !host.isEmpty())
? URIUtils.buildURL(scheme, host, port, eventData.path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@datadog-prod-us1-3 datadog-prod-us1-3 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Datadog Autotest: FAIL

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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

inst: aws lambda AWS Lambda instrumentation tag: ai generated Largely based on code generated by an AI or LLM type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant