@@ -615,7 +615,6 @@ impl Processor {
615615 ) ;
616616 }
617617
618- // todo(duncanista): Add missing metric tags for ASM
619618 // Add dynamic and trigger tags
620619 context
621620 . invocation_span
@@ -626,6 +625,19 @@ impl Processor {
626625 context. invocation_span . meta . extend ( trigger_tags) ;
627626 }
628627
628+ // Ensure _dd.appsec.enabled is present on the invocation span when AAP is enabled.
629+ // complete_inferred_spans (called below) propagates this metric from the invocation
630+ // span to the inferred trigger span. AppSec's process_span will set it again from the
631+ // security context when it runs, but this baseline guarantees the tag is always present
632+ // even when the context cannot be found at flush time.
633+ if self . config . serverless_appsec_enabled {
634+ context
635+ . invocation_span
636+ . metrics
637+ . entry ( "_dd.appsec.enabled" . to_string ( ) )
638+ . or_insert ( 1.0 ) ;
639+ }
640+
629641 self . inferrer
630642 . complete_inferred_spans ( & context. invocation_span ) ;
631643
@@ -1405,6 +1417,7 @@ impl Processor {
14051417 execution_id : & str ,
14061418 execution_name : & str ,
14071419 first_invocation : Option < bool > ,
1420+ execution_status : Option < String > ,
14081421 ) {
14091422 if let Err ( e) = self
14101423 . durable_context_tx
@@ -1413,6 +1426,7 @@ impl Processor {
14131426 execution_id : execution_id. to_owned ( ) ,
14141427 execution_name : execution_name. to_owned ( ) ,
14151428 first_invocation,
1429+ execution_status,
14161430 } )
14171431 . await
14181432 {
@@ -2332,4 +2346,103 @@ mod tests {
23322346 "no contexts should be ready to send yet"
23332347 ) ;
23342348 }
2349+
2350+ fn setup_appsec ( ) -> Processor {
2351+ let aws_config = Arc :: new ( AwsConfig {
2352+ region : "us-east-1" . into ( ) ,
2353+ aws_lwa_proxy_lambda_runtime_api : Some ( "***" . into ( ) ) ,
2354+ function_name : "test-function" . into ( ) ,
2355+ sandbox_init_time : Instant :: now ( ) ,
2356+ runtime_api : "***" . into ( ) ,
2357+ exec_wrapper : None ,
2358+ initialization_type : "on-demand" . into ( ) ,
2359+ } ) ;
2360+ let config = Arc :: new ( config:: Config {
2361+ service : Some ( "test-service" . to_string ( ) ) ,
2362+ serverless_appsec_enabled : true ,
2363+ ..config:: Config :: default ( )
2364+ } ) ;
2365+ let tags_provider = Arc :: new ( provider:: Provider :: new (
2366+ Arc :: clone ( & config) ,
2367+ LAMBDA_RUNTIME_SLUG . to_string ( ) ,
2368+ & HashMap :: from ( [ ( "function_arn" . to_string ( ) , "test-arn" . to_string ( ) ) ] ) ,
2369+ ) ) ;
2370+ let ( service, handle) =
2371+ dogstatsd:: aggregator:: AggregatorService :: new ( dogstatsd:: metric:: EMPTY_TAGS , 1024 )
2372+ . expect ( "failed to create aggregator service" ) ;
2373+ tokio:: spawn ( service. run ( ) ) ;
2374+ let propagator = Arc :: new ( DatadogCompositePropagator :: new ( Arc :: clone ( & config) ) ) ;
2375+ let ( durable_context_tx, _) = tokio:: sync:: mpsc:: channel ( 1 ) ;
2376+ Processor :: new (
2377+ tags_provider,
2378+ config,
2379+ aws_config,
2380+ handle,
2381+ propagator,
2382+ durable_context_tx,
2383+ )
2384+ }
2385+
2386+ #[ tokio:: test]
2387+ async fn enrich_ctx_sets_appsec_enabled_when_aap_enabled ( ) {
2388+ let mut p = setup_appsec ( ) ;
2389+ let request_id = String :: from ( "req-appsec" ) ;
2390+ p. on_invoke_event ( request_id. clone ( ) ) ;
2391+ p. on_platform_start ( request_id. clone ( ) , chrono:: Utc :: now ( ) ) ;
2392+
2393+ let ctx = p
2394+ . enrich_ctx_at_platform_done ( & request_id, Status :: Success )
2395+ . expect ( "context must be present" ) ;
2396+
2397+ assert_eq ! (
2398+ ctx. invocation_span. metrics. get( "_dd.appsec.enabled" ) ,
2399+ Some ( & 1.0 ) ,
2400+ "_dd.appsec.enabled must be 1.0 when AAP is enabled"
2401+ ) ;
2402+ }
2403+
2404+ #[ tokio:: test]
2405+ async fn enrich_ctx_does_not_set_appsec_enabled_when_aap_disabled ( ) {
2406+ let mut p = setup ( ) ;
2407+ let request_id = String :: from ( "req-no-appsec" ) ;
2408+ p. on_invoke_event ( request_id. clone ( ) ) ;
2409+ p. on_platform_start ( request_id. clone ( ) , chrono:: Utc :: now ( ) ) ;
2410+
2411+ let ctx = p
2412+ . enrich_ctx_at_platform_done ( & request_id, Status :: Success )
2413+ . expect ( "context must be present" ) ;
2414+
2415+ assert ! (
2416+ !ctx. invocation_span
2417+ . metrics
2418+ . contains_key( "_dd.appsec.enabled" ) ,
2419+ "_dd.appsec.enabled must not be set when AAP is disabled"
2420+ ) ;
2421+ }
2422+
2423+ #[ tokio:: test]
2424+ async fn enrich_ctx_does_not_override_existing_appsec_enabled ( ) {
2425+ let mut p = setup_appsec ( ) ;
2426+ let request_id = String :: from ( "req-appsec-preset" ) ;
2427+ p. on_invoke_event ( request_id. clone ( ) ) ;
2428+ p. on_platform_start ( request_id. clone ( ) , chrono:: Utc :: now ( ) ) ;
2429+
2430+ // Pre-set a different value to verify or_insert does not overwrite it.
2431+ p. context_buffer
2432+ . get_mut ( & request_id)
2433+ . expect ( "context must exist" )
2434+ . invocation_span
2435+ . metrics
2436+ . insert ( "_dd.appsec.enabled" . to_string ( ) , 0.0 ) ;
2437+
2438+ let ctx = p
2439+ . enrich_ctx_at_platform_done ( & request_id, Status :: Success )
2440+ . expect ( "context must be present" ) ;
2441+
2442+ assert_eq ! (
2443+ ctx. invocation_span. metrics. get( "_dd.appsec.enabled" ) ,
2444+ Some ( & 0.0 ) ,
2445+ "pre-existing _dd.appsec.enabled value must not be overwritten"
2446+ ) ;
2447+ }
23352448}
0 commit comments