From 71ab3ea59d34b03c10555f7d03782abb2b12f31d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 08:26:35 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20insecure=20random=20code=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: `Math.random()` was being used for generating sensitive tokens (coupon codes and store credit codes). This poses a risk as `Math.random()` generates easily predictable outcomes, meaning an attacker could easily guess valid codes. 🎯 Impact: Attackers could predict valid coupon or store credit codes and exploit them for unauthorized discounts or account balance increments. 🔧 Fix: Added a `generateSecureCode(prefix, length)` utility in `lib/utils.ts` utilizing `crypto.getRandomValues()`. Updated both generation usages across `app/api/bargain/route.ts` and `lib/actions/admin.ts`. Logged learning to `.jules/sentinel.md`. ✅ Verification: Tested random string generation locally, ran all Next.js builds and bun tests. No new test regressions were introduced. Co-authored-by: f4teless <60130665+f4teless@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ app/api/bargain/route.ts | 8 ++------ lib/actions/admin.ts | 7 ++----- lib/utils.ts | 11 +++++++++++ 4 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..0687906 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-02-20 - Insecure Random Number Generation for Coupon Codes +**Vulnerability:** Found `Math.random()` being used to generate coupon codes and store credit codes in `app/api/bargain/route.ts` and `lib/actions/admin.ts`. `Math.random()` is not cryptographically secure and its outputs can be predicted. +**Learning:** For any code that generates sensitive tokens like coupon codes, authentication tokens, or store credits, we need to ensure the source of randomness is cryptographically secure. The use of `Math.random()` in Next.js backend actions can expose financial endpoints to brute-force attacks if code generation becomes predictable. +**Prevention:** Use the Web Crypto API (`crypto.getRandomValues()`) or the Node.js `crypto` module (`crypto.randomBytes()`) for generation. Created a `generateSecureCode(prefix, length)` utility in `lib/utils.ts` to be used for all code generation. diff --git a/app/api/bargain/route.ts b/app/api/bargain/route.ts index cd182da..940c89b 100644 --- a/app/api/bargain/route.ts +++ b/app/api/bargain/route.ts @@ -4,6 +4,7 @@ import { db, user, coupons, bargainSessions, products, combos } from "@/lib/db"; import { and, eq, inArray } from "drizzle-orm"; import { headers } from "next/headers"; import { auth } from "@/lib/auth"; +import { generateSecureCode } from "@/lib/utils"; export const maxDuration = 30; const MAX_NEGOTIATION_ROUNDS = 10; @@ -20,12 +21,7 @@ type BargainCartItem = { // Generate unique coupon code function generateCouponCode(): string { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - let code = "BRG-"; - for (let i = 0; i < 6; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return code; + return generateSecureCode("BRG-", 6); } function calculateCartRuleCap(cartTotal: number, isFirstTimeUser: boolean): number { diff --git a/lib/actions/admin.ts b/lib/actions/admin.ts index 54c3848..9a1c424 100644 --- a/lib/actions/admin.ts +++ b/lib/actions/admin.ts @@ -5,6 +5,7 @@ import { products, productVariants, coupons, orders, orderItems } from "@/lib/db import { requireAdmin } from "@/lib/auth-server"; import { eq, desc, sql, and, gte } from "drizzle-orm"; import { revalidatePath } from "next/cache"; +import { generateSecureCode } from "@/lib/utils"; // ============================================ // PRODUCT ACTIONS @@ -567,11 +568,7 @@ export async function issueStoreCredit(data: IssueStoreCreditInput) { const creditAmount = Math.round(data.refundAmount * bonusMultiplier); // Generate unique store credit code - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - let code = "CREDIT-"; - for (let i = 0; i < 8; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } + const code = generateSecureCode("CREDIT-", 8); // Set validity (30-60 days) const validityDays = Math.min(Math.max(data.validityDays || 30, 30), 60); diff --git a/lib/utils.ts b/lib/utils.ts index bd0c391..372f2a1 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -4,3 +4,14 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } + +export function generateSecureCode(prefix: string, length: number): string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const array = new Uint32Array(length); + crypto.getRandomValues(array); + let code = prefix; + for (let i = 0; i < length; i++) { + code += chars[array[i] % chars.length]; + } + return code; +}