Skip to content

feat(appsec): upgrade to go-libddwaf v5 with subcontext-based ephemeral runs#4938

Merged
darccio merged 25 commits into
mainfrom
eliottness/go-libdwaf-v5
Jul 1, 2026
Merged

feat(appsec): upgrade to go-libddwaf v5 with subcontext-based ephemeral runs#4938
darccio merged 25 commits into
mainfrom
eliottness/go-libdwaf-v5

Conversation

@eliottness

@eliottness eliottness commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What & why

Upgrades dd-trace-go from go-libddwaf/v4 v4.10.0 to go-libddwaf/v5 v5.0.0-rc.2 across the root module and all nested modules. v5 removed the per-run Persistent/Ephemeral data channel, so previously-ephemeral WAF evaluations (RASP LFI/SQLi/SSRF/CMDi, gRPC request/response messages, GraphQL resolvers) now run on v5 subcontexts. Persistent runs stay on the main Context.

Design

  • SubcontextOperation (internal/appsec/emitter/waf/subcontext.go) wraps *libddwaf.Subcontext. Constructed via ContextOperation.NewSubcontextOp(); Run/Close are nil-safe no-ops when the WAF context is closed.
  • No Ephemeral flag. Dispatch is explicit at the call site: RunSimple (persistent → Context) vs RunSimpleSubcontext (ephemeral → Subcontext). addresses.RunAddressData is now a type alias of libddwaf.RunAddressData (Data + TimerKey).
  • Metrics via the library. rc.2's Subcontext.Close()/Context.Close() fold per-scope timer durations + truncations into the parent Context, so ContextMetrics no longer keeps a manual subcontext accumulator — onFinish reads ctx.Timer.Stats()/ctx.Truncations() which already include subcontext data.
  • libddwaf.Runner is used for the shared run helper (no local interface).
  • No dyngo core changes; ephemeral runs go through SubcontextOperation.Run, not a new dispatch path.

Decisions

  • D1=B: an SSRF round trip shares one subcontext across its request + response runs (validated by a spike: v5 does not re-fire the SSRFRequest rule on the response run; encoded as a permanent regression test).
  • D2=A: context.Background() throughout (no request-context threading into the WAF).
  • D3: tests-after + new SubcontextOperation coverage (ephemeral routing, close/no-leak, SSRF freshness, unsupported-address filtering, nil-context safety, idempotent Close).

Notable fixes (from review)

  • Keep blocking/derivatives when the metrics instance is nil (previously an early-return dropped blocking).
  • defer the round-trip subcontext Close; defer the read-lock release in WAFManager.NewHandle.
  • Obfuscator config nested under obfuscator so v5 redaction actually applies (end-to-end redaction test).
  • tools/v2fix go.mod tidied (go-libddwaf v4 indirect) so make fix-modules is a no-op.

Verification

  • go build ./... (root + internal/orchestrion/_integration) clean; make fix-modules no-op; make generate clean.
  • make test/appsec + go test ./internal/appsec/... green, including the new SubcontextOperation coverage.
  • golangci-lint clean on the changed appsec packages.
  • Zero go-libddwaf/v4 (outside the v2fix dev tool) and zero rc.1 references.

🤖 Generated with assistance; please review.

Mechanical import-path rewrite from go-libddwaf/v4 to /v5 across the root
module (incl. /timer, /waferrors subpackages). API breakages introduced by
v5 are intentionally left for subsequent commits in this series.
…waf v5

The flat {key_regex,value_regex} shape under obfuscator/config did not apply
redaction in v5; the C WAF expects the config nested under an "obfuscator"
key. Verified by the end-to-end TestWAF/obfuscation redaction assertion.
- wafmanager: wrap WAF build error with telemetrylog.NewSafeError (gocritic)
- metrics: simplify composite literals in subcontext duration map (gofmt)
- roundtripper subcontext test: justified //nolint:bodyclose (false positive)
@eliottness eliottness requested review from a team as code owners June 24, 2026 11:26
@github-actions github-actions Bot added the apm:ecosystem contrib/* related feature requests or bugs label Jun 24, 2026
@datadog-official

datadog-official Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 2 Pipeline jobs failed

Check Pull Request CI Status | all-jobs-are-green   View in Datadog   GitHub Actions

DataDog/apm-reliability/dd-trace-go | pr-performance-gates   View in Datadog   GitLab

❄️ 5 New flaky tests detected

TestProtectRoundTrip_closes_shared_subcontext_when_ssrf_request_blocks from github.com/DataDog/dd-trace-go/v2/instrumentation/appsec/emitter/httpsec   View in Datadog
Failed

=== RUN   TestProtectRoundTrip_closes_shared_subcontext_when_ssrf_request_blocks
    roundtripper_subcontext_test.go:32: 
        	Error Trace:	D:/a/dd-trace-go/dd-trace-go/instrumentation/appsec/emitter/httpsec/roundtripper_subcontext_test.go:70
        	            				D:/a/dd-trace-go/dd-trace-go/instrumentation/appsec/emitter/httpsec/roundtripper_subcontext_test.go:32
        	Error:      	Received unexpected error:
        	            	failed to load WAF library while creating builder: unsupported OS/Arch: windows/amd64
        	            	the WAF has been manually disabled using the \`datadog.no_waf\` go build tag
        	Test:       	TestProtectRoundTrip_closes_shared_subcontext_when_ssrf_request_blocks
...

New test introduced in this PR is flaky.

TestSubcontextOperation_aggregates_truncations_and_external_rasp_timer from github.com/DataDog/dd-trace-go/v2/internal/appsec/emitter/waf   View in Datadog
Failed

=== RUN   TestSubcontextOperation_aggregates_truncations_and_external_rasp_timer
    subcontext_test.go:55: 
        	Error Trace:	D:/a/dd-trace-go/dd-trace-go/internal/appsec/emitter/waf/subcontext_test.go:153
        	            				D:/a/dd-trace-go/dd-trace-go/internal/appsec/emitter/waf/subcontext_test.go:132
        	            				D:/a/dd-trace-go/dd-trace-go/internal/appsec/emitter/waf/subcontext_test.go:55
        	Error:      	Received unexpected error:
        	            	failed to load WAF library while creating builder: unsupported OS/Arch: windows/amd64
        	            	the WAF has been manually disabled using the \`datadog.no_waf\` go build tag
...

New test introduced in this PR is flaky.

View in Flaky Test Management

ℹ️ Info

No other issues found (see more)

🧪 All tests passed

🎯 Code Coverage (details)
Patch Coverage: 39.38%
Overall Coverage: 62.99% (-3.46%)

Useful? React with 👍 / 👎

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

@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: 57af60e62d

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +56
if metrics := sub.contextOp.GetMetricsInstance(); metrics != nil {
metrics.AddSubcontextStats(sub.scope, sub.subcontext.Truncations(), sub.subcontext.Timer.Stats())

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 Preserve per-scope subcontext timer stats

When a shared subcontext is used for more than one run, this records only externalTimerStats[sub.scope] for the last run's scope. In the real HTTP round-tripper path, a request-side SSRF block now calls op.Finish(RoundTripOperationRes{}), which fires the response finish listener with empty WAF-scope data before Close; that overwrites the request run's RASP scope, so the accumulated RASP subcontext duration is not added to rasp.duration_ext and may be reported under WAF or dropped. Aggregate all scopes present in subcontext.Timer.Stats() instead of using a single mutable sub.scope.

Useful? React with 👍 / 👎.

Comment thread instrumentation/appsec/emitter/httpsec/roundtripper.go
Comment thread instrumentation/appsec/emitter/waf/addresses/builder.go Outdated
Comment thread instrumentation/appsec/emitter/waf/addresses/builder.go Outdated
Comment thread instrumentation/appsec/emitter/waf/addresses/builder.go Outdated

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.

The new file is unnecessary, merge it into another test file

Comment thread internal/appsec/emitter/waf/run.go Outdated
Comment on lines +67 to +69
if metrics == nil {
return
}

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.

This one is very problematic

Comment thread internal/appsec/emitter/waf/run.go Outdated
Comment thread internal/appsec/emitter/waf/subcontext.go
Comment thread internal/appsec/emitter/waf/subcontext_test.go
Comment thread internal/appsec/listener/waf/waf.go
@pr-commenter

pr-commenter Bot commented Jun 24, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-06-24 13:36:47

Comparing candidate commit 5c54ac7 in PR branch eliottness/go-libdwaf-v5 with baseline commit baa5573 in branch main.

Found 1 performance improvements and 5 performance regressions! Performance is the same for 291 metrics, 2 unstable metrics, 1 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:BenchmarkOTLPProtoSize/1000spans

  • 🟥 execution_time [+8.178µs; +9.033µs] or [+2.499%; +2.761%]

scenario:BenchmarkOTLPProtoSize/100spans

  • 🟥 execution_time [+1.844µs; +1.987µs] or [+5.811%; +6.263%]

scenario:BenchmarkOTLPProtoSize/10spans

  • 🟥 execution_time [+203.707ns; +213.693ns] or [+6.292%; +6.601%]

scenario:BenchmarkOTLPProtoSize/1span

  • 🟥 execution_time [+30.960ns; +32.820ns] or [+7.964%; +8.443%]

scenario:BenchmarkSampleWAFContext

  • 🟥 allocations [+6; +6] or [+3.681%; +3.681%]
  • 🟩 execution_time [-17.776µs; -17.058µs] or [-14.433%; -13.850%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

Known flaky benchmarks without significant changes:

  • scenario:BenchmarkOTLPTraceWriterFlush

eliottness and others added 9 commits June 24, 2026 14:37
…dressData

Replace the per-run Ephemeral boolean dispatch with explicit entry points:
RunSimple (persistent -> request Context) and RunSimpleSubcontext (ephemeral
-> short-lived Subcontext). gRPC message monitors now call RunSimpleSubcontext;
HTTP body monitors stay on RunSimple.

addresses.RunAddressData becomes a type alias of libddwaf.RunAddressData
(Data + TimerKey), dropping the Ephemeral field, the hasPersistent/hasEphemeral
bookkeeping, the Build() mix panic, and ToLibddwaf(). RASP rule-type detection
keys off TimerKey==RASPScope. skipRASPRuleAfterRequest now lives only on the
subcontext path, since RASP always runs in subcontexts.

Addresses PR review feedback (remove Ephemeral; embed/alias RunAddressData;
RunSimpleSubcontext; relocate RASP skip). Tests updated to the new API and
guarded with libddwaf.Usable(); round-trip subcontext test merged into
roundtripper_test.go.
runWAF returned early on a nil metrics instance before sending action events,
absorbing derivatives, applying Keep, and emitting the security event - so a
nil metrics instance silently dropped blocking. Reorder so action/blocking/
derivative handling always runs and only the metrics calls are guarded.
A shared subcontext (SSRF round trip) recorded only the last run's scope via a
single mutable field, so a WAF-scope response finish on the block path dropped
the RASP external duration. AddSubcontextStats now iterates the known scopes and
attributes each from the subcontext timer stats. Adds a regression test plus
nil-context and idempotent-Close coverage.
…RUnlock

Guarantee the shared round-trip subcontext is closed even if FinishOperation
panics, and defer the read lock release in WAFManager.NewHandle.
The v2fix dev tool transitively requires go-libddwaf/v4; commit the indirect
require so 'make fix-modules' is a no-op and the check-modules CI gate passes.
The tracer modules themselves remain v5-only.
Signed-off-by: Eliott Bouhana <eliott.bouhana@datadoghq.com>
… Runner

rc.2 folds a subcontext's per-scope timer durations and truncations into the
parent Context on Close, and exports libddwaf.Runner. Drop the manual
accumulator (AddSubcontextStats, subcontextExternalDurations, MergedTruncations,
ExternalDuration and the Submit subcontext loop) and the local wafRunner
interface; onFinish now reads ctx.Timer.Stats()/ctx.Truncations() which already
include subcontext contributions. Tests assert via the rolled-up Context.
make fix-modules + orchestrion _integration direct bump to rc.2. Indirect
version/go.sum updates across all modules and go.work.sum.
@eliottness eliottness requested a review from a team as a code owner June 29, 2026 13:04
Comment thread .github/workflows/apps/go.mod Outdated
github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.79.0 // indirect
github.com/DataDog/datadog-go/v5 v5.8.3 // indirect
github.com/DataDog/go-libddwaf/v4 v4.10.0 // indirect
github.com/DataDog/go-libddwaf/v5 v5.0.0-rc.1 // indirect

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 guess before merging it might be nice to release the v5.0.0 tag

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.

I want to dogfood the PR before doing the final release. I will tag go-libddwaf@v5.0.0 after dd-trace-go@v2.10.0-rc.1 is published and dogfooded

@eliottness

Copy link
Copy Markdown
Contributor Author

Benchmark analysis

I investigated every benchmark flagged by the benchmarking platform (candidate 5c54ac7 @ go-libddwaf v5.0.0-rc.1 vs baseline baa5573 @ v4.10.0). The 5 reported regressions are really 1 genuine — and net-positive — change plus 4 measurement artifacts.

Benchmark Platform report (linux/amd64) Verdict
BenchmarkOTLPProtoSize/1span execution_time +7.96%…+8.44% 🟦 Code-layout artifact, not a real regression
BenchmarkOTLPProtoSize/10spans execution_time +6.29%…+6.60% 🟦 Code-layout artifact
BenchmarkOTLPProtoSize/100spans execution_time +5.81%…+6.26% 🟦 Code-layout artifact
BenchmarkOTLPProtoSize/1000spans execution_time +2.50%…+2.76% 🟦 Code-layout artifact
BenchmarkSampleWAFContext allocations +6 (+3.68%) 🟩 Real, inseparable from the −14% speedup below
BenchmarkSampleWAFContext execution_time −13.85%…−14.43% 🟩 Real improvement
BenchmarkOTLPTraceWriterFlush flaky, no significant change ⬜ Ignore (already flagged flaky)

1. BenchmarkOTLPProtoSize/* — not a real regression (binary-layout / measurement bias)

The timed loop only calls proto.Size(tracesData). A real algorithmic regression here is physically impossible, because the OTLP serialization code did not change:

  • Pure-CPU, zero-allocation0 B/op, 0 allocs/op in both builds (the report only ever flags execution_time, never allocations), so GC/heap pressure cannot be the mechanism.

  • Serialization deps unchangedgo.sum shows the only dependency change is go-libddwaf v4→v5; google.golang.org/protobuf (v1.36.11) and go.opentelemetry.io/proto/otlp (v1.10.0) are untouched.

  • Benchmark source unchanged.

  • Binary-level proof. ddtrace/tracer transitively links go-libddwaf (via internal/appsec), so swapping v4→v5 changes the linked image and relocates everything. I built both test binaries (identical toolchain go1.26.4, same flags CGO_ENABLED=1/-buildmode=exe) and diffed the compiled protobuf hot path with go tool objdump:

    function instr count (v4 / v5) result
    proto.(*MarshalOptions).Size 32 / 32 identical stream
    impl.(*MessageInfo).sizePointer 276 / 276 identical stream
    impl.(*MessageInfo).sizePointerSlow 224 / 224 identical stream
    impl.sizeMessageInfo 32 / 32 identical stream

    The instruction + source-line streams are identical; the only differences are pure relocations (e.g. proto.Size moved 0x10046d6100x10046d790; an ADRP data reference and the CALL runtime.morestack displacement shift accordingly). Same code — just placed a few hundred bytes later.

  • Does not reproduce off-amd64. Interleaved A/B (n=12, benchstat) on an Apple M4 Max (darwin/arm64) shows no regression — the candidate is marginally faster:

    OTLPProtoSize/1span-16       234.2n ± 1%   234.2n ± 2%       ~ (p=0.702)
    OTLPProtoSize/10spans-16     1.903µ ± 1%   1.872µ ± 2%  -1.63% (p=0.037)
    OTLPProtoSize/100spans-16    18.45µ ± 1%   18.27µ ± 2%       ~ (p=0.410)
    OTLPProtoSize/1000spans-16   182.9µ ± 2%   181.8µ ± 1%       ~ (p=0.114)
    geomean                      6.227µ        6.178µ       -0.79%
    

This is textbook layout-induced measurement bias (Mytkowicz et al., "Producing Wrong Data Without Doing Anything Obviously Wrong!", ASPLOS 2009): relinking unrelated code shifts the alignment of a tiny zero-alloc loop, which amd64 front-ends (uop cache / 16–32B fetch windows) are sensitive to. Corroborating signs: BenchmarkOTLPProtoMarshal (same package, calls Size internally) was not flagged, and the relative effect shrinks as the workload grows (8.4% @ 1span → 2.6% @ 1000spans), i.e. a fixed front-door perturbation diluted by real work. It carries no production cost and will drift/vanish on the next unrelated commit.

2. BenchmarkSampleWAFContext — the +6 allocations and the −14% are the same change (net win)

This benchmark drives the go-libddwaf v5 API directly (NewContext + 2× Run + Close). Both reported items come from one design change: v5's per-scope timer model. The candidate adds timer.WithComponents(addresses.Scopes[:]...) (2 scopes: rasp, waf) and the new RunAddressData{Data, Scope} model, replacing v4's single budget timer + Persistent/Ephemeral channel. That per-scope timing is exactly what makes the run faster — so the handful of extra allocations is the cost of a ~14–19% speedup. Reproduced on arm64:

SampleWAFContext-16   sec/op:  66.12µ → 53.42µ   -19.20% (p=0.000)
SampleWAFContext-16   B/op:    9.425Ki → 9.230Ki  -2.07% (p=0.000)   ← bytes go DOWN
SampleWAFContext-16   allocs:  162 → 167          +3.09% (p=0.000)   ← +5 (report: +6)

Faster, fewer bytes, +5/6 small allocations — inherent to the go-libddwaf v5 API and a favorable trade.


Bottom line

Nothing here needs fixing. The WAF result is a net improvement, and the OTLP items are alignment/measurement bias (proto.Size is provably the same machine code, doesn't reproduce off-amd64). If the OTLP noise on future reports is bothersome, the realistic lever is marking BenchmarkOTLPProtoSize flaky rather than chasing a non-existent code regression.

Methodology: clean-worktree A/B of the exact report commits, go test -c binaries run interleaved with benchstat (n=12); machine-code comparison via go tool nm / go tool objdump. Repro host is darwin/arm64 (Apple M4 Max); the platform runs linux/amd64, which is the crux of the OTLP finding.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Config Audit

PACKAGE: contrib/confluentinc/confluent-kafka-go/kafkatrace
  STATUS      CONFIG                            CALL_SITES
  UNMIGRATED  DD_TRACE_KAFKA_ANALYTICS_ENABLED  1

PACKAGE: ddtrace/opentelemetry/log
  STATUS      CONFIG                            CALL_SITES
  STILL_READ  DD_AGENT_HOST                     2
  STILL_READ  DD_ENV                            1
  STILL_READ  DD_SERVICE                        1
  STILL_READ  DD_TAGS                           1
  STILL_READ  DD_TRACE_AGENT_URL                2
  STILL_READ  DD_TRACE_REPORT_HOSTNAME          1
  STILL_READ  DD_VERSION                        1
  UNMIGRATED  DD_HOSTNAME                       1
  UNMIGRATED  OTEL_BLRP_EXPORT_TIMEOUT          1
  UNMIGRATED  OTEL_BLRP_MAX_EXPORT_BATCH_SIZE   1
  UNMIGRATED  OTEL_BLRP_MAX_QUEUE_SIZE          1
  UNMIGRATED  OTEL_BLRP_SCHEDULE_DELAY          1
  UNMIGRATED  OTEL_EXPORTER_OTLP_ENDPOINT       4
  UNMIGRATED  OTEL_EXPORTER_OTLP_HEADERS        2
  UNMIGRATED  OTEL_EXPORTER_OTLP_LOGS_ENDPOINT  4
  UNMIGRATED  OTEL_EXPORTER_OTLP_LOGS_HEADERS   2
  UNMIGRATED  OTEL_EXPORTER_OTLP_LOGS_PROTOCOL  2
  UNMIGRATED  OTEL_EXPORTER_OTLP_LOGS_TIMEOUT   1
  UNMIGRATED  OTEL_EXPORTER_OTLP_PROTOCOL       2
  UNMIGRATED  OTEL_EXPORTER_OTLP_TIMEOUT        1
  UNMIGRATED  OTEL_RESOURCE_ATTRIBUTES          1

PACKAGE: ddtrace/opentelemetry/metric
  STATUS      CONFIG                                             CALL_SITES
  STILL_READ  DD_AGENT_HOST                                      2
  STILL_READ  DD_ENV                                             1
  STILL_READ  DD_METRICS_OTEL_ENABLED                            1
  STILL_READ  DD_SERVICE                                         1
  STILL_READ  DD_TAGS                                            1
  STILL_READ  DD_TRACE_AGENT_URL                                 2
  STILL_READ  DD_TRACE_REPORT_HOSTNAME                           1
  STILL_READ  DD_VERSION                                         1
  STILL_READ  OTEL_METRICS_EXPORTER                              1
  UNMIGRATED  DD_HOSTNAME                                        1
  UNMIGRATED  OTEL_EXPORTER_OTLP_ENDPOINT                        2
  UNMIGRATED  OTEL_EXPORTER_OTLP_HEADERS                         1
  UNMIGRATED  OTEL_EXPORTER_OTLP_METRICS_ENDPOINT                2
  UNMIGRATED  OTEL_EXPORTER_OTLP_METRICS_HEADERS                 1
  UNMIGRATED  OTEL_EXPORTER_OTLP_METRICS_PROTOCOL                2
  UNMIGRATED  OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE  1
  UNMIGRATED  OTEL_EXPORTER_OTLP_PROTOCOL                        2
  UNMIGRATED  OTEL_EXPORTER_OTLP_TIMEOUT                         1
  UNMIGRATED  OTEL_RESOURCE_ATTRIBUTES                           1
  UNMIGRATED  OTEL_SERVICE_NAME                                  1

PACKAGE: ddtrace/tracer
  STATUS      CONFIG                                     CALL_SITES
  STILL_READ  DD_API_KEY                                 1
  UNMIGRATED  DD_APM_TRACING_ENABLED                     1
  UNMIGRATED  DD_APP_KEY                                 1
  UNMIGRATED  DD_CIVISIBILITY_AGENTLESS_URL              1
  UNMIGRATED  DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED  1
  UNMIGRATED  DD_LLMOBS_AGENTLESS_ENABLED                1
  UNMIGRATED  DD_LLMOBS_ENABLED                          1
  UNMIGRATED  DD_LLMOBS_ML_APP                           1
  UNMIGRATED  DD_LLMOBS_PROJECT_NAME                     1
  UNMIGRATED  DD_SITE                                    2
  UNMIGRATED  DD_TRACER_EXPERIMENTAL_SPAN_POOL_ENABLED   1
  UNMIGRATED  DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED   1
  UNMIGRATED  DD_TRACE_DEBUG_SEELOG_WORKAROUND           1
  UNMIGRATED  DD_TRACE_ENABLED                           1
  UNMIGRATED  DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT      1
  UNMIGRATED  DD_TRACE_PROPAGATION_EXTRACT_FIRST         1
  UNMIGRATED  DD_TRACE_PROPAGATION_STYLE_EXTRACT         1
  UNMIGRATED  DD_TRACE_PROPAGATION_STYLE_INJECT          1
  UNMIGRATED  OTEL_TRACES_SAMPLER_ARG                    1

PACKAGE: instrumentation
  STATUS      CONFIG                                       CALL_SITES
  STILL_READ  DD_DATA_STREAMS_ENABLED                      1
  UNMIGRATED  DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED  1

PACKAGE: instrumentation/graphql
  STATUS      CONFIG                             CALL_SITES
  UNMIGRATED  DD_TRACE_GRAPHQL_ERROR_EXTENSIONS  1

PACKAGE: instrumentation/httptrace
  STATUS      CONFIG                                                 CALL_SITES
  UNMIGRATED  DD_TRACE_BAGGAGE_TAG_KEYS                              1
  UNMIGRATED  DD_TRACE_CLIENT_IP_ENABLED                             1
  UNMIGRATED  DD_TRACE_HTTP_SERVER_ERROR_STATUSES                    1
  UNMIGRATED  DD_TRACE_HTTP_URL_QUERY_STRING_ALLOWLIST               1
  UNMIGRATED  DD_TRACE_HTTP_URL_QUERY_STRING_ALLOWLIST_CLIENT        1
  UNMIGRATED  DD_TRACE_HTTP_URL_QUERY_STRING_ALLOWLIST_SERVER        1
  UNMIGRATED  DD_TRACE_HTTP_URL_QUERY_STRING_DISABLED                1
  UNMIGRATED  DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED               1
  UNMIGRATED  DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP               2
  UNMIGRATED  DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT  1
  UNMIGRATED  DD_TRACE_RESOURCE_RENAMING_ENABLED                     1

PACKAGE: instrumentation/internal/namingschema
  STATUS      CONFIG                                             CALL_SITES
  STILL_READ  DD_SERVICE                                         1
  STILL_READ  DD_TRACE_SPAN_ATTRIBUTE_SCHEMA                     1
  UNMIGRATED  DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED  1

PACKAGE: internal
  STATUS      CONFIG                         CALL_SITES
  STILL_READ  DD_AGENT_HOST                  1
  STILL_READ  DD_TAGS                        1
  STILL_READ  DD_TRACE_AGENT_PORT            1
  STILL_READ  DD_TRACE_AGENT_URL             1
  UNMIGRATED  DD_EXTERNAL_ENV                1
  UNMIGRATED  DD_GIT_COMMIT_SHA              1
  UNMIGRATED  DD_GIT_REPOSITORY_URL          1
  UNMIGRATED  DD_TRACE_GIT_METADATA_ENABLED  1

PACKAGE: internal/appsec
  STATUS      CONFIG           CALL_SITES
  UNMIGRATED  DD_APPSEC_RULES  1

PACKAGE: internal/appsec/config
  STATUS      CONFIG                                                CALL_SITES
  UNMIGRATED  DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE  1
  UNMIGRATED  DD_API_SECURITY_ENABLED                               1
  UNMIGRATED  DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS  1
  UNMIGRATED  DD_API_SECURITY_PROXY_SAMPLE_RATE                     1
  UNMIGRATED  DD_API_SECURITY_REQUEST_SAMPLE_RATE                   1
  UNMIGRATED  DD_API_SECURITY_SAMPLE_DELAY                          1
  UNMIGRATED  DD_APM_TRACING_ENABLED                                1
  UNMIGRATED  DD_APPSEC_ENABLED                                     1
  UNMIGRATED  DD_APPSEC_RASP_ENABLED                                1
  UNMIGRATED  DD_APPSEC_RULES                                       1
  UNMIGRATED  DD_APPSEC_SCA_ENABLED                                 1
  UNMIGRATED  DD_APPSEC_TRACE_RATE_LIMIT                            1
  UNMIGRATED  DD_APPSEC_WAF_TIMEOUT                                 1

PACKAGE: internal/appsec/listener/httpsec
  STATUS      CONFIG                     CALL_SITES
  UNMIGRATED  DD_TRACE_CLIENT_IP_HEADER  1

PACKAGE: internal/bazel
  STATUS      CONFIG                                  CALL_SITES
  UNMIGRATED  DD_TEST_OPTIMIZATION_MANIFEST_FILE      1
  UNMIGRATED  DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES  1

PACKAGE: internal/civisibility/envconfig
  STATUS      CONFIG                   CALL_SITES
  STILL_READ  DD_CIVISIBILITY_ENABLED  1

PACKAGE: internal/civisibility/integrations
  STATUS      CONFIG                                               CALL_SITES
  STILL_READ  DD_SERVICE                                           1
  STILL_READ  DD_TRACE_DEBUG                                       1
  UNMIGRATED  DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED  1
  UNMIGRATED  DD_CIVISIBILITY_FLAKY_RETRY_COUNT                    1
  UNMIGRATED  DD_CIVISIBILITY_FLAKY_RETRY_ENABLED                  1
  UNMIGRATED  DD_CIVISIBILITY_GIT_UPLOAD_ENABLED                   1
  UNMIGRATED  DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED     1
  UNMIGRATED  DD_CIVISIBILITY_SUBTEST_FEATURES_ENABLED             1
  UNMIGRATED  DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT              1
  UNMIGRATED  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES            1
  UNMIGRATED  DD_TEST_MANAGEMENT_ENABLED                           1

PACKAGE: internal/civisibility/integrations/gotesting
  STATUS      CONFIG                                                           CALL_SITES
  UNMIGRATED  DD_CIVISIBILITY_INTERNAL_PARALLEL_EARLY_FLAKE_DETECTION_ENABLED  1
  UNMIGRATED  DD_TEST_MANAGEMENT_ENABLED                                       1

PACKAGE: internal/civisibility/integrations/logs
  STATUS      CONFIG                        CALL_SITES
  UNMIGRATED  DD_CIVISIBILITY_LOGS_ENABLED  1

PACKAGE: internal/civisibility/utils
  STATUS      CONFIG                              CALL_SITES
  STILL_READ  DD_SERVICE                          1
  UNMIGRATED  DD_ACTION_EXECUTION_ID              1
  UNMIGRATED  DD_PIPELINE_EXECUTION_ID            1
  UNMIGRATED  DD_TEST_OPTIMIZATION_ENV_DATA_FILE  1
  UNMIGRATED  DD_TEST_SESSION_NAME                1

PACKAGE: internal/civisibility/utils/net
  STATUS      CONFIG                             CALL_SITES
  STILL_READ  DD_API_KEY                         1
  STILL_READ  DD_CIVISIBILITY_AGENTLESS_ENABLED  1
  STILL_READ  DD_ENV                             1
  STILL_READ  DD_SERVICE                         1
  STILL_READ  DD_TAGS                            1
  STILL_READ  DD_VERSION                         1
  UNMIGRATED  DD_CIVISIBILITY_AGENTLESS_URL      1
  UNMIGRATED  DD_SITE                            1

PACKAGE: internal/civisibility/utils/telemetry
  STATUS      CONFIG                                         CALL_SITES
  UNMIGRATED  DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER  1

PACKAGE: internal/globalconfig
  STATUS      CONFIG                           CALL_SITES
  UNMIGRATED  DD_INSTRUMENTATION_INSTALL_ID    1
  UNMIGRATED  DD_INSTRUMENTATION_INSTALL_TIME  1
  UNMIGRATED  DD_INSTRUMENTATION_INSTALL_TYPE  1

PACKAGE: internal/hostname
  STATUS      CONFIG       CALL_SITES
  UNMIGRATED  DD_HOSTNAME  1

PACKAGE: internal/namingschema
  STATUS      CONFIG                                             CALL_SITES
  STILL_READ  DD_SERVICE                                         1
  STILL_READ  DD_TRACE_SPAN_ATTRIBUTE_SCHEMA                     1
  UNMIGRATED  DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED  1

PACKAGE: internal/processtags
  STATUS      CONFIG                                          CALL_SITES
  UNMIGRATED  DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED  1

PACKAGE: internal/remoteconfig
  STATUS      CONFIG                                  CALL_SITES
  STILL_READ  DD_ENV                                  1
  UNMIGRATED  DD_RC_TUF_ROOT                          1
  UNMIGRATED  DD_REMOTE_CONFIGURATION_ENABLED         1
  UNMIGRATED  DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS  1

PACKAGE: internal/stacktrace
  STATUS      CONFIG                           CALL_SITES
  UNMIGRATED  DD_APPSEC_MAX_STACK_TRACE_DEPTH  1
  UNMIGRATED  DD_APPSEC_STACK_TRACE_ENABLED    1

PACKAGE: internal/telemetry
  STATUS      CONFIG                                             CALL_SITES
  STILL_READ  DD_API_KEY                                         1
  UNMIGRATED  DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT  1
  UNMIGRATED  DD_INSTRUMENTATION_TELEMETRY_ENABLED               1
  UNMIGRATED  DD_SITE                                            1
  UNMIGRATED  DD_TELEMETRY_DEBUG                                 1
  UNMIGRATED  DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED         1
  UNMIGRATED  DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL           1
  UNMIGRATED  DD_TELEMETRY_HEARTBEAT_INTERVAL                    1
  UNMIGRATED  DD_TELEMETRY_LOG_COLLECTION_ENABLED                1
  UNMIGRATED  DD_TELEMETRY_METRICS_ENABLED                       1

PACKAGE: openfeature
  STATUS      CONFIG                                     CALL_SITES
  STILL_READ  DD_ENV                                     2
  STILL_READ  DD_SERVICE                                 2
  STILL_READ  DD_VERSION                                 2
  UNMIGRATED  DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED  1
  UNMIGRATED  DD_FLAGGING_EVALUATION_COUNTS_ENABLED      1

PACKAGE: profiler
  STATUS      CONFIG                                    CALL_SITES
  STILL_READ  DD_API_KEY                                1
  STILL_READ  DD_ENV                                    1
  STILL_READ  DD_SERVICE                                1
  STILL_READ  DD_TAGS                                   1
  STILL_READ  DD_TRACE_STARTUP_LOGS                     1
  STILL_READ  DD_VERSION                                1
  UNMIGRATED  DD_PROFILING_AGENTLESS                    1
  UNMIGRATED  DD_PROFILING_DEBUG_COMPRESSION_SETTINGS   1
  UNMIGRATED  DD_PROFILING_DELTA                        1
  UNMIGRATED  DD_PROFILING_ENABLED                      3
  UNMIGRATED  DD_PROFILING_ENDPOINT_COUNT_ENABLED       1
  UNMIGRATED  DD_PROFILING_EXECUTION_TRACE_ENABLED      1
  UNMIGRATED  DD_PROFILING_EXECUTION_TRACE_LIMIT_BYTES  1
  UNMIGRATED  DD_PROFILING_EXECUTION_TRACE_PERIOD       1
  UNMIGRATED  DD_PROFILING_FLUSH_ON_EXIT                1
  UNMIGRATED  DD_PROFILING_OUTPUT_DIR                   1
  UNMIGRATED  DD_PROFILING_UPLOAD_TIMEOUT               1
  UNMIGRATED  DD_PROFILING_URL                          1
  UNMIGRATED  DD_SITE                                   1

@darccio darccio merged commit 40280d1 into main Jul 1, 2026
326 of 329 checks passed
@darccio darccio deleted the eliottness/go-libdwaf-v5 branch July 1, 2026 09:14
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Jul 1, 2026
### What does this PR do?

Merges WAF attribute derivatives with **first-write-wins** in `AbsorbDerivatives` so a uniform-status redirect chain reports the **first** hop's `appsec.api.redirection.*` value instead of the last.

It also enables the `APPSEC_RASP_NON_BLOCKING` scenario in the system-tests CI matrix (across all weblog variants, matching `APPSEC_RASP`) so this stays covered.

### Motivation

#4938 moved per-hop downstream request/response WAF evaluation from the persistent request context to per-roundtrip **ephemeral subcontexts**. The persistent context deduplicates attribute generation within a request, so a redirection attribute rule matching the same status (e.g. 302) on every hop produced its derivative only once (the first hop). Ephemeral subcontexts don't share that dedup, so the rule re-fires on every hop and `AbsorbDerivatives`' last-write-wins kept the **final** hop.

This regressed system-tests `Test_API10_redirect_status::test_api10_redirect` on `golang@2.10.0-dev` (bisected to `40280d189`): `appsec.api.redirection.move_target` became `/mirror/200` (last hop) instead of `/redirect?totalRedirects=2` (first hop).

The fix restores the pre-#4938 "first discovery wins" invariant at the derivative merge boundary while preserving #4938's per-hop ephemeral evaluation (RASP SSRF still fires on every hop). The existing blocked-response-schema skip is kept ahead of the new guard.

### Testing

- New `TestAppsecHTTP30XRedirectChainKeepsFirstHop` (uniform-302 chain) — verified it FAILS without the fix and PASSES with it. The existing `TestAppsecHTTP30X` used distinct 308/307 per hop, so it never collided on a single key.
- New `AbsorbDerivatives` unit tests (first-write-wins + blocked-response-schema skip preserved).
- Full `contrib/net/http` appsec suite and `internal/appsec/emitter/waf` package pass; `go vet` clean.

### Reviewer's Checklist

- [x] Changed code has unit tests for its functionality at or near 100% coverage.
- [x] [System-Tests](https://github.com/DataDog/system-tests/) covering this feature have been enabled (`APPSEC_RASP_NON_BLOCKING` added to the CI matrix).
- [ ] There is a benchmark for any new code, or changes to existing code.
- [ ] If this interacts with the agent in a new way, a system test has been added.
- [x] New code is free of linting errors.
- [x] New code doesn't break existing tests.
- [ ] Add an appropriate team label so this PR gets put in the right place for the release notes.
- [x] All generated files are up to date.
- [ ] Non-trivial go.mod changes reviewed by @DataDog/dd-trace-go-guild (no go.mod changes).


Co-authored-by: eliott.bouhana <eliott.bouhana@datadoghq.com>
darccio pushed a commit that referenced this pull request Jul 1, 2026
### What does this PR do?

Merges WAF attribute derivatives with **first-write-wins** in `AbsorbDerivatives` so a uniform-status redirect chain reports the **first** hop's `appsec.api.redirection.*` value instead of the last.

It also enables the `APPSEC_RASP_NON_BLOCKING` scenario in the system-tests CI matrix (across all weblog variants, matching `APPSEC_RASP`) so this stays covered.

### Motivation

#4938 moved per-hop downstream request/response WAF evaluation from the persistent request context to per-roundtrip **ephemeral subcontexts**. The persistent context deduplicates attribute generation within a request, so a redirection attribute rule matching the same status (e.g. 302) on every hop produced its derivative only once (the first hop). Ephemeral subcontexts don't share that dedup, so the rule re-fires on every hop and `AbsorbDerivatives`' last-write-wins kept the **final** hop.

This regressed system-tests `Test_API10_redirect_status::test_api10_redirect` on `golang@2.10.0-dev` (bisected to `40280d189`): `appsec.api.redirection.move_target` became `/mirror/200` (last hop) instead of `/redirect?totalRedirects=2` (first hop).

The fix restores the pre-#4938 "first discovery wins" invariant at the derivative merge boundary while preserving #4938's per-hop ephemeral evaluation (RASP SSRF still fires on every hop). The existing blocked-response-schema skip is kept ahead of the new guard.

### Testing

- New `TestAppsecHTTP30XRedirectChainKeepsFirstHop` (uniform-302 chain) — verified it FAILS without the fix and PASSES with it. The existing `TestAppsecHTTP30X` used distinct 308/307 per hop, so it never collided on a single key.
- New `AbsorbDerivatives` unit tests (first-write-wins + blocked-response-schema skip preserved).
- Full `contrib/net/http` appsec suite and `internal/appsec/emitter/waf` package pass; `go vet` clean.

### Reviewer's Checklist

- [x] Changed code has unit tests for its functionality at or near 100% coverage.
- [x] [System-Tests](https://github.com/DataDog/system-tests/) covering this feature have been enabled (`APPSEC_RASP_NON_BLOCKING` added to the CI matrix).
- [ ] There is a benchmark for any new code, or changes to existing code.
- [ ] If this interacts with the agent in a new way, a system test has been added.
- [x] New code is free of linting errors.
- [x] New code doesn't break existing tests.
- [ ] Add an appropriate team label so this PR gets put in the right place for the release notes.
- [x] All generated files are up to date.
- [ ] Non-trivial go.mod changes reviewed by @DataDog/dd-trace-go-guild (no go.mod changes).


Co-authored-by: eliott.bouhana <eliott.bouhana@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apm:ecosystem contrib/* related feature requests or bugs performance/ignore-performance-regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants