Conversation
chain.proceed(request) was wrapped in the same try/catch that guards the AppSec request/response hooks, so any IOException from the real network call (e.g. ConnectException) was swallowed and the request was silently retried via chain.proceed(chain.request()). This double- executes non-idempotent requests on transient network failures and surfaces the retry's own failure as an unhandled error blamed on the interceptor. Narrow the try/catch to only cover the AppSec hooks so genuine I/O failures propagate normally. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers both okhttp-2.2 and okhttp-3.0 AppSecInterceptor.intercept(): asserts an IOException from chain.proceed() propagates without being swallowed/retried, using Mockito + AgentTracer.forceRegister instead of Groovy/Spock. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
span.getTag(Tags.HTTP_URL) can return null, and .toString() on it throws an NPE that silently skips the AppSec request hook for that call. Null-check instead of relying on the catch-all to swallow it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
If publish() throws inside onResponse() after the response body has already been read and rebuilt into `result`, the exception propagated out of onResponse() and caused intercept() to fall back to the original response, whose body had already been consumed. Callers then saw an empty/closed body instead of the real one. Catch and log non-blocking failures from publish() locally so the rebuilt response is always returned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
More details
The new catch keeps the rebuilt response when the AppSec hook fails. It does not catch BlockingException.
🤖 Datadog Autotest · Commit 04ae7fb · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
…ougqh/fix-appsec-interceptor-response-body-on-hook-failure
…nterceptor-response-body-on-hook-failure
…sponse-body-on-hook-failure' into dougqh/fix-appsec-interceptor-response-body-on-hook-failure
There was a problem hiding this comment.
A wrapped exception with a null stack trace still causes a null-pointer exception after the new code installs outer probes. The same skip also gives those probes the wrong exception-chain index.
🤖 Datadog Autotest · Commit 47b65d5 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| while ((throwable = chainedExceptions.pollFirst()) != null) { | ||
| StackTraceElement[] stackTrace = throwable.getStackTrace(); | ||
| if (stackTrace == null || stackTrace.length == 0) { | ||
| continue; |
There was a problem hiding this comment.
Guard null traces in installed exception probes
Exception Replay still fails for the FastThrow input that the new guard must support.
Assertion details
- Input: An exception with a normal outer stack trace wraps an inner exception whose getStackTrace() method returns null.
- Expected:
Exception Replay must skip this exception safely or evaluate the installed probes without reading a null stack trace. - Actual:
The new skip installs probes on the outer exception. ExceptionProbe.evaluate() later reads the null inner stack trace and throws a null-pointer exception.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest · Open Bits AI session
| while ((throwable = chainedExceptions.pollFirst()) != null) { | ||
| StackTraceElement[] stackTrace = throwable.getStackTrace(); | ||
| if (stackTrace == null || stackTrace.length == 0) { | ||
| continue; |
There was a problem hiding this comment.
Keep the exception-chain index after a skipped trace
Snapshot processing can select the wrong throwable and assign incorrect frame tags after probe capture.
Assertion details
- Input: An inner chained exception has a null or empty stack trace, and an outer exception has stack frames that can receive probes.
- Expected:
Each probe must store the index of the throwable that supplies its stack frames. - Actual:
The continue statement does not increase chainedExceptionIdx. The code gives the outer exception the skipped inner exception's index.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest · Open Bits AI session
onResponse() reads and closes the original response body, then rebuilds a Response with a fresh body before calling publish(). If publish()'s WAF/ gateway callback throws a non-blocking exception, that exception used to propagate out of onResponse() entirely, and intercept()'s outer catch fell back to the original (already-drained/closed) response instead of the rebuilt one -- handing the caller an empty or closed body. Guard publish() locally so a non-blocking failure there no longer discards the rebuilt response; BlockingException still propagates as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…plicate-request' into dougqh/fix-appsec-interceptor-duplicate-request
…plicate-request' into dougqh/fix-appsec-interceptor-response-body-on-hook-failure # Conflicts: # dd-java-agent/instrumentation/okhttp/okhttp-2.2/src/main/java/datadog/trace/instrumentation/okhttp2/AppSecInterceptor.java # dd-java-agent/instrumentation/okhttp/okhttp-3.0/src/main/java/datadog/trace/instrumentation/okhttp3/AppSecInterceptor.java
|
|
||
| _Notes:_ The override label skips the workflow entirely. | ||
|
|
||
| ### team-freeze-guard [🔗](team-freeze-guard.yaml) |
There was a problem hiding this comment.
why this has been changed?
There was a problem hiding this comment.
More details
Both OkHttp variants keep the rebuilt response after a non-blocking response-hook exception. They still pass a blocking exception to the caller.
🤖 Datadog Autotest · Commit 7188831 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
…nterceptor-response-body-on-hook-failure # Conflicts: # dd-java-agent/instrumentation/okhttp/okhttp-2.2/src/main/java/datadog/trace/instrumentation/okhttp2/AppSecInterceptor.java # dd-java-agent/instrumentation/okhttp/okhttp-2.2/src/test/java/datadog/trace/instrumentation/okhttp2/AppSecInterceptorTest.java # dd-java-agent/instrumentation/okhttp/okhttp-3.0/src/main/java/datadog/trace/instrumentation/okhttp3/AppSecInterceptor.java # dd-java-agent/instrumentation/okhttp/okhttp-3.0/src/test/java/datadog/trace/instrumentation/okhttp3/AppSecInterceptorTest.java
There was a problem hiding this comment.
What Does This Do
Adds regression tests (
responseHookFailureAfterBodyCapturePreservesCapturedBody) toAppSecInterceptorTestin bothokhttp-2.2andokhttp-3.0, verifying that the response body survives a throwing response hook.Motivation
This PR was originally stacked on #12242 to fix a bug where
onResponse()'s already-rebuilt response body was discarded if the response hook (publish()) threw. That fix has since landed on master as part of #12242 itself, so rebasing this branch onto master collapsed the production-code diff to nothing — the only remaining content is the two regression tests, which master didn't yet have.Additional Notes
okhttp-2.2andokhttp-3.0instrumentation modules.AppSecInterceptorTestsuites pass for both modules;./gradlew spotlessApplyrun./techdebtand/perf-reviewrun over the branch diff: no findings.Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issueJira ticket: [PROJ-IDENT]
🤖 Generated with Claude Code