Skip to content

feat(aap): In App WAF support for lambda#7783

Open
CarlesDD wants to merge 10 commits into
masterfrom
ccapell/APPSEC-60752/in-app-waf-port
Open

feat(aap): In App WAF support for lambda#7783
CarlesDD wants to merge 10 commits into
masterfrom
ccapell/APPSEC-60752/in-app-waf-port

Conversation

@CarlesDD
Copy link
Copy Markdown
Contributor

@CarlesDD CarlesDD commented Mar 16, 2026

What does this PR do?

Adds AppSec support for AWS lambda to dd-trace-js by introducing DC handlers that allow the datadog-lambda-js layer to delegate WAF execution to the tracer.

inappwafport

Changes:

  • New datadog:lambda:start-invocation and datadog:lambda:end-invocation diagnostic channels with its subscribers (appsec/lambdja.js), which maps extracted HTTP data to WAF addresses, run the WAF and reports the results.
  • Reporter adaptation: reportMetrics, reportAttack, reportAttributes, and finishRequest now accept an optional rootSpan parameter, falling back to web.root(req) when not provided. This allows the Lambda handler to pass the span directly since there is no req object in Lambda
  • WAF rootSpan threading: waf/index.js and waf_context_wrapper.js thread rootSpan through the call chain so that keepTrace and reporter calls work correctly for Lambda

Motivation

Porting the In-App WAF security product to AWS Lambda for the Node.js runtime. The Lambda layer extracts HTTP data from the event and dispatches it to the tracer's AppSec domain via DC.

This approach keeps all security logic (WAF execution, attack reporting, trace keep decisions) inside dd-trace-js, and the Lambda layer is only responsible for extracting raw HTTP data and publishing it.

Additional Notes

  • The Lambda channels are always subscribed when AppSec is enabled. They are no-ops unless datadog-lambda-js publishes to them.
  • No new configuration is needed;DD_APPSEC_ENABLED env var controls everything.
  • Serverless defaults in dd-trace-js already neutralize telemetry, remote config, and error logging, so there are no side effects to enabling AppSec in Lambda beyond the WAF initialization cold start cost.
  • This is a monitoring-only first iteration. Blocking, Remote Config, and telemetry are out of scope.
  • Companion PR in datadog-lambda-js: feat(aap): In App WAF support datadog-lambda-js#744

APPSEC-60752

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 16, 2026

Overall package size

Self size: 5.56 MB
Deduped: 6.41 MB
No deduping: 6.41 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.0.1 | 82.56 kB | 817.39 kB | | dc-polyfill | 0.1.10 | 26.73 kB | 26.73 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.81%. Comparing base (31624d7) to head (590897a).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7783      +/-   ##
==========================================
+ Coverage   73.76%   73.81%   +0.04%     
==========================================
  Files         782      783       +1     
  Lines       36316    36384      +68     
==========================================
+ Hits        26790    26857      +67     
- Misses       9526     9527       +1     
Flag Coverage Δ
aiguard-macos 36.49% <0.00%> (-0.11%) ⬇️
aiguard-ubuntu 36.60% <0.00%> (-0.11%) ⬇️
aiguard-windows 36.40% <0.00%> (-0.11%) ⬇️
apm-capabilities-tracing-macos 47.89% <9.09%> (-0.14%) ⬇️
apm-capabilities-tracing-ubuntu-active 47.96% <9.09%> (-0.10%) ⬇️
apm-capabilities-tracing-ubuntu-latest 47.93% <9.09%> (-0.10%) ⬇️
apm-capabilities-tracing-ubuntu-maintenance 47.96% <9.09%> (-0.10%) ⬇️
apm-capabilities-tracing-ubuntu-oldest 47.95% <9.09%> (-0.10%) ⬇️
apm-capabilities-tracing-windows 47.61% <9.09%> (-0.24%) ⬇️
apm-integrations-child-process 36.15% <0.00%> (-0.12%) ⬇️
apm-integrations-couchbase-18 35.15% <0.00%> (-0.14%) ⬇️
apm-integrations-couchbase-eol 35.23% <0.00%> (-0.11%) ⬇️
apm-integrations-oracledb 35.20% <0.00%> (-0.11%) ⬇️
appsec-express 52.73% <35.00%> (-0.19%) ⬇️
appsec-fastify 49.22% <35.00%> (-0.18%) ⬇️
appsec-graphql 49.57% <32.50%> (-0.19%) ⬇️
appsec-kafka 42.09% <0.00%> (-0.10%) ⬇️
appsec-ldapjs 41.41% <0.00%> (-0.10%) ⬇️
appsec-lodash 41.44% <0.00%> (-0.10%) ⬇️
appsec-macos 57.10% <100.00%> (+0.12%) ⬆️
appsec-mongodb-core 45.74% <0.00%> (-0.09%) ⬇️
appsec-mongoose 46.62% <0.00%> (-0.09%) ⬇️
appsec-mysql 48.70% <32.50%> (-0.18%) ⬇️
appsec-node-serialize 40.61% <0.00%> (-0.10%) ⬇️
appsec-passport 44.50% <26.25%> (-0.20%) ⬇️
appsec-postgres 48.31% <32.50%> (-0.16%) ⬇️
appsec-sourcing 40.10% <0.00%> (-0.10%) ⬇️
appsec-stripe 42.24% <26.25%> (-0.19%) ⬇️
appsec-template 40.78% <0.00%> (-0.10%) ⬇️
appsec-ubuntu 57.18% <100.00%> (+0.12%) ⬆️
appsec-windows 57.00% <100.00%> (+0.14%) ⬆️
instrumentations-instrumentation-bluebird 29.84% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-body-parser 37.74% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-child_process 35.52% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-cookie-parser 31.76% <0.00%> (-0.10%) ⬇️
instrumentations-instrumentation-express 31.98% <0.00%> (-0.10%) ⬇️
instrumentations-instrumentation-express-mongo-sanitize 31.89% <0.00%> (-0.10%) ⬇️
instrumentations-instrumentation-express-session 37.37% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-fs 29.51% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-generic-pool 30.88% <ø> (ø)
instrumentations-instrumentation-http 36.98% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-knex 29.81% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-light-my-request 37.31% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-mongoose 30.90% <0.00%> (-0.10%) ⬇️
instrumentations-instrumentation-multer 37.51% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-mysql2 35.50% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-passport 41.15% <23.75%> (-0.20%) ⬇️
instrumentations-instrumentation-passport-http 40.85% <23.75%> (-0.19%) ⬇️
instrumentations-instrumentation-passport-local 41.35% <23.75%> (-0.20%) ⬇️
instrumentations-instrumentation-pg 35.02% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-promise 29.78% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-promise-js 29.78% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-q 29.81% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-url 29.77% <0.00%> (-0.11%) ⬇️
instrumentations-instrumentation-when 29.79% <0.00%> (-0.11%) ⬇️
llmobs-ai 38.47% <0.00%> (-0.11%) ⬇️
llmobs-anthropic 37.92% <0.00%> (-0.11%) ⬇️
llmobs-bedrock 37.15% <0.00%> (-0.10%) ⬇️
llmobs-google-genai 37.59% <0.00%> (-0.10%) ⬇️
llmobs-langchain 37.06% <0.00%> (-0.19%) ⬇️
llmobs-openai 41.29% <0.00%> (-0.10%) ⬇️
llmobs-vertex-ai 37.78% <0.00%> (-0.11%) ⬇️
platform-core 30.03% <ø> (ø)
platform-esbuild 32.83% <ø> (ø)
platform-instrumentations-misc 40.01% <ø> (ø)
platform-shimmer 35.72% <ø> (ø)
platform-unit-guardrails 31.39% <ø> (ø)
platform-webpack 20.78% <ø> (ø)
plugins-azure-durable-functions 25.36% <ø> (ø)
plugins-azure-event-hubs 25.51% <ø> (ø)
plugins-azure-service-bus 24.92% <ø> (ø)
plugins-bullmq 40.81% <0.00%> (-0.12%) ⬇️
plugins-cassandra 35.34% <0.00%> (-0.11%) ⬇️
plugins-cookie 26.47% <ø> (ø)
plugins-cookie-parser 26.28% <ø> (ø)
plugins-crypto 25.70% <ø> (ø)
plugins-dd-trace-api 35.46% <0.00%> (-0.34%) ⬇️
plugins-express-mongo-sanitize 26.42% <ø> (ø)
plugins-express-session 26.24% <ø> (ø)
plugins-fastify 39.36% <0.00%> (-0.11%) ⬇️
plugins-fetch 35.85% <0.00%> (-0.11%) ⬇️
plugins-fs 35.75% <0.00%> (-0.11%) ⬇️
plugins-generic-pool 25.40% <ø> (ø)
plugins-google-cloud-pubsub 43.11% <0.00%> (-0.10%) ⬇️
plugins-grpc 38.12% <0.00%> (-0.11%) ⬇️
plugins-handlebars 26.46% <ø> (ø)
plugins-hapi 37.36% <0.00%> (-0.11%) ⬇️
plugins-hono 37.61% <0.00%> (-0.11%) ⬇️
plugins-ioredis 35.80% <0.00%> (-0.11%) ⬇️
plugins-knex 26.14% <ø> (ø)
plugins-langgraph 35.09% <0.00%> (-0.11%) ⬇️
plugins-ldapjs 24.02% <ø> (ø)
plugins-light-my-request 25.88% <ø> (ø)
plugins-limitd-client 30.07% <0.00%> (-0.11%) ⬇️
plugins-lodash 25.47% <ø> (ø)
plugins-mariadb 36.67% <0.00%> (-0.11%) ⬇️
plugins-memcached 35.45% <0.00%> (-0.11%) ⬇️
plugins-microgateway-core 36.44% <0.00%> (-0.11%) ⬇️
plugins-modelcontextprotocol-sdk 34.38% <0.00%> (-0.11%) ⬇️
plugins-moleculer 38.14% <0.00%> (-0.11%) ⬇️
plugins-mongodb 36.61% <0.00%> (-0.11%) ⬇️
plugins-mongodb-core 36.24% <0.00%> (-0.11%) ⬇️
plugins-mongoose 36.11% <0.00%> (-0.11%) ⬇️
plugins-multer 26.24% <ø> (ø)
plugins-mysql 36.54% <0.00%> (-0.11%) ⬇️
plugins-mysql2 36.52% <0.00%> (-0.11%) ⬇️
plugins-node-serialize 26.51% <ø> (ø)
plugins-opensearch 35.10% <0.00%> (-0.11%) ⬇️
plugins-passport-http 26.30% <ø> (ø)
plugins-pino 31.88% <0.00%> (-0.11%) ⬇️
plugins-postgres 34.51% <0.00%> (-0.10%) ⬇️
plugins-process 25.70% <ø> (ø)
plugins-pug 26.47% <ø> (ø)
plugins-redis 36.00% <0.00%> (-0.11%) ⬇️
plugins-router 40.01% <0.00%> (-0.11%) ⬇️
plugins-sequelize 25.18% <ø> (ø)
plugins-test-and-upstream-amqp10 35.77% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-amqplib 40.95% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-apollo 36.60% <0.00%> (-0.10%) ⬇️
plugins-test-and-upstream-avsc 35.53% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-bunyan 31.23% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-connect 37.95% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-graphql 37.30% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-koa 37.56% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-protobufjs 35.75% <0.00%> (-0.11%) ⬇️
plugins-test-and-upstream-rhea 41.04% <0.00%> (-0.14%) ⬇️
plugins-undici 36.61% <0.00%> (-0.10%) ⬇️
plugins-url 25.70% <ø> (ø)
plugins-valkey 35.36% <0.00%> (-0.23%) ⬇️
plugins-vm 25.70% <ø> (ø)
plugins-winston 31.66% <0.00%> (-0.11%) ⬇️
plugins-ws 39.09% <0.00%> (-0.11%) ⬇️
profiling-macos 37.95% <0.00%> (-0.11%) ⬇️
profiling-ubuntu 38.11% <0.00%> (-0.11%) ⬇️
profiling-windows 39.47% <0.00%> (-0.11%) ⬇️
serverless-azure-functions-client 25.25% <ø> (ø)
serverless-azure-functions-eventhubs 25.25% <ø> (ø)
serverless-azure-functions-servicebus 25.25% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented Mar 16, 2026

Benchmarks

Benchmark execution time: 2026-04-27 08:30:00

Comparing candidate commit 590897a in PR branch ccapell/APPSEC-60752/in-app-waf-port with baseline commit 31624d7 in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 1344 metrics, 100 unstable metrics.

@datadog-official
Copy link
Copy Markdown

datadog-official Bot commented Mar 16, 2026

Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

🎯 Code Coverage (details)
Patch Coverage: 96.25%
Overall Coverage: 68.19% (+0.05%)

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

Comment thread packages/dd-trace/src/appsec/reporter.js Outdated
Comment on lines +340 to 343
function reportAttack ({ events: attackData, actions }, req, rootSpan) {
if (!req) {
req = storage('legacy').getStore()?.req
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i'm worried that in this function, if we don't pass the empty object fake "req", it will try to get it from the store, which would make this req variable here undefined, and crash later down the function because it's trying to access props in req (req.socket, req.body, etc).

The safety mecanism that was in place before was the rootSpan check. If req was missing, then rootSpan would also be missing, and thus the if(!rootSpan) return would protect us from undefined req.
But now that we can pass the rootSpan separately from req, this safety check is inoperant. Does that make sense ?

Can you prove to me that req can never be empty here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, your concern is legit: decoupling rootSpan from req breaks the implicit safety mechanism. But in the lambda path, req is never null or undefined in reportAttack (it's an invocationKey ({}), a truthy object flowing from lambda.js). The only place where req is null is in finishRequest.

I've added a test to check the WAF path with a strict req object that throws on any unaudited property access, making it fail if someone adds req.something without a guard in this path.

Comment thread packages/dd-trace/src/appsec/lambda.js
@simon-id
Copy link
Copy Markdown
Member

Overall this PR is a bit scary because it invalidates assumptions that were made during the entire evolution of our codebase. We should really be careful about these changes, a strong test suite, and proactive monitoring. For now I feel like the test suite is a bit light no ?

@CarlesDD CarlesDD force-pushed the ccapell/APPSEC-60752/in-app-waf-port branch from f4de885 to e0b39e1 Compare April 21, 2026 15:05
@CarlesDD
Copy link
Copy Markdown
Contributor Author

Overall this PR is a bit scary because it invalidates assumptions that were made during the entire evolution of our codebase. We should really be careful about these changes, a strong test suite, and proactive monitoring. For now I feel like the test suite is a bit light no ?

Agree this needs careful handling. I've pushed additional changes to strengthen the safety net:

  • More tests for Lambda handler
  • A proxy based WAF path safety test: it fails if anyone adds req.something with no guard in the future
  • Reporter lambda specific test: covering finishRequest and reportAttack with null/empty req
  • JSDocs to make the contract more explicit.

@CarlesDD CarlesDD force-pushed the ccapell/APPSEC-60752/in-app-waf-port branch from c83559f to 590897a Compare April 27, 2026 08:15
@CarlesDD CarlesDD marked this pull request as ready for review April 27, 2026 09:04
@CarlesDD CarlesDD requested a review from a team as a code owner April 27, 2026 09:04
Copy link
Copy Markdown

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

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: 590897a5f9

ℹ️ 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 thread packages/dd-trace/src/appsec/lambda.js
@CarlesDD CarlesDD requested a review from simon-id April 27, 2026 17:20
rootSpan = req && web.root(req)
}

if (!rootSpan) return
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (!rootSpan) return
if (!req || !rootSpan) return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from our previous thread #7783 (comment)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants