Skip to content

fix: prevent header duplication & guard JSON parsing in auth proxy#33

Closed
danielbrunt57 wants to merge 9 commits intoalandtse:mainfrom
danielbrunt57:Prevent-header-“growth”-by-mutation

Hidden character warning

The head ref may contain hidden characters: "Prevent-header-\u201cgrowth\u201d-by-mutation"
Closed

fix: prevent header duplication & guard JSON parsing in auth proxy#33
danielbrunt57 wants to merge 9 commits intoalandtse:mainfrom
danielbrunt57:Prevent-header-“growth”-by-mutation

Conversation

@danielbrunt57
Copy link
Copy Markdown
Contributor

@danielbrunt57 danielbrunt57 commented Jan 29, 2026

Fixes runaway header duplication during auth flow by isolating header mutation and guarding JSON parsing.
Resolves CloudFront 500 / Amazon error page during login.

Resolves: #3308

Summary by CodeRabbit

  • New Features

    • Optional session factory and header-preservation configuration.
    • Background task tracking to ensure clean start/stop of proxy operations.
  • Bug Fixes

    • Safer JSON parsing only for JSON content-types; graceful handling of malformed JSON.
    • Request header isolation to prevent cross-request mutation.
    • Improved and clearer error handling for connection, timeout, and redirect scenarios.
    • More reliable multipart/content-type detection and logging.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 29, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Walkthrough

Constructor extended to accept session_factory and preserve_headers. JSON decoding is only attempted for JSON content-type requests and failures are caught. Incoming headers are shallow-copied to req_headers to avoid mutation. Background tasks are tracked; HTTPX error handling and multipart/content-type checks updated.

Changes

Cohort / File(s) Summary
Core proxy logic
authcaptureproxy/auth_capture_proxy.py
Constructor signature updated to include session_factory and preserve_headers; added _background_tasks: set[asyncio.Task].
Request parsing & header handling
authcaptureproxy/auth_capture_proxy.py
Shallow-copy incoming headers to req_headers; remove proxy-specific headers from the copy; pass headers=req_headers to outbound requests; guard JSON decoding by Content-Type and catch JSONDecodeError/ValueError.
Error handling & multipart checks
authcaptureproxy/auth_capture_proxy.py
Replaced/expanded exception handling to use httpx.ConnectError, httpx.TimeoutException, httpx.TooManyRedirects, and httpx.HTTPError. Multipart/content-type checks now rely on hdrs.CONTENT_TYPE.
Proxy/host swapping & misc
authcaptureproxy/auth_capture_proxy.py
Small adjustments in _swap_proxy_and_host and access_url/path construction; logging updated to reference req_headers; docstrings/comments trimmed/updated.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Proxy as AuthCaptureProxy
    participant Upstream
    participant BG as BackgroundTaskMgr

    Client->>Proxy: Send HTTP request (headers + body)
    Proxy->>Proxy: Make shallow copy -> req_headers
    Proxy->>Proxy: Inspect req_headers Content-Type
    alt Content-Type is JSON
        Proxy->>Proxy: try parse JSON (catch JSONDecodeError/ValueError)
    else
        Proxy->>Proxy: preserve raw/form data or multipart handling
    end
    Proxy->>Upstream: Forward request with headers=req_headers and body
    Upstream-->>Proxy: Return response
    Proxy->>BG: Register/track any stop/start background task
    Proxy-->>Client: Return response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A header copied, safe and clean,
JSON peeked where content's seen.
Background tasks now tracked in flight,
Timeouts caught, redirects in sight. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the two main changes: preventing header duplication and guarding JSON parsing in the authentication proxy.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@danielbrunt57 danielbrunt57 changed the title fix: guard JSON parsing, avoid header mutation, handle timeouts fix: prevent header duplication & guard JSON parsing in auth proxy Jan 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
authcaptureproxy/auth_capture_proxy.py (1)

467-474: Catch httpx timeout exceptions.

Request timeouts from httpx will bubble up uncaught and cause 500 errors. Add exception handler for httpx.TimeoutException to return a friendly error response.

Suggested fix
         except ClientConnectionError as ex:
             return await self._build_response(
                 text=f"Error connecting to {site}; please retry: {ex}"
             )
+        except httpx.TimeoutException as ex:
+            return await self._build_response(
+                text=f"Error connecting to {site}; request timed out: {ex}"
+            )
         except TooManyRedirects as ex:
             return await self._build_response(
                 text=f"Error connecting to {site}; too may redirects: {ex}"
             )
🤖 Fix all issues with AI agents
In `@authcaptureproxy/auth_capture_proxy.py`:
- Line 6: When JSON decoding of the incoming aiohttp Request fails, the code
currently drops the body; instead read and cache the raw bytes via await
request.read() (which is safe because aiohttp caches reads) and set a raw_body
variable; then in the dispatch branch that currently sends an empty body, change
it to call getattr(self.session, method)(site, content=raw_body,
headers=req_headers, follow_redirects=True) so the proxied request forwards the
original bytes; update the logic around JSONDecodeError handling and the branch
that checks raw_body/request body to use this raw_body when present (referencing
request, raw_body, self.session, method, req_headers, and site).

Refactor error handling for connection issues in auth_capture_proxy.py to provide more specific error messages.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@authcaptureproxy/auth_capture_proxy.py`:
- Around line 467-478: The except blocks for ClientConnectionError,
httpx.TimeoutException, and TooManyRedirects in the function using
self._build_response have inconsistent indentation causing a syntax error and
also contain a typo; fix the indentation so all except clauses align with the
surrounding try/except block (make the three except lines and their return await
self._build_response(...) blocks use the same indentation level), and correct
the message in the TooManyRedirects handler from "too may redirects" to "too
many redirects".

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@authcaptureproxy/auth_capture_proxy.py`:
- Line 403: The call to asyncio.create_task(self.stop_proxy(3)) can be
garbage-collected; modify the class so __init__ defines a container like
self._background_tasks: set[asyncio.Task] = set(), then when scheduling use task
= asyncio.create_task(self.stop_proxy(3)) and add it to self._background_tasks;
also add a done callback to remove the task from self._background_tasks (or
await/cleanup elsewhere) so the task reference is retained until completion and
won't be GC'd; refer to the stop_proxy coroutine, __init__, and the new
_background_tasks attribute when making this change.
- Around line 398-399: The regex used for path normalization mistakenly uses r"/
+" (slash + space) instead of collapsing consecutive slashes; update the pattern
to r"/+" wherever it's used (specifically in the comparisons that normalize
self._proxy_url.with_path(...) — e.g., inside the conditional comparing
URL(str(request.url)).path with re.sub(...)) so that multiple adjacent slashes
are reduced to one; apply the same change to the other two occurrences noted
(the checks at the other two similar comparisons referenced in the comment).

- Fix regex pattern for path normalization: r"/ +" should be r"/+".
- Add an instance variable to hold background tasks.
@danielbrunt57
Copy link
Copy Markdown
Contributor Author

danielbrunt57 commented Jan 29, 2026

2026-01-29 13:06:52.686 DEBUG (MainThread) [authcaptureproxy.helper] Running function replace_empty_url

2026-01-29 13:06:53.742 DEBUG (MainThread) [authcaptureproxy.auth_capture_proxy] Received post: http://192.168.2.46:8123/auth/alexamedia/proxy/verify for https://www.amazon.com/ap/register?openid.return_to=https://www.amazon.com/ap/maplanding&openid.assoc_handle=amzn_dp_project_dee_ios&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&pageId=amzn_dp_project_dee_ios&accountStatusPolicy=P1&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns.oa2=http://www.amazon.com/ap/ext/oauth/2&openid.oa2.client_id=device:393662306139663732303137363363386131346232623833343936336534333832333431333234393536346335363335353634643332353733383331&openid.ns.pape=http://specs.openid.net/extensions/pape/1.0&openid.oa2.response_type=code&openid.ns=http://specs.openid.net/auth/2.0&openid.pape.max_auth_age=0&openid.oa2.scope=device_auth_access+offline_access&openid.oa2.code_challenge_method=S256&openid.oa2.code_challenge=8ekpeF4bNDL0MhmKaWjgM-Evr3llIEUjB5-E&language=en_GB

2026-01-29 13:06:53.742 DEBUG (MainThread) [authcaptureproxy.auth_capture_proxy] Storing data {'cvf_aamation_response_token': 'staticSessionToken', 'cvf_captcha_captcha_action': 'verifyAamationChallenge', 'cvf_aamation_error_code': 'NetworkError', 'clientContext': '133-6173413-1644700', 'openid.pape.max_auth_age': '0', 'forceMobileLayout': '1', 'accountStatusPolicy': 'P1', 'openid.identity': 'http://specs.openid.net/auth/2.0/identifier_select', 'isSHuMAAuthenticable': 'false', 'language': 'en_GB', 'pageId': 'amzn_dp_project_dee_ios', 'openid.return_to': 'https://www.amazon.com/ap/maplanding', 'openid.assoc_handle': 'amzn_dp_project_dee_ios', 'openid.oa2.response_type': 'code', 'openid.mode': 'checkid_setup', 'openid.ns.pape': 'http://specs.openid.net/extensions/pape/1.0', 'openid.ns.oa2': 'http://www.amazon.com/ap/ext/oauth/2', 'openid.oa2.code_challenge_method': 'S256', 'openid.oa2.code_challenge': '7Ig8ekpeF4bSbQBNDL0MhmKaWjgM-Evr3llIEUjB5-E', 'openid.oa2.scope': 'device_auth_access offline_access', 'openid.claimed_id': 'http://specs.openid.net/auth/2.0/identifier_select', 'openid.oa2.client_id': 'device:393662306139663732303137363363386131346232623833343936336534333832333431333234393536346335363335353634643332353733383331', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'verifyToken': 'd9-3373-9a96-'}

2026-01-29 13:06:53.743 DEBUG (MainThread) [authcaptureproxy.auth_capture_proxy] Final headers {'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Origin': 'https://www.amazon.com', 'DNT': '1', 'Upgrade-Insecure-Requests': '1', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'en-GB,en;q=0.9,de;q=0.8,nl;q=0.7,mk;q=0.6'}

2026-01-29 13:06:53.743 DEBUG (MainThread) [authcaptureproxy.auth_capture_proxy] Attempting post to https://www.amazon.com/verifyheaders: {'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Origin': 'https://www.amazon.com', 'DNT': '1', 'Upgrade-Insecure-Requests': '1', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'en-GB,en;q=0.9,de;q=0.8,nl;q=0.7,mk;q=0.6'} cookies: <CookieJar[<Cookie lc-main=en_GB for .amazon.com/>, <Cookie session-id=131-4372066-6237459 for .amazon.com/>, <Cookie session-id-time=2082787201l for .amazon.com/>, <Cookie ubid-main=133-6173413-1644700 for .amazon.com/>, <Cookie session-token=****** for .amazon.com/>]>

2026-01-29 13:06:54.013 INFO (MainThread) [httpx] HTTP Request: POST https://www.amazon.com/verify "HTTP/1.1 301 Moved Permanently"

@danielbrunt57 danielbrunt57 deleted the Prevent-header-“growth”-by-mutation branch January 31, 2026 08:23
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.

1 participant