From 36c875ab2370ce24d97690c4fbf30446b0a7ff47 Mon Sep 17 00:00:00 2001 From: Shekhar Vaidya Date: Thu, 18 Jun 2026 16:12:36 +0530 Subject: [PATCH] fix: use extractRawExpiry for /contribute expiry validation parseLinkExpiry has a 1h safety floor that causes it to treat already- expired URLs as valid. Replace with extractRawExpiry + direct se timestamp comparison so any URL expiring < 1h out is rejected. Co-Authored-By: Claude Sonnet 4.6 --- backend/main.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/main.go b/backend/main.go index e0d4328..1cbeb06 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1274,13 +1274,22 @@ func handleContribute(w http.ResponseWriter, r *http.Request) { return } - // Validation 3: expiry must be > 1 hour out - expiresAt := parseLinkExpiry(raw) - if time.Until(expiresAt) < time.Hour { - log.Printf("contribute: product_id=%s sku_id=%s -> rejected (expiry < 1h)\n", productID, skuID) + // Validation 3: expiry must be > 1 hour out. + // Use extractRawExpiry (direct se timestamp) — NOT parseLinkExpiry, which has + // a 1h safety floor that would accept already-expired URLs. + seStr := extractRawExpiry(raw) + if seStr == "" { + log.Printf("contribute: product_id=%s sku_id=%s -> rejected (no valid se param)\n", productID, skuID) + respondJSONError(w, http.StatusUnprocessableEntity, "no valid signed expiry found in download URL") + return + } + seTime, _ := time.Parse(time.RFC3339, seStr) + if time.Until(seTime) < time.Hour { + log.Printf("contribute: product_id=%s sku_id=%s -> rejected (expiry < 1h: %s)\n", productID, skuID, seStr) respondJSONError(w, http.StatusUnprocessableEntity, "link expires in less than 1 hour") return } + expiresAt := parseLinkExpiry(raw) // cache TTL (se minus 30m buffer + jitter) // Validation 4: all download URLs must be from allowed Microsoft CDN hosts var data map[string]interface{}