Skip to content

feat(aap): In App WAF support#744

Merged
CarlesDD merged 23 commits into
mainfrom
ccapell/APPSEC-60752/in-app-waf-port
Jul 17, 2026
Merged

feat(aap): In App WAF support#744
CarlesDD merged 23 commits into
mainfrom
ccapell/APPSEC-60752/in-app-waf-port

Conversation

@CarlesDD

@CarlesDD CarlesDD commented Mar 16, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds AppSec support to the Lambda layer by extracting HTTP data from Lambda events and publishing it to diagnostic channels consumed by dd-trace-js's AppSec subsystem.

inappwafport

Changes:

  • Dockerfile + move script: move_ddtrace_dependency.js now reads @datadog/native-appsec from dd-trace's optionalDependencies and promotes it to a direct dependency so it survives --ignore-optional. The Dockerfile runs the script before rm -rf node_modules and strips unused native prebuilds (non-Linux-glibc platforms)
  • Event data extractor (src/appsec/event-data-extractor.ts): Parses API Gateway v1/v2, ALB, and Lambda Function URL events, extracting headers, method, path, query, body, client IP, path params, cookies, and route
  • Orchestrator (src/appsec/index.ts): Checks DD_APPSEC_ENABLED and publishes extracted data to datadog:lambda:start-invocation / datadog:lambda:end-invocation diagnostic channels
  • TraceListener integration: initAppsec() called in onStartInvocation, processAppSecRequest and processAppSecResponse called in onEndingInvocation

Motivation

Porting the In-App WAF security product to AWS Lambda for the Node.js runtime. The Lambda layer extracts HTTP data and dispatches it to the tracer for WAF execution and reporting.

The layer is intentionally kept thin, only extracting and publishing data. All security logic (WAF, reporting, trace keeping) lives in dd-trace-js.

Testing Guidelines

  • Unit tests for the event data extractor cover all 4 HTTP event types (API GW v1/v2, ALB, Lambda Function URL) and non-HTTP events
  • Unit tests for the orchestrator verify configuration gating and channel publishing
  • Existing listener tests pass unchanged

Additional Notes

  • @datadog/native-appsec is NOT added to this repo's package.json. The version is read dynamically from dd-trace's optionalDependencies at build time, so dd-trace-js remains the single owner of the native module version.
  • No new environment variables, using the existing DD_APPSEC_ENABLED.
  • This is a monitoring-only first iteration. Blocking, Remote Config, and telemetry are out of scope.
  • Companion PR in dd-trace-js: feat(aap): In App WAF support for lambda dd-trace-js#7783

Types of Changes

  • Bug fix
  • New feature
  • Breaking change
  • Misc (docs, refactoring, dependency upgrade, etc.)

Check all that apply

  • This PR's description is comprehensive
  • This PR contains breaking changes that are documented in the description
  • This PR introduces new APIs or parameters that are documented and unlikely to change in the foreseeable future
  • This PR impacts documentation, and it has been updated (or a ticket has been logged)
  • This PR's changes are covered by the automated tests
  • This PR collects user input/sensitive content into Datadog
  • This PR passes the integration tests (ask a Datadog member to run the tests)

APPSEC-60752

Comment thread scripts/move_ddtrace_dependency.js Outdated
Comment thread src/trace/listener.ts Outdated
Comment thread src/appsec/event-data-extractor.ts
Comment thread src/appsec/event-data-extractor.ts
@CarlesDD
CarlesDD force-pushed the ccapell/APPSEC-60752/in-app-waf-port branch from a9a47e9 to a79197a Compare April 21, 2026 15:06
Comment thread src/appsec/index.ts Outdated
@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jun 16, 2026

Copy link
Copy Markdown

Pipelines

Unblock PR with BitsAI

⚠️ Warnings

🚦 1 Pipeline job failed

DataDog/datadog-lambda-js | e2e-test-status   View in Datadog   GitLab

Useful? React with 👍 / 👎

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

@CarlesDD
CarlesDD marked this pull request as ready for review June 30, 2026 05:58
@CarlesDD
CarlesDD requested review from a team as code owners June 30, 2026 05:58
@CarlesDD
CarlesDD requested a review from shreyamalpani June 30, 2026 05:58
Comment thread src/trace/listener.ts Outdated
if (!this.tracerWrapper.currentSpan) return false;
this.wrappedCurrentSpan = new SpanWrapper(this.tracerWrapper.currentSpan, {});

processAppsecRequest(event, this.tracerWrapper.currentSpan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this happen at the beginning of the invocation? I'm wondering if this should be moved to onStartInvocation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. processAppsecRequest ran after the handler, too late to ever support blocking. Moved it to onRequestStart, called right before the handler runs, once the span is active. Couldnt be onStartInvocation itself since the span doesnt exist yet there.

Comment thread src/trace/listener.ts
* @param event
*/
public onRequestStart(event: any): void {
processAppsecRequest(event, this.tracerWrapper.currentSpan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason we can't just put this into onStartInvocation?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, there is. onStartInvocation runs before onWrap even creates the span (tracer.wrap() activates it later, wrapping the handler invocation itself), so currentSpan would be null there. onRequestStart is the earliest point where the span is guaranteed active, right before the handler runs.

Comment thread src/trace/listener.ts Outdated
}

public async onStartInvocation(event: any, context: Context) {
initAppsec();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like there's a this.config object available. Makes sense to add the appsec enabled check to the config.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added appsecEnabled to TraceConfig, resolved from DD_APPSEC_ENABLED in getConfig() like every other flag.

@CarlesDD
CarlesDD force-pushed the ccapell/APPSEC-60752/in-app-waf-port branch from a2945c1 to fde79ca Compare July 16, 2026 18:16
@CarlesDD
CarlesDD requested a review from purple4reina July 16, 2026 19:31
Comment thread src/trace/listener.ts Outdated
}
const responseStatusCode = result?.statusCode?.toString();
const responseHeaders = result?.headers as Record<string, string> | undefined;
processAppsecResponse(this.tracerWrapper.currentSpan, responseStatusCode, responseHeaders);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we move the is appsec enabled check to wrap this new block? That way none of it will execute if appsec isn't enabled. It will also make it easier to see on a quick read that this section is skipped in most cases.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit. I would make the signature of this method like

processAppsecResponse(this.tracerWrapper.currentSpan, result)

Do the extraction of the status code and headers inside the function, rather than before it. That keeps this already long onEndInvocation method neater and puts the extraction of the data closer to where you'll use it.

Comment thread src/trace/listener.ts Outdated
}

public async onStartInvocation(event: any, context: Context) {
initAppsec(this.config.appsecEnabled);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't seem necessary anymore now that we've moved the bool to the config. We should just use this.config.appsecEnabled around the processAppsecRequest and processAppsecResponse methods directly.

Comment thread src/appsec/index.ts Outdated
Comment on lines +9 to +13
let enabled = false;

export function initAppsec(appsecEnabled: boolean): void {
enabled = appsecEnabled;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think these lines can be removed as long as we do the appsec enabled check just before each process call.

@purple4reina purple4reina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a couple of small nitpicks. Otherwise, everything looks great.

@purple4reina

Copy link
Copy Markdown
Contributor

Next steps to merge:

  1. We'll need to increase the allowed binary size check since we're now bundling @datadog/native-appsec. We'll need to update the numbers here:

    MAX_LAYER_COMPRESSED_SIZE_KB=$(expr 7 \* 1024)
    MAX_LAYER_UNCOMPRESSED_SIZE_KB=$(expr 19 \* 1024)

  2. Once those tests are fixed, the e2e tests will run. We have tests already that check for appsec when DD_APPSEC_ENABLED=true. Currently, nodejs tests are marked as xfail, so this change should make them start passing. We have the tests configured to alert us when a test unexpectedly passes. So, we would expect the nodejs appsec in tracer tests to be listed as failures. If that makes any sense. I say just ping me when the e2e tests have finished running, before you merge, and I'll take a look.

@CarlesDD
CarlesDD merged commit 06813d2 into main Jul 17, 2026
41 of 42 checks passed
@CarlesDD
CarlesDD deleted the ccapell/APPSEC-60752/in-app-waf-port branch July 17, 2026 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants