fix(exit-node): fix double-wrapped response envelope and panic in error paths#1209
fix(exit-node): fix double-wrapped response envelope and panic in error paths#1209CaptainMirage wants to merge 3 commits into
Conversation
…herealaleph#1022) Fixes therealaleph#1022 -- exit-node-routed requests returned raw {s,h,b} JSON to the browser instead of actual page content. Root cause: Code.gs had no raw-return handler. The Rust client sets r:true on the outer Apps Script request to signal verbatim passthrough, but without a matching branch Code.gs was wrapping the exit node's {s,h,b} response in a second {s,h,b} envelope. parse_exit_node_response() peeled one layer and handed the inner {s,h,b} JSON string to the browser as the body. Code.gs (_doSingle): - add req.r === true branch returning resp.getContentText() verbatim via ContentService before the normal {s,h,b} wrap - _buildOpts: followRedirects is now unconditionally true; r controls raw-return mode only, not redirect following domain_fronter.rs (parse_exit_node_response): - scan for \r\n\r\n separator and skip any HTTP framing prefix before JSON parsing; some Apps Script edge nodes prepend HTTP headers to the response body - add content-encoding to SKIP_RESPONSE_HEADERS; exit node fetch() auto-decompresses so forwarding the header causes Content Encoding Error in the browser
…paths Four error format strings truncated a &str at byte offset 200 using &text[..text.len().min(200)]. When the response body contains multi-byte UTF-8 characters (quota error HTML, brotli-compressed or binary Apps Script responses, cold-start warning pages), byte offset 200 can fall inside a character boundary, causing a panic and SIGILL core dump. Replace byte-offset truncation with char-aware truncation via .chars().take(200).collect::<String>() at all four sites: - parse_relay_json: "no json in" and "no json end in" messages - finalize_tunnel_response: "no json in tunnel response" message - finalize_batch_response: "no json in batch response" message Reproducible under normal operating conditions when Apps Script returns a non-JSON body under quota pressure or transient errors.
|
Reviewed via Anthropic Claude. Two real fixes I want to land, mixed with one regression I don't. Good (want to keep):
Regression (need to fix before merge):
Without the try/catch, those throws propagate unhandled, Apps Script serves the default HTML error page, and the client surfaces Diagnostic logging (need to remove before merge):
Suggested action: If you'd like, I can split this PR's changes — keep (1), (2), (3) and drop (4)'s try/catch removal + (5) the EXIT_DIAG warnings. Or you can do it on your branch:
After that, this lands. Re: #1022 root cause — agree it was double-wrapping. The parse-side fix in (2) is the right architectural shape because it doesn't require a Code.gs/client coordinated upgrade. [reply via Anthropic Claude | reviewed by @therealaleph] |
|
i kinda didnt realized i removed that i was behind the main branch lol sorry, restored that one and removed the debug stuff, tho i would say it wouldnt clutter the info thing since i did put it in debug trace not warn traces but still not that useful outside of this fix so removed it anyway |
therealaleph
left a comment
There was a problem hiding this comment.
Thanks for the fast follow-up here. The two underlying fixes make sense to me, especially the char-aware truncation in the error paths.
One blocker before I can merge this: the current diff still contains the temporary EXIT_DIAG block in src/domain_fronter.rs:
tracing::debug!(
"EXIT_DIAG app_body len={} first_200={:?}",
app_body.len(),
String::from_utf8_lossy(&app_body[..app_body.len().min(200)])
);That can put the first bytes of proxied response content into logs. In the exit-node path that may be real destination content, not just relay metadata, so we should not ship it even at debug. The PR body/commit message says this was removed, but the patch still has it.
Please drop that diagnostic block and leave the direct parse_exit_node_response(&app_body) call. After that I can rerun local verification and merge if the rest stays clean.
[reply via Anthropic Claude | reviewed by @therealaleph]
Fixed two bugs in the exit-node relay path.
Commit 1 fixes #1022 — Code.gs was double-wrapping the exit node's
{s,h,b} response envelope, causing browsers to receive raw JSON instead
of page content. Adds the raw-return mode (req.r === true) to _doSingle
and fixes parse_exit_node_response to handle HTTP framing prefixes and
stale content-encoding headers.
Commit 2 fixes a panic (SIGILL/core dump) in four error format strings
that sliced a &str at byte offset 200, which crashes when the response
contains multi-byte UTF-8 characters (quota error pages, binary responses).
Replaced with char-aware truncation.
Tested against claude.ai, chatgpt.com, openai.com, perplexity.ai and general browsing with both
WARP and direct exit-node modes.