From 0716bebcfc900b0d71ac1c52a1bc938a6614fbb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:02:17 +0000 Subject: [PATCH 1/2] Initial plan From fa0f8c24524995d38e93b4a68874fcd6f579317d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:05:00 +0000 Subject: [PATCH 2/2] Apply code quality and security fixes - Fix whitespace issues in auth-client.tsx - Resolve merge conflicts in prompt-draft.ts - Improve vite.config.ts configuration - Enhance check-redis.ts security and robustness Co-authored-by: leoisadev1 <108278866+leoisadev1@users.noreply.github.com> --- apps/web/src/lib/auth-client.tsx | 3 +- apps/web/src/stores/prompt-draft.ts | 16 +---------- apps/web/vite.config.ts | 11 ++++++-- scripts/check-redis.ts | 44 +++++++++++++++++++++++++---- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/apps/web/src/lib/auth-client.tsx b/apps/web/src/lib/auth-client.tsx index 5b2d8ba4..baf1f9d2 100644 --- a/apps/web/src/lib/auth-client.tsx +++ b/apps/web/src/lib/auth-client.tsx @@ -1,5 +1,5 @@ import { - + createContext, useCallback, useContext, @@ -16,7 +16,6 @@ import { env } from "./env"; import { analytics } from "./analytics"; import type {ReactNode} from "react"; - /** * Better Auth client with Convex integration. */ diff --git a/apps/web/src/stores/prompt-draft.ts b/apps/web/src/stores/prompt-draft.ts index c075f2a6..dfae0fb0 100644 --- a/apps/web/src/stores/prompt-draft.ts +++ b/apps/web/src/stores/prompt-draft.ts @@ -4,29 +4,15 @@ import { createJSONStorage, devtools, persist } from "zustand/middleware"; /** * Store for persisting prompt drafts within the current browser session. * -<<<<<<< HEAD - * Uses sessionStorage instead of localStorage to limit exposure of sensitive - * chat content — data is scoped to the tab/session and not accessible after - * the browser session ends. -||||||| 54e09ce - * Store for persisting prompt drafts across page reloads. -======= * Security: Uses sessionStorage instead of localStorage to limit exposure * of sensitive draft content. Drafts are automatically cleared when the * browser tab is closed, reducing the risk of exfiltration via XSS or * compromised browser profiles. ->>>>>>> main * * Non-annoying approach: * - Drafts are saved per-chat (or "global" for new chat input) * - Drafts are automatically cleared when a message is sent -<<<<<<< HEAD - * - Old drafts are cleaned up after 7 days to prevent storage bloat -||||||| 54e09ce - * - Old drafts are cleaned up after 7 days to prevent localStorage bloat -======= * - Old drafts are cleaned up after 24 hours as a defensive measure ->>>>>>> main */ const DRAFT_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours (session-scoped, defensive expiry) @@ -91,7 +77,7 @@ export const usePromptDraftStore = create()( return ""; } - // Don't return expired drafts + // Only return drafts that have not yet expired if (Date.now() - draft.updatedAt < DRAFT_EXPIRY_MS) { return draft.text; } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 1df04cc9..5d22215f 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -7,8 +7,8 @@ import tailwindcss from '@tailwindcss/vite' const config = defineConfig({ server: { - port: parseInt(process.env.PORT ?? '3000'), - host: '127.0.0.1', + port: parseInt(process.env.PORT ?? '3000', 10), + host: process.env.HOST ?? '127.0.0.1', }, plugins: [ viteTsConfigPaths({ @@ -16,7 +16,12 @@ const config = defineConfig({ }), tailwindcss(), tanstackStart(), - nitro({ preset: process.env.VERCEL ? 'vercel' : 'bun' }), + nitro({ + preset: + process.env.VERCEL === '1' || process.env.VERCEL === 'true' + ? 'vercel' + : 'bun', + }), viteReact(), ], }) diff --git a/scripts/check-redis.ts b/scripts/check-redis.ts index 1fef70f5..81a31ea0 100644 --- a/scripts/check-redis.ts +++ b/scripts/check-redis.ts @@ -19,7 +19,17 @@ function parseEnvFile(content: string): Record { (value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'")) ) { + const quoteChar = value[0]; value = value.slice(1, -1); + + // Unescape the matching quote character and backslashes inside quoted values. + if (quoteChar === "\"") { + // For double-quoted values, interpret \" as " and \\ as \. + value = value.replace(/\\(["\\])/g, "$1"); + } else if (quoteChar === "'") { + // For single-quoted values, interpret \' as ' and \\ as \. + value = value.replace(/\\(['\\])/g, "$1"); + } } parsed[key] = value; @@ -28,6 +38,17 @@ function parseEnvFile(content: string): Record { return parsed; } +function isRedisPingResponse( + value: unknown, +): value is Array<{ result?: string }> { + if (!Array.isArray(value)) return false; + const first = value[0]; + if (first === undefined) return true; + if (typeof first !== "object" || first === null) return false; + const result = (first as { result?: unknown }).result; + return result === undefined || typeof result === "string"; +} + function loadLocalEnvDefaults(): void { const envFiles = [ join(process.cwd(), "apps/web/.env.local"), @@ -60,8 +81,11 @@ async function main() { } try { - const normalizedUrl = url.replace(/\/+$/, ""); - const response = await fetch(`${normalizedUrl}/pipeline`, { + const baseUrl = new URL(url); + // Remove trailing slashes from the pathname to avoid double slashes when appending "/pipeline" + baseUrl.pathname = baseUrl.pathname.replace(/\/+$/, "") || "/"; + const pipelineUrl = new URL("/pipeline", baseUrl); + const response = await fetch(pipelineUrl, { method: "POST", headers: { Authorization: `Bearer ${token}`, @@ -76,7 +100,11 @@ async function main() { throw new Error(`HTTP ${response.status} ${body}`); } - const payload = (await response.json()) as Array<{ result?: string }>; + const rawPayload = await response.json(); + if (!isRedisPingResponse(rawPayload)) { + throw new Error("Unexpected Redis response format"); + } + const payload = rawPayload; const result = payload[0]?.result; if (result !== "PONG") { throw new Error("Redis ping failed"); @@ -87,10 +115,16 @@ async function main() { process.stderr.write( "Check UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN.\n", ); - if (process.env.NODE_ENV === "production") { + const nodeEnv = process.env.NODE_ENV; + if (nodeEnv !== "development") { + process.stderr.write( + "[check-redis] Redis is required for rate limiting in this environment. Failing fast.\n", + ); process.exit(1); } - process.stderr.write("[check-redis] Dev mode — continuing without Redis. Rate limiting is disabled.\n"); + process.stderr.write( + "[check-redis] Dev mode — continuing without Redis. Rate limiting is disabled.\n", + ); } }