@@ -1144,6 +1144,111 @@ class TelemetryTests {
11441144 assert ' rate_limited:true' in wafReqRateLimited. tags
11451145 }
11461146
1147+ /**
1148+ * Verifies that appsec.rasp.rule.match carries the `block` tag with the correct value:
1149+ * - block:irrelevant → RASP rule matched but `on_match` did not request a block
1150+ * - block:success → RASP rule matched, block was requested and (per PHP semantics)
1151+ * always succeeds; PHP cannot fail to block once it decides to.
1152+ *
1153+ * Cross-tracer spec (see dd-trace-go, dd-trace-py): the tag is always emitted on
1154+ * rasp.rule.match. PHP never emits block:failure because the layer is assumed to
1155+ * always succeed at terminating the script.
1156+ *
1157+ * Only applies to the Rust helper.
1158+ */
1159+ @Test
1160+ @Order (12 )
1161+ void ' rasp rule match has block tag' () {
1162+ Assumptions . assumeTrue(System . getProperty(' USE_HELPER_RUST' ) != null ,
1163+ ' block tag on rasp.rule.match is only implemented on the Rust helper' )
1164+
1165+ try {
1166+ // Phase 1: non-blocking RASP rule match (recommended.json lfi/ssrf rules have
1167+ // on_match: ["stack_trace"], so a match does not block). Expect block:irrelevant.
1168+ Supplier<RemoteConfigRequest > requestSup = CONTAINER . applyRemoteConfig(RC_TARGET , [
1169+ ' datadog/2/ASM_FEATURES/asm_features_activation/config' : [
1170+ asm : [enabled : true ]
1171+ ]
1172+ ])
1173+
1174+ CONTAINER . traceFromRequest(' /hello.php' ) { HttpResponse<InputStream > resp ->
1175+ assert resp. statusCode() == 200
1176+ }
1177+ assert requestSup. get() != null
1178+
1179+ CONTAINER . traceFromRequest(
1180+ ' /multiple_rasp.php?path=../somefile&other=../otherfile&domain=169.254.169.254' ) {
1181+ HttpResponse<InputStream > resp -> assert resp. statusCode() == 200
1182+ }
1183+
1184+ // Phase 2: override the LFI rule on_match to ["block"]. The first @fopen in
1185+ // multiple_rasp.php hits an LFI match, the WAF returns block_request, and the
1186+ // PHP layer terminates the request. Expect block:success on the rasp.rule.match
1187+ // emitted for rule_type:lfi from that request.
1188+ Supplier<RemoteConfigRequest > overrideSup = CONTAINER . applyRemoteConfig(RC_TARGET , [
1189+ ' datadog/2/ASM_FEATURES/asm_features_activation/config' : [
1190+ asm : [enabled : true ]
1191+ ],
1192+ ' datadog/2/ASM/rasp_lfi_block_override/config' : [
1193+ rules_override : [[
1194+ rules_target : [[rule_id : ' rasp-001-001' ]],
1195+ on_match : [' block' ]
1196+ ]]
1197+ ]
1198+ ])
1199+ assert overrideSup. get() != null
1200+
1201+ // Trigger the blocking LFI; status code is 403 because the rule now blocks.
1202+ HttpRequest blockingReq = CONTAINER . buildReq(
1203+ ' /multiple_rasp.php?path=../somefile&other=../otherfile&domain=169.254.169.254' )
1204+ .GET (). build()
1205+ CONTAINER . traceFromRequest(blockingReq, ofString()) { HttpResponse<String > resp ->
1206+ assert resp. statusCode() == 403
1207+ }
1208+
1209+ TelemetryHelpers.Metric lfiMatchIrrelevant
1210+ TelemetryHelpers.Metric lfiMatchSuccess
1211+
1212+ TelemetryHelpers . waitForMetrics(CONTAINER , 30 ) { List<TelemetryHelpers.GenerateMetrics > messages ->
1213+ def allSeries = messages. collectMany { it. series }
1214+ lfiMatchIrrelevant = lfiMatchIrrelevant ?: allSeries. find {
1215+ it. name == ' rasp.rule.match' &&
1216+ ' rule_type:lfi' in it. tags &&
1217+ ' block:irrelevant' in it. tags
1218+ }
1219+ lfiMatchSuccess = lfiMatchSuccess ?: allSeries. find {
1220+ it. name == ' rasp.rule.match' &&
1221+ ' rule_type:lfi' in it. tags &&
1222+ ' block:success' in it. tags
1223+ }
1224+ lfiMatchIrrelevant != null && lfiMatchSuccess != null
1225+ }
1226+
1227+ assert lfiMatchIrrelevant != null :
1228+ ' rasp.rule.match metric with block:irrelevant not found — ' +
1229+ ' helper must emit block:irrelevant when the matched RASP rule has no block action'
1230+ assert lfiMatchIrrelevant. namespace == ' appsec'
1231+ assert lfiMatchIrrelevant. type == ' count'
1232+ assert lfiMatchIrrelevant. points[0 ][1 ] >= 1.0d
1233+
1234+ assert lfiMatchSuccess != null :
1235+ ' rasp.rule.match metric with block:success not found — ' +
1236+ ' helper must emit block:success when the matched RASP rule triggered a block'
1237+ assert lfiMatchSuccess. namespace == ' appsec'
1238+ assert lfiMatchSuccess. type == ' count'
1239+ assert lfiMatchSuccess. points[0 ][1 ] >= 1.0d
1240+ } finally {
1241+ // Drop the override so subsequent ordered tests in this class run with the
1242+ // default (non-blocking) RASP ruleset, even if assertions above failed.
1243+ CONTAINER . applyRemoteConfig(RC_TARGET , [
1244+ ' datadog/2/ASM_FEATURES/asm_features_activation/config' : [
1245+ asm : [enabled : true ]
1246+ ],
1247+ ' datadog/2/ASM/rasp_lfi_block_override/config' : null
1248+ ])
1249+ }
1250+ }
1251+
11471252 @Test
11481253 @Order (21 )
11491254 void ' waf config errors emitted with action init for bad init rules' () {
0 commit comments