← back to Lawyer Directory Builder
upgrade.ts: harden ?_demo=paid gate against Host-header spoofing
85d7704a36bb303573349d1da5e95ede6405a967 · 2026-05-04 01:19:26 -0700 · SteveStudio2
Replaced req.hostname check (spoofable when Express has 'trust proxy' or
when reverse proxies forward arbitrary Host headers) with strict
NODE_ENV !== 'production' gate. Local-dev preview still works on dev
machines; production traffic can no longer trigger the paid branch via
crafted Host header.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 85d7704a36bb303573349d1da5e95ede6405a967
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon May 4 01:19:26 2026 -0700
upgrade.ts: harden ?_demo=paid gate against Host-header spoofing
Replaced req.hostname check (spoofable when Express has 'trust proxy' or
when reverse proxies forward arbitrary Host headers) with strict
NODE_ENV !== 'production' gate. Local-dev preview still works on dev
machines; production traffic can no longer trigger the paid branch via
crafted Host header.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
src/server/upgrade.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/server/upgrade.ts b/src/server/upgrade.ts
index 9ae7d16..d2f0fac 100644
--- a/src/server/upgrade.ts
+++ b/src/server/upgrade.ts
@@ -301,10 +301,11 @@ router.post('/upgrade', async (req, res) => {
router.get('/upgrade/thanks', async (req, res) => {
const sid = (req.query.session_id as string) || '';
- // Local-dev preview: ?_demo=paid forces the paid branch when accessed from
- // a loopback host. Never matches the public domain.
- const isLocal = req.hostname === 'localhost' || req.hostname === '127.0.0.1';
- const demoPaid = req.query._demo === 'paid' && isLocal;
+ // Local-dev preview: ?_demo=paid forces the paid branch in non-prod.
+ // SECURITY: was checking req.hostname which is spoofable via Host header
+ // (especially with `trust proxy`). Now gated on NODE_ENV !== 'production'.
+ const isNonProd = process.env.NODE_ENV !== 'production';
+ const demoPaid = req.query._demo === 'paid' && isNonProd;
let paid = demoPaid;
let orderId: number | null = demoPaid ? 1 : null;
let amountPaid = 49900;
← bf9516d Iter 25: /upgrade/thanks — post-Stripe-success editorial con
·
back to Lawyer Directory Builder
·
Iter 26: /audit.html — champagne-tier scoring + critique-app 95b1ccc →