🛡️ Sentinel: [CRITICAL] Fix SyntaxError in httpx event hooks

Fixed a `SyntaxError: keyword argument repeated` in `app/api/url_upload.py` where `event_hooks` was being passed twice as a keyword argument to `httpx.AsyncClient`. Combined both SSRF redirect validation hooks into a single list `event_hooks={"response": [validate_redirect, verify_redirect]}`. This restores critical SSRF protections via redirect validation and fixes the application crash.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot]
2026-05-15 02:57:37 +00:00
parent 58b14ae769
commit 54f2b23075
2 changed files with 5 additions and 2 deletions
+4
View File
@@ -28,3 +28,7 @@
**Vulnerability:** The `/process-url` endpoint used `httpx.AsyncClient(follow_redirects=True)` after validating the initial user-provided URL against SSRF protections. However, it did not validate the target URLs of any subsequent HTTP redirects, allowing an attacker to provide a safe URL that redirects to an internal/private IP, bypassing the security check.
**Learning:** Initial URL validation is insufficient when the HTTP client is configured to follow redirects automatically. The client must be explicitly configured to validate every redirect target.
**Prevention:** When using `httpx.AsyncClient(follow_redirects=True)` for user-provided URLs, always implement a redirect validator hook function (e.g., using `event_hooks={'response': [validate_redirect]}`) that resolves the `Location` header and passes it through the same SSRF validation logic before the redirect is followed.
## 2026-03-27 - SyntaxError: keyword argument repeated in httpx event hooks
**Vulnerability:** The `/process-url` endpoint in `app/api/url_upload.py` initialized `httpx.AsyncClient` with the `event_hooks` keyword argument twice. This caused a Python SyntaxError, effectively crashing the API endpoint and preventing any execution.
**Learning:** Python does not allow duplicate keyword arguments. In scenarios where multiple hooks (like local and module-level SSRF interceptors) must be provided to a client, they must be merged into a single list value.
**Prevention:** Combine multiple callables for the same event key into a single list, e.g., `event_hooks={"response": [hook1, hook2]}`.
+1 -2
View File
@@ -194,11 +194,10 @@ async def process_url(
async with httpx.AsyncClient(
timeout=settings.http_request_timeout,
follow_redirects=True,
event_hooks={"response": [validate_redirect]},
event_hooks={"response": [validate_redirect, verify_redirect]},
headers={
"User-Agent": "DocuElevate/1.0", # Identify ourselves
},
event_hooks={"response": [verify_redirect]},
) as client:
async with client.stream("GET", url) as response:
response.raise_for_status()