Skip to content

Commit f3e5e5b

Browse files
authored
API Security sampling when tracers lack HTTP routes - Rfc 1076 (#10424)
Implements http.endpoint fallback in the API Security Sampler when http.route is unavailable, enabling sampling of traffic in frameworks that don't provide route information. Reuses EndpointResolver.computeEndpoint() from RFC-1051 (no code duplication) Uses static computation method to avoid tagging the span when endpoint is used as fallback Excludes 404 responses from fallback sampling (failsafe against sampling not-found routes) Caches computed endpoint with boolean flag to prevent multiple computations per request
1 parent c706f2b commit f3e5e5b

File tree

5 files changed

+334
-4
lines changed

5 files changed

+334
-4
lines changed

dd-java-agent/appsec/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
implementation project(':communication')
1717
implementation project(':products:metrics:metrics-api')
1818
implementation project(':telemetry')
19+
implementation project(':dd-trace-core')
1920
implementation group: 'io.sqreen', name: 'libsqreen', version: '17.3.0'
2021
implementation libs.moshi
2122

dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/ApiSecuritySamplerImpl.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,26 @@ public ApiSecuritySamplerImpl(
5757

5858
@Override
5959
public boolean preSampleRequest(final @Nonnull AppSecRequestContext ctx) {
60-
final String route = ctx.getRoute();
60+
String route = ctx.getRoute();
61+
62+
// If route is absent, use http.endpoint as fallback (RFC-1076)
6163
if (route == null) {
62-
return false;
64+
// Don't sample blocked requests - they represent attacks, not valid API endpoints
65+
if (ctx.isWafBlocked()) {
66+
return false;
67+
}
68+
final int statusCode = ctx.getResponseStatus();
69+
// Don't use endpoint for 404 responses as a failsafe
70+
if (statusCode == 404) {
71+
return false;
72+
}
73+
// Try to get or compute the endpoint
74+
route = ctx.getOrComputeEndpoint();
75+
if (route == null) {
76+
return false;
77+
}
6378
}
79+
6480
final String method = ctx.getMethod();
6581
if (method == null) {
6682
return false;

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import datadog.trace.api.Config;
1414
import datadog.trace.api.http.StoredBodySupplier;
1515
import datadog.trace.api.internal.TraceSegment;
16+
import datadog.trace.core.endpoint.EndpointResolver;
1617
import datadog.trace.util.Numbers;
1718
import datadog.trace.util.stacktrace.StackTraceEvent;
1819
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -121,6 +122,9 @@ public class AppSecRequestContext implements DataBundle, Closeable {
121122
private String method;
122123
private String savedRawURI;
123124
private String route;
125+
private String httpUrl;
126+
private String endpoint;
127+
private boolean endpointComputed = false;
124128
private final Map<String, List<String>> requestHeaders = new LinkedHashMap<>();
125129
private final Map<String, List<String>> responseHeaders = new LinkedHashMap<>();
126130
private volatile Map<String, List<String>> collectedCookies;
@@ -424,6 +428,45 @@ public void setRoute(String route) {
424428
this.route = route;
425429
}
426430

431+
public String getHttpUrl() {
432+
return httpUrl;
433+
}
434+
435+
public void setHttpUrl(String httpUrl) {
436+
this.httpUrl = httpUrl;
437+
}
438+
439+
/**
440+
* Gets or computes the http.endpoint for this request. The endpoint is computed lazily on first
441+
* access and cached to avoid recomputation.
442+
*
443+
* @return the http.endpoint value, or null if it cannot be computed
444+
*/
445+
public String getOrComputeEndpoint() {
446+
if (!endpointComputed) {
447+
if (httpUrl != null && !httpUrl.isEmpty()) {
448+
try {
449+
endpoint = EndpointResolver.computeEndpoint(httpUrl);
450+
} catch (Exception e) {
451+
endpoint = null;
452+
}
453+
}
454+
endpointComputed = true;
455+
}
456+
return endpoint;
457+
}
458+
459+
/**
460+
* Sets the endpoint directly without computing it. This is useful when the endpoint has already
461+
* been computed elsewhere.
462+
*
463+
* @param endpoint the endpoint value to set
464+
*/
465+
public void setEndpoint(String endpoint) {
466+
this.endpoint = endpoint;
467+
this.endpointComputed = true;
468+
}
469+
427470
public void setKeepOpenForApiSecurityPostProcessing(final boolean flag) {
428471
this.keepOpenForApiSecurityPostProcessing = flag;
429472
}

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,11 +949,16 @@ private NoopFlow onRequestEnded(RequestContext ctx_, IGSpanInfo spanInfo) {
949949
private boolean maybeSampleForApiSecurity(
950950
AppSecRequestContext ctx, IGSpanInfo spanInfo, Map<String, Object> tags) {
951951
log.debug("Checking API Security for end of request handler on span: {}", spanInfo.getSpanId());
952-
// API Security sampling requires http.route tag.
952+
// API Security sampling requires http.route tag or http.url for endpoint inference.
953953
final Object route = tags.get(Tags.HTTP_ROUTE);
954954
if (route != null) {
955955
ctx.setRoute(route.toString());
956956
}
957+
// Pass http.url to enable endpoint inference when route is absent
958+
final Object url = tags.get(Tags.HTTP_URL);
959+
if (url != null) {
960+
ctx.setHttpUrl(url.toString());
961+
}
957962
ApiSecuritySampler requestSampler = requestSamplerSupplier.get();
958963
return requestSampler.preSampleRequest(ctx);
959964
}

0 commit comments

Comments
 (0)