-
Notifications
You must be signed in to change notification settings - Fork 0
feat: transparently support form-encoded bodies (work around T3 host JSON-parse) #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,11 +132,28 @@ async function handle( | |
| } | ||
| } | ||
|
|
||
| // Work around the T3 host egress parsing every request body as JSON (a raw | ||
| // form-encoded body fails with `http.parse_payload: expected value…`). | ||
| // Form-encoded APIs (Stripe, Twilio, AWS query APIs) accept the same params | ||
| // in the query string, so move a form body into the URL and send no body. | ||
| // The agent's code is unchanged — it can POST a normal form and this adapts | ||
| // it. JSON bodies are left untouched (the host parses those fine). | ||
| let outboundUrl = upstream; | ||
| let outboundBody: string | undefined = body.length ? body.toString("utf8") : undefined; | ||
| const contentType = (headers.find(([k]) => k.toLowerCase() === "content-type")?.[1] ?? "").toLowerCase(); | ||
| if (outboundBody && contentType.includes("application/x-www-form-urlencoded")) { | ||
| const u = new URL(outboundUrl); | ||
| for (const [k, v] of new URLSearchParams(outboundBody)) u.searchParams.append(k, v); | ||
| outboundUrl = u.toString(); | ||
| outboundBody = undefined; | ||
|
Comment on lines
+145
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Edge Case: Large form bodies may exceed URL length limits after rewriteThe rewrite appends every form parameter to the URL (proxy.ts:146) with no size check. Servers, proxies, and the T3 host typically enforce URL length limits (commonly ~8 KB). A form body that is comfortably within HTTP body limits (e.g. a Stripe object with many metadata fields, or a long Twilio message Was this helpful? React with 👍 / 👎 |
||
| safeLog("info", { msg: "form_body_to_query", provider: provider.id }); | ||
|
Comment on lines
+145
to
+149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Security: Form params relocated to URL query can leak into access logsMoving the request body into the URL query string (proxy.ts:145-147) changes where potentially sensitive payload data lives. Request bodies are rarely logged, but query strings are routinely captured in host/upstream access logs, TLS-terminating proxies, and error-tracking systems. For these providers the params can include PII and sensitive fields (Stripe customer emails/metadata/tokens, Twilio phone numbers and message Was this helpful? React with 👍 / 👎 |
||
| } | ||
|
Comment on lines
+144
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| const forwardReq: ForwardRequest = { | ||
| method: req.method ?? "GET", | ||
| url: upstream, | ||
| url: outboundUrl, | ||
| headers, | ||
| body: body.length ? body.toString("utf8") : undefined, | ||
| body: outboundBody, | ||
| secret_key: providerSecretKey, | ||
| auth: provider.auth, | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Bug: Forced form content-type can mangle non-form bodies for Stripe/Twilio
providers.ts now injects
content-type: application/x-www-form-urlencodedby default for Stripe and Twilio (providers.ts:117,165). Because the query-rewrite triggers purely on the content-type string (proxy.ts:143-144), any body an agent sends to these providers without its own content-type header will be treated as form-encoded and run throughnew URLSearchParams(outboundBody). If the agent sends a non-form body (e.g. JSON), it will be silently parsed as a single garbage key and appended to the URL, corrupting the request rather than failing clearly. This is low-likelihood since these providers are form-only, but the coupling between the default header and the rewrite condition is fragile — consider gating the rewrite on the provider actually being form-based and/or validating that the body parses as form data.Was this helpful? React with 👍 / 👎