From 43dc1fb50a7bce1f903c2f66b7bf691e19f7a100 Mon Sep 17 00:00:00 2001 From: msuitcase <97645156+msuitcase@users.noreply.github.com> Date: Fri, 18 Sep 2026 01:31:30 -0700 Subject: [PATCH] Send card details to Stripe in the request body, not the URL `ReferralCustomer.addCreditCard` built the `POST /v1/tokens` request with the card number, expiry and CVC in the URL query string. Stripe accepts the same form-encoded fields in the request body, which keeps cardholder data out of access logs, proxy logs, tracing tools and Referer headers. SEC-768 Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 4 ++++ src/services/referral_customer_service.ts | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 776ade4ce..bd5fa2bc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Next Release + +- Sends card details in the request body instead of the URL query string when `ReferralCustomer.addCreditCard` creates a Stripe token + ## v9.0.0-rc.1 (2026-09-08) - Breaking: Node 18+ is now required (built-in `fetch`) diff --git a/src/services/referral_customer_service.ts b/src/services/referral_customer_service.ts index c5ef0ce5c..4b24bf0cb 100644 --- a/src/services/referral_customer_service.ts +++ b/src/services/referral_customer_service.ts @@ -70,13 +70,15 @@ async function _sendCardDetailsToStripe( expirationYear: string, cvc: string, ): Promise { - const searchParams = new URLSearchParams({ + // Card details must travel in the form-encoded request body, never in the URL, + // so they cannot end up in access logs, proxy logs, or Referer headers. + const formBody = new URLSearchParams({ 'card[number]': number, 'card[exp_month]': expirationMonth, 'card[exp_year]': expirationYear, 'card[cvc]': cvc, }); - const url = `https://api.stripe.com/v1/tokens?${searchParams.toString()}`; + const url = 'https://api.stripe.com/v1/tokens'; try { const response = await fetch(url, { @@ -85,6 +87,7 @@ async function _sendCardDetailsToStripe( Authorization: `Bearer ${stripeKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, + body: formBody.toString(), }); if (!response.ok) {