fix(appsec): Go runtime _dd.appsec.enabled missing on aws.lambda#1213
Merged
purple4reina merged 4 commits intomainfrom Apr 28, 2026
Merged
fix(appsec): Go runtime _dd.appsec.enabled missing on aws.lambda#1213purple4reina merged 4 commits intomainfrom
_dd.appsec.enabled missing on aws.lambda#1213purple4reina merged 4 commits intomainfrom
Conversation
Two related bugs caused the `_dd.appsec.enabled` metric and other AppSec tags to be absent from the `aws.lambda` invocation span for Go (and Java) runtimes when using extension-side App & API Protection enablement. **Bug 1 — race condition (context deleted by placeholder span)** Go's tracer emits a placeholder `aws.lambda` span with `resource = "dd-tracer-serverless-span"` in its `/v0.4/traces` flush. AppSec's `service_entry_span_mut` was matching this placeholder (same name, `request_id` in meta). Depending on tokio scheduling, the placeholder could arrive and be processed by AppSec _after_ `/runtime/invocation/ response` had already set `response_seen = true`. In that case, `Processor::process_span` would tag the placeholder (harmless, it gets dropped by `ChunkProcessor`) and then _delete the AppSec context_ (the "finalized" branch). When `process_on_platform_runtime_done` later sent the extension-built `aws.lambda` span via `send_ctx_spans`, the context was gone and no tags were applied. Fix: filter placeholder spans out of `service_entry_span_mut` by excluding spans whose `resource == INVOCATION_SPAN_RESOURCE`. These spans are always dropped before reaching the backend, so tagging them is both pointless and harmful. **Bug 2 — `_dd.appsec.enabled` never pre-set on the invocation span** `enrich_ctx_at_platform_done` calls `inferrer.complete_inferred_spans` which propagates `_dd.appsec.enabled` from the invocation span to the inferred trigger span (e.g. `aws.lambda.url`). However, AppSec has not yet run on the invocation span at that point, so the metric is not in `invocation_span.metrics`. `propagate_appsec` therefore falls back to the `serverless_appsec_enabled` config flag for the _inferred_ span (which always got the tag) but never sets it on the _invocation_ span itself. If the AppSec context could not be found at flush time for any reason, `aws.lambda` shipped without `_dd.appsec.enabled`. Fix: in `enrich_ctx_at_platform_done`, pre-set `_dd.appsec.enabled = 1.0` on the invocation span when AAP is enabled, before calling `complete_inferred_spans`. This resolves the pre-existing TODO comment (`// todo(duncanista): Add missing metric tags for ASM`), ensures the inferred span inherits the value from the actual metric rather than the config fallback, and makes the tag present even when the AppSec context is unavailable. Both issues are specific to Go (and Java) because only those runtimes use the placeholder span pattern. Python and Node emit the `aws.lambda` span directly in their tracer payload, which is not filtered and is not subject to the context-deletion race. JJ-Change-Id: xltnyl
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes missing/flaky AppSec tags (notably _dd.appsec.enabled) on the aws.lambda invocation span for Go/Java runtimes when using extension-side App & API Protection, by avoiding placeholder-span races and ensuring the metric is present early enough for propagation.
Changes:
- Expose
INVOCATION_SPAN_RESOURCEwithin the crate so non-tracesmodules can reliably identify placeholder invocation spans. - Update AppSec span selection to ignore Go/Java placeholder
aws.lambdaspans (resource == INVOCATION_SPAN_RESOURCE) to prevent premature AppSec context deletion. - Pre-set
_dd.appsec.enabled = 1.0on the invocation span when AAP is enabled, before inferred-span completion/propagation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
bottlecap/src/traces/mod.rs |
Makes the placeholder invocation span resource constant available crate-wide. |
bottlecap/src/appsec/processor/mod.rs |
Filters out placeholder aws.lambda spans when selecting the service-entry span for AppSec tagging. |
bottlecap/src/lifecycle/invocation/processor.rs |
Ensures _dd.appsec.enabled is present on the invocation span prior to inferred span propagation. |
purple4reina
approved these changes
Apr 27, 2026
duncanista
approved these changes
Apr 27, 2026
Contributor
|
Running the e2e tests here: https://gitlab.ddbuild.io/DataDog/serverless-e2e-tests/-/pipelines/110011644 |
Contributor
The test was flaky though, sometimes passing, sometimes not, but in this case it passed. |
Add two unit tests for `Processor::service_entry_span_mut`: - verifies the non-placeholder span is returned when both a placeholder and a real `aws.lambda` span are present - verifies `None` is returned when only a placeholder span exists These guard against regressions in the span-selection logic that caused AppSec context to be prematurely deleted on Go/Java runtimes, leaving the real invocation span untagged. JJ-Change-Id: vlkyxo
Add three unit tests for `enrich_ctx_at_platform_done`: - verifies `_dd.appsec.enabled = 1.0` is set on the invocation span when `serverless_appsec_enabled` is true - verifies the metric is absent when AAP is disabled - verifies an already-present value is not overwritten by the `or_insert` baseline JJ-Change-Id: myopvz
- `cargo fmt`: reformat `service_entry_span_mut` predicate and `setup_appsec` helper - wrap `AppSec` in backticks in doc comment to satisfy `clippy::doc_markdown` - replace `.get(k).is_none()` with `!contains_key(k)` in `enrich_ctx_does_not_set_appsec_enabled_when_aap_disabled` JJ-Change-Id: xnknop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two related bugs caused
_dd.appsec.enabledand other AppSec tags to be absent from theaws.lambdainvocation span for Go (and Java) runtimes when using extension-side App & API Protection.The symptom was flaky integration tests: sometimes all AppSec tags were missing, and even when that race was won,
_dd.appsec.enabledwas consistently absent fromaws.lambdawhile the inferred trigger span (aws.lambda.url,aws.httpapi, etc.) always had it.Root causes
Bug 1 — race condition: AppSec context deleted by placeholder span
Go's tracer emits a placeholder
aws.lambdaspan withresource = "dd-tracer-serverless-span"alongside its child spans in/v0.4/traces.AppSecProcessor::service_entry_span_mutwas matching this placeholder (it hasname = "aws.lambda"andrequest_idin meta). Depending on tokio scheduling, the placeholder could reachProcessor::process_spanafter/runtime/invocation/responsehad setresponse_seen = true. In that case,process_spanwould tag the placeholder — harmless, sinceChunkProcessordrops it — but then delete the AppSec context. Whenprocess_on_platform_runtime_donelater sent the extension-builtaws.lambdaspan viasend_ctx_spans, the context was gone and no tags were applied.Fix: filter placeholder spans out of
service_entry_span_mutby excluding spans whoseresource == INVOCATION_SPAN_RESOURCE("dd-tracer-serverless-span"). These spans are always dropped before reaching the backend; tagging them is both pointless and harmful.Bug 2 —
_dd.appsec.enablednever pre-set on the invocation spanenrich_ctx_at_platform_donecallsinferrer.complete_inferred_spans, which propagates_dd.appsec.enabledfrom the invocation span to the inferred trigger span viapropagate_appsec. However, AppSec has not yet run on the invocation span at that point, so_dd.appsec.enabledis not ininvocation_span.metrics.propagate_appsecfalls back to theserverless_appsec_enabledconfig flag for the inferred span (so it always got the tag) but never sets it on the invocation span itself. If AppSec's context was unavailable at flush time for any reason,aws.lambdashipped without the tag. This also explains an existing// todo(duncanista): Add missing metric tags for ASMcomment at that exact location.Fix: pre-set
_dd.appsec.enabled = 1.0on the invocation span inenrich_ctx_at_platform_donewhen AAP is enabled, before callingcomplete_inferred_spans. This ensures the inferred span inherits from the actual metric value rather than the config fallback, and guarantees the tag is present even when the AppSec security context cannot be found at flush time.Why Go/Java only
Only Go and Java use the placeholder span pattern. Python and Node emit their
aws.lambdaspan directly in their tracer payload withresource = function_name, soservice_entry_span_mutcorrectly identifies it, and the context-deletion race cannot happen.Changes
bottlecap/src/traces/mod.rsINVOCATION_SPAN_RESOURCEpub(crate)bottlecap/src/appsec/processor/mod.rsservice_entry_span_mutbottlecap/src/lifecycle/invocation/processor.rs_dd.appsec.enabledon invocation span beforecomplete_inferred_spansTesting
appsec_processor_testintegration test passes._dd.appsec.enabledis now consistently present on theaws.lambdaspan.