← back to CelebritySignatures
SECURITY (HOLE 5, blocks live money): paid download link no longer permanent — a Stripe session stays paid forever, so a shared ?sid= URL was infinite free re-download. Now gated to a 1-HOUR window from purchase + max 3 serves/session (persisted download-sids.json), then 410 → contact support. Allows legit retry, kills permanent sharing. Prereq for the live-key flip (TK-10181)
d8cfb50f63fc430176d3db63f887d203dc8de83c · 2026-08-04 10:25:44 -0700 · Steve Abrams
Files touched
Diff
commit d8cfb50f63fc430176d3db63f887d203dc8de83c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 4 10:25:44 2026 -0700
SECURITY (HOLE 5, blocks live money): paid download link no longer permanent — a Stripe session stays paid forever, so a shared ?sid= URL was infinite free re-download. Now gated to a 1-HOUR window from purchase + max 3 serves/session (persisted download-sids.json), then 410 → contact support. Allows legit retry, kills permanent sharing. Prereq for the live-key flip (TK-10181)
---
.gitignore | 1 +
server.js | 16 +++++++++++++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 44c588f..8ae9c19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,4 @@ data/download-ledger.jsonl
data/admin-token.txt
data/leaderboard.json
.env
+data/download-sids.json
diff --git a/server.js b/server.js
index 49401c8..a25a670 100644
--- a/server.js
+++ b/server.js
@@ -423,13 +423,27 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
const sid = url.searchParams.get('sid') || '';
if (!STRIPE_TEST_KEY) return sendJSON(res, 503, { ok: false, error: 'downloads require payment — not configured yet' });
if (!/^cs_test_[A-Za-z0-9]+$/.test(sid)) return sendJSON(res, 402, { ok: false, error: 'payment required — purchase this download first', purchase: '/api/signature-checkout' });
- let paid = false;
+ let paid = false, sessionCreated = 0;
try {
const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${STRIPE_TEST_KEY}` } })).json();
// gate fulfillment on the settled session (status===complete), not just payment_status
paid = s.status === 'complete' && s.payment_status === 'paid' && String(s.metadata?.upload_id) === x.id;
+ sessionCreated = s.created || 0;
} catch {}
if (!paid) return sendJSON(res, 402, { ok: false, error: 'payment not verified' });
+ // HOLE 5 FIX: a paid Stripe session stays paid forever, so a shared ?sid= URL
+ // would be permanent free re-download. Gate the file to a SHORT WINDOW (1h from
+ // purchase) AND a small per-session use cap (3), so a leaked/shared link dies
+ // fast but a buyer whose download drops can retry. Beyond that → 410, contact support.
+ if (sessionCreated && (Date.now() / 1000 - sessionCreated) > 3600) {
+ return sendJSON(res, 410, { ok: false, error: 'this download link has expired (1-hour window) — contact info@designerwallcoverings.com to re-download' });
+ }
+ const sidUse = await load('download-sids.json', {});
+ if ((sidUse[sid] || 0) >= 3) {
+ return sendJSON(res, 410, { ok: false, error: 'this download link has been used its maximum times — contact info@designerwallcoverings.com to re-download' });
+ }
+ sidUse[sid] = (sidUse[sid] || 0) + 1;
+ await store('download-sids.json', sidUse);
// Idempotent credit: claim the sid ATOMICALLY in-process (closes the concurrent
// double-credit race — has+add are synchronous, no await between them), then confirm
// against the persistent ledger so a restart can't re-credit an already-paid sid.
← f2fc2ab payments(TEST): Cody gate fixes — atomic USED_SIDS guard (cl
·
back to CelebritySignatures
·
Live-mode switch (prep for go-live, defaults OFF): STRIPE_LI cc656ee →