From efb5dc70e759e2447bbee0819d06742f1201ad0d Mon Sep 17 00:00:00 2001 From: kappa Date: Sun, 25 Jan 2026 16:11:45 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Critical=20=EB=B3=B4=EC=95=88=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=204=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. SQL injection 취약점 수정 (currency 직접 삽입 제거) - SQL 쿼리에서 currency 제거, 결과 매핑에서 추가 2. 에러 메시지 정보 노출 수정 - 클라이언트에 내부 에러 상세 숨김 - 서버 로그에만 기록 3. API 키 로깅 제거 - sk-*** 형식만 표시, 실제 값 노출 안함 4. Rate limit fail-closed 정책 적용 - KV 오류 시 요청 거부 (보안 강화) Co-Authored-By: Claude Opus 4.5 --- src/index.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index f532950..9fbc627 100644 --- a/src/index.ts +++ b/src/index.ts @@ -635,8 +635,8 @@ async function checkRateLimit(clientIP: string, env: Env): Promise<{ allowed: bo return { allowed: true, requestId }; } catch (error) { console.error('[RateLimit] KV error:', error); - // On error, allow the request (fail open) - return { allowed: true, requestId }; + // On error, deny the request (fail closed) for security + return { allowed: false, requestId }; } } @@ -1077,10 +1077,10 @@ async function handleRecommend( } catch (error) { console.error('[Recommend] Error:', error); console.error('[Recommend] Error stack:', error instanceof Error ? error.stack : 'No stack'); + console.error('[Recommend] Error details:', error instanceof Error ? error.message : 'Unknown error'); return jsonResponse( { error: 'Failed to generate recommendations', - details: error instanceof Error ? error.message : 'Unknown error', request_id: requestId, }, 500, @@ -1303,7 +1303,6 @@ async function queryCandidateServers( it.gpu_count, it.gpu_type, MIN(${priceColumn}) as monthly_price, - '${currency}' as currency, r.region_name as region_name, r.region_code as region_code, r.country_code as country_code @@ -1436,8 +1435,14 @@ async function queryCandidateServers( throw new Error('Failed to query candidate servers'); } - // Validate each result with type guard - const validServers = (result.results as unknown[]).filter(isValidServer); + // Add currency to each result and validate with type guard + const serversWithCurrency = (result.results as unknown[]).map(server => { + if (typeof server === 'object' && server !== null) { + return { ...server, currency }; + } + return server; + }); + const validServers = serversWithCurrency.filter(isValidServer); const invalidCount = result.results.length - validServers.length; if (invalidCount > 0) { console.warn(`[Candidates] Filtered out ${invalidCount} invalid server records`); @@ -1851,7 +1856,7 @@ async function getAIRecommendations( console.error('[AI] OPENAI_API_KEY has invalid format (should start with sk-)'); throw new Error('Invalid OPENAI_API_KEY format'); } - console.log('[AI] API key validated (sk-***' + apiKey.slice(-4) + ')'); + console.log('[AI] API key validated (format: sk-***)'); // Build dynamic tech specs prompt from database const techSpecsPrompt = formatTechSpecsForPrompt(techSpecs);