Skip to content

fix(httplib): finish the span when a RASP block raises through the wrapper - #20073

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 2 commits into
mainfrom
alberto.vara/httplib-span-basexception-on-block
Sep 4, 2026
Merged

fix(httplib): finish the span when a RASP block raises through the wrapper#20073
gh-worker-dd-mergequeue-cf854d[bot] merged 2 commits into
mainfrom
alberto.vara/httplib-span-basexception-on-block

Conversation

@avara1986

@avara1986 avara1986 commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Jira: APPSEC-70027

Exploit Prevention blocks an outgoing request by raising BlockingException, which derives from BaseException so that no intermediate handler can swallow a blocking decision. _wrap_request and _wrap_putrequest guard the wrapped call with except Exception:

    try:
        return func_to_call(*args, **kwargs)
    except Exception:
        span = getattr(instance, "_datadog_span", None)
        ...

The block goes straight past that handler. The span has already been created and activated by tracer.trace() and attached to the connection, so it is never finished: it is never flushed, it stays current for the rest of the thread's life, and every later span on that thread is parented to it.

Measured with the integration and appsec both patched, blocking from HTTPConnection.request:

span created: True   span finished: False   still current: True
Shutting down tracer with 1 spans. These spans will not be sent to Datadog: name=http.client.request

Order dependence

Reachable on main today, but only in one patch order:

patch order main
httplib contrib patched first clean, no span created
appsec patched first leaked

When appsec patches the attribute after the integration, its wrapt wrapper sits outside _wrap_request and the block raises before the span exists. When appsec patches first, the integration wraps appsec's wrapper and the block happens inside the span.

Why now

The AppSec RASP migration to WrappingContext (#19989, APPSEC-69878) removes the order that avoids this. A wrapping context lives in the target's own bytecode, so it is always innermost and the block always raises inside the span. #19989 depends on this landing first. Found by Codex review on that PR, split out here because it is a pre-existing bug in shared integration code rather than part of the migration.

Fix

Catch BaseException around the wrapped call in both wrappers and re-raise. That is what the rest of the tracer already does for span cleanup — redis_utils, valkey_utils, elasticsearch, asgi, pyramid, tornado and tracer.py. _wrap_getresponse already used try/finally and was unaffected.

Testing

Two tests in tests/contrib/httplib/test_httplib.py raise a real BlockingException from inside the wrapped call, so they need no network and no appsec setup:

  • test_span_is_finished_when_the_request_raises_a_base_exception
  • test_span_is_finished_when_putrequest_raises_a_base_exception

Both assert the span is finished and no span is left current. Mutation-checked — reverting to except Exception fails both.

contrib::httplib green on py3.12. scripts/lint fmt clean.

Checklist

  • PR author has checked that all the criteria below are met
  • The PR description includes an overview of the change
  • The PR description articulates the motivation for the change
  • The change includes tests OR the PR description describes a testing strategy
  • The PR description notes risks associated with the change, if any
  • Newly-added code is easy to change
  • The change follows the library release note guidelines
  • The change includes or references documentation updates if necessary
  • Backport labels are set (if applicable)

Reviewer Checklist

  • Reviewer has checked that all the criteria below are met
  • Title is accurate
  • All changes are related to the pull request's stated goal
  • Avoids breaking API changes
  • Testing strategy adequately addresses listed risks
  • Newly-added code is easy to change
  • Release note makes sense to a user of the library
  • If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment
  • Backport labels are set in a manner that is consistent with the release branch maintenance policy

🤖 Generated with Claude Code

…apper [APPSEC-70027]

Exploit Prevention blocks an outgoing request by raising BlockingException,
which derives from BaseException so that no intermediate handler can swallow a
blocking decision. _wrap_request and _wrap_putrequest guard the wrapped call
with "except Exception", so the block goes straight past them. The span has
already been created and activated by tracer.trace() and attached to the
connection, so it is never finished: it is never flushed, it stays current for
the rest of the thread's life, and every later span on that thread is parented
to it.

Measured with the integration and appsec both patched, blocking from
HTTPConnection.request:

  span created: True   span finished: False   still current: True
  Shutting down tracer with 1 spans. These spans will not be sent to Datadog

Reachable on main today, but only in one patch order. When appsec patches the
attribute after the integration, its wrapt wrapper sits outside _wrap_request
and the block raises before the span exists; when appsec patches first, the
integration wraps appsec's wrapper and the block happens inside the span.

  patch order            main
  httplib contrib first  clean, no span created
  appsec first           leaked

Catching BaseException and re-raising is what the rest of the tracer already
does for span cleanup - redis_utils, valkey_utils, elasticsearch, asgi,
pyramid, tornado and tracer.py all do the same. _wrap_getresponse already used
try/finally and was unaffected.

The two tests raise a real BlockingException from inside the wrapped call, so
they need no network and no appsec setup. Both fail without the fix.

contrib::httplib green on py3.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against main using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

No remaining files require a CODEOWNERS review.

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 4, 2026

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 3 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.llmobs -> ddtrace.llmobs._evaluators -> ddtrace.llmobs._evaluators.format -> ddtrace.llmobs._experiment -> ddtrace.llmobs
ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector
ddtrace.appsec._asm_request_context -> ddtrace.appsec._iast._iast_request_context_base -> ddtrace.appsec._iast._iast_env -> ddtrace.appsec._iast.reporter -> ddtrace.appsec._exploit_prevention.stack_traces -> ddtrace.appsec._asm_request_context

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 4, 2026

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 230 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 230 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=134)
ddtrace.llmobs._integrations.openai_agents -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)
ddtrace.debugging._signal.model -×-> ddtrace.trace  (product:debugging -> product:tracing, score=132)
ddtrace.internal.opentelemetry.span -×-> ddtrace.trace  (product:opentelemetry -> product:tracing, score=132)
ddtrace.aiguard._api_client -×-> ddtrace.trace  (product:aiguard -> product:tracing, score=132)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

avara1986 added a commit that referenced this pull request Sep 4, 2026
…877]

The httplib span leak is a pre-existing bug in shared integration code, not
part of this migration: on main it already strands the span whenever appsec
patched http.client before the integration did. Only the second order was safe,
because appsec's wrapt wrapper then sat outside _wrap_request and the block
raised before the span existed.

It belongs on its own, so it moves to #20073 (APPSEC-70027) with tests that
raise a real BlockingException from inside the wrapped call and therefore need
neither the network nor appsec. The regression test added here goes with it -
its contract is the same one those two cover, and keeping a copy that cannot
pass until #20073 lands would only make this branch red.

This branch now depends on #20073: a wrapping context lives in the target's own
bytecode, so it is always innermost and the block always raises inside the
span, which removes the order that used to avoid the leak. Merge main in once
#20073 has landed.

test_common_modules 95 passed on py3.13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@avara1986
avara1986 marked this pull request as ready for review September 4, 2026 12:27
@avara1986
avara1986 requested review from a team as code owners September 4, 2026 12:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf12a2503d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ddtrace/contrib/internal/httplib/patch.py Outdated
Comment thread ddtrace/contrib/internal/httplib/patch.py Outdated
Comment thread ddtrace/contrib/internal/httplib/patch.py Outdated
…arly [APPSEC-70027]

Connections are reused, and _wrap_putrequest adopts whatever _datadog_span the
connection already carries. Only _wrap_getresponse ever detached it, so all four
exception handlers finished the span and left it attached: the next request on
that connection then reused an already-finished span and reported nothing.

Reproduced before fixing:

  assert not hasattr(conn, "_datadog_span")
  E   assert not True

The four handlers had drifted into four slightly different copies of finish-the
-span, so they collapse into one _finish_span helper that also detaches. That
covers the two configuration-error handlers as well as the two around the
wrapped call, since the defect was identical in all of them.

Also reworks the BaseException comment. It described what the old code got
wrong, which stops making sense once this is merged, so it now states the
invariant instead, and carries the AIDEV-NOTE anchor that AGENTS.md asks for on
non-obvious lifecycle knowledge.

The two existing tests read the span back off the connection, which no longer
works now that it is detached; they assert through pop_spans instead, which is
the better check anyway.

contrib::httplib 102 passed on py3.12. No-oping the delattr fails the new test.

Reported by christophe-papazian, dubloom and Codex on #20073.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit df7be7f into main Sep 4, 2026
411 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the alberto.vara/httplib-span-basexception-on-block branch September 4, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants