fix(tools/web): http_request works on Python 3.13; flag https jku/x5u#414
Closed
VoidChecksum wants to merge 1 commit into
Closed
fix(tools/web): http_request works on Python 3.13; flag https jku/x5u#414VoidChecksum wants to merge 1 commit into
VoidChecksum wants to merge 1 commit into
Conversation
http_request drove its coroutine via
`asyncio.get_event_loop().run_until_complete(_do())`. On the pinned
Python 3.13 runtime this raises in both reachable contexts: with no
current loop it errors ("There is no current event loop"), and inside
the agent's running loop it errors ("This event loop is already
running"). Every call therefore returned an opaque RuntimeError, making
the tool dead on the supported runtime.
Introduce a `_run_coro` helper that runs a coroutine safely regardless
of context: it uses `asyncio.run` directly when no loop is running, and
otherwise drives the coroutine on a fresh single-worker thread that owns
its own loop (via ThreadPoolExecutor + asyncio.run). http_request now
calls this helper, preserving the existing try/except and return shape.
http_request is the only tool in this module using the broken pattern.
The JWT jku key-confusion check only fired when `jku` did not start with
`https://`, so the dominant attack — `jku=https://attacker.com/jwks.json`
— was treated as safe, and `x5u` was parsed but never checked. The host
is attacker-influenced regardless of scheme, so emit a finding whenever
`jku` or `x5u` is present, and add the symmetric `x5u` check.
Tests: http_request returns a parsed result without a RuntimeError from
both a plain sync context and from inside a running event loop (and a
direct `_run_coro` guard while a loop is active); JWT parsing flags both
`https` jku and `https` x5u headers.
|
|
||
| @pytest.fixture | ||
| def patched_session(monkeypatch: pytest.MonkeyPatch) -> None: | ||
| monkeypatch.setattr(tools, "_get_session", lambda: _FakeSession()) |
VoidChecksum
added a commit
that referenced
this pull request
May 30, 2026
Folds the unique JWT enhancement from #414 into this PR: flag ANY jku header (not only non-HTTPS — an attacker-influenced https JWKS URL is still a key-confusion vector) and add the previously-missing x5u (X.509 URL) check. Uses the string-safe coercion from this PR's non-string-header fix so non-string jku/x5u values still can't crash parse_token. Adds regression tests. Consolidates #414's jwt change; #414's http_request part is already covered by #358.
Collaborator
Author
|
Closing and consolidating. This PR mixed two concerns:
The CodeQL 'unnecessary lambda' alert that was blocking this PR was in the http_request test, which is dropped. Merge #358 (http_request) + #390 (jwt) instead. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two correctness/security fixes in the web exploitation toolkit, with tests.
1.
http_requestwas dead on Python 3.13 (tools/web/tools.py)http_requestdrove its coroutine viaasyncio.get_event_loop().run_until_complete(_do()). On the pinned Python 3.13 runtime this raises in both reachable contexts:RuntimeError: There is no current event loopRuntimeError: This event loop is already runningSo every call returned an opaque
{"error": "RuntimeError ..."}— the tool was unusable on the supported runtime.Fix: a small
_run_corohelper that runs a coroutine safely regardless of context —asyncio.rundirectly when no loop is running, otherwise driven on a fresh single-worker thread that owns its own loop (ThreadPoolExecutor+asyncio.run).http_requestnow calls the helper and keeps its existing try/except + return shape. It was the only tool in the module using the broken pattern.2. JWT
jku/x5ukey-confusion under-reported (tools/web/jwt.py)The
jkucheck only fired whenjkudid not start withhttps://, so the dominant attack —jku=https://attacker.com/jwks.json— was treated as safe, andx5uwas parsed but never checked. The referenced host is attacker-influenced regardless of scheme.Fix: emit a finding whenever
jkuorx5uis present, and add the symmetricx5ucheck. Existing finding-text style preserved.Tests (
tests/unit/web/)test_tools.py:http_requestreturns a parsed result without aRuntimeErrorfrom (a) a plain sync context and (b) inside a running event loop, plus a direct_run_coroguard asserting it works while a loop is active. Outbound HTTP is mocked by patching_get_session.test_jwt.py: a token withjku=https://attacker.com/jwks.jsonyields a finding; a token withx5u=https://attacker.com/cert.pemyields a finding.Verification
ruff check(4 files): All checks passedruff format --check(4 files): already formattedbasedpyright(both source files): 0 errors (2 pre-existing warnings inhttp_history, unrelated)pytest packages/decepticon/tests/unit/web -q: 42 passed