- React frontend with Vite + TypeScript - Cloudflare Worker backend with Durable Objects - AI-powered chat moderation via OpenAI - WebSocket-based real-time messaging - XSS prevention, rate limiting, input validation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.7 KiB
Chat App 버그 수정 보고서
수정일: 2026-01-19 검토 범위: frontend/, worker/
요약
| 우선순위 | 발견 | 수정 |
|---|---|---|
| Critical | 4 | 4 |
| High | 4 | 4 |
| Medium | 9 | 9 |
| 합계 | 17 | 17 |
Critical Issues
1. WebSocket Race Condition
파일: frontend/src/hooks/useWebSocket.ts
증상: 빠른 연속 호출 시 중복 WebSocket 연결 생성
원인: 연결 상태 확인 전에 lock 획득이 이루어지지 않음
수정:
const connect = useCallback(() => {
// CRITICAL: Atomic lock acquisition to prevent race conditions
if (isConnectingRef.current) {
return;
}
// Acquire lock FIRST, before any other checks
isConnectingRef.current = true;
// ...
2. RateLimiter Memory Leak
파일: worker/src/RateLimiter.ts
증상: 장시간 운영 시 메모리 사용량 증가
원인: cleanup() 메서드가 만료된 엔트리를 제대로 삭제하지 않음
수정:
- blocked 상태 만료 시 즉시 삭제
- 빈 timestamp 배열 엔트리 삭제
- timestamp 배열 정리 로직 개선
3. API Key Exposure in Logs
파일: worker/src/AIManager.ts
증상: 에러 로그에 API 키가 노출될 가능성
원인: sanitizeErrorForLogging() 함수의 패턴 누락
수정: 다양한 API 키 형식 지원
- OpenAI:
sk-proj-*,sk-svcacct-* - Anthropic:
sk-ant-api03-* - AWS:
AKIA*,ASIA* - Cloudflare tokens
- Basic auth credentials
4. Silent Storage Failure
파일: worker/src/ChatRoom.ts
증상: DO storage 실패 시 메시지 유실
원인: saveMessage()에 retry 로직 부재
수정:
const STORAGE_MAX_RETRIES = 3;
const STORAGE_RETRY_BASE_DELAY_MS = 50;
private async saveMessage(message: StoredMessage): Promise<void> {
for (let attempt = 1; attempt <= STORAGE_MAX_RETRIES; attempt++) {
try {
// storage operation
return;
} catch (error) {
if (attempt < STORAGE_MAX_RETRIES) {
const jitter = Math.random() * STORAGE_RETRY_BASE_DELAY_MS;
await delay(STORAGE_RETRY_BASE_DELAY_MS * attempt + jitter);
}
}
}
}
High Priority Issues
5. XSS via URL Encoding
파일: frontend/src/utils/sanitize.ts
증상: javascript: 같은 인코딩된 XSS 공격 가능
원인: URL 검증 시 인코딩된 문자열 미처리
수정: isUrlSafe() 함수 추가
function isUrlSafe(url: string): boolean {
const decoded = decodeURIComponent(url);
const doubleDecoded = decodeURIComponent(decoded);
// Check all versions for dangerous protocols
const dangerousProtocols = [
/^javascript:/, /^vbscript:/, /^data:/,
/^file:/, /^about:/, /^blob:/,
];
// ...
}
6. TOCTOU Race Condition (Silence/Violations)
파일: worker/src/ChatRoom.ts
증상: 동시 요청 시 silence 체크 우회 가능
원인: read-check-write가 atomic하지 않음
수정: DO transaction 사용
private async isUserSilenced(userName: string): Promise<boolean> {
return await this.ctx.storage.transaction(async (txn) => {
const entry = await txn.get<SilenceEntry>(storageKey);
if (!entry) return false;
if (Date.now() >= entry.expiresAt) {
await txn.delete(storageKey);
return false;
}
return true;
});
}
7. Unvalidated JSON Parsing
파일: worker/src/AIManager.ts
증상: OpenAI API 응답 형식 변경 시 런타임 에러
원인: 응답 JSON 검증 부재
수정: Zod schema 추가
const OpenAIResponseSchema = z.object({
choices: z.array(z.object({
message: z.object({
content: z.string().nullable(),
tool_calls: z.array(OpenAIToolCallSchema).optional(),
}),
finish_reason: z.string(),
})),
});
8. Missing API Timeout
파일: worker/src/Context7Client.ts
증상: 외부 API 무응답 시 무한 대기
원인: fetch 요청에 timeout 미설정
수정: fetchWithTimeout() 함수 추가 (10초 timeout)
Medium Priority Issues
9. Frontend Message Array Memory Leak
파일: frontend/src/components/ChatRoom.tsx, frontend/src/config.ts
증상: 장시간 채팅 시 브라우저 메모리 증가
원인: 메시지 배열 무한 증가
수정:
// config.ts
export const CHAT_CONFIG = {
maxDisplayMessages: 200,
} as const;
// ChatRoom.tsx
function limitMessages(messages: DisplayMessage[]): DisplayMessage[] {
if (messages.length > CHAT_CONFIG.maxDisplayMessages) {
return messages.slice(-CHAT_CONFIG.maxDisplayMessages);
}
return messages;
}
10. Config Mismatch
파일: frontend/src/config.ts
증상: 프론트엔드에서 2000자 메시지 전송 시 잘림
원인: 프론트엔드(1000) vs 백엔드(2000) 제한 불일치
수정: maxMessageLength: 2000으로 통일
11. Context7 Cache Unbounded Growth
파일: worker/src/Context7Client.ts
증상: 캐시 크기 무한 증가 가능
원인: 캐시 cleanup 로직 부재
수정:
MAX_CACHE_SIZE = 100제한cleanupExpired(): 만료 엔트리 정리evictOldest(): LRU 방식 evictioncleanupIfNeeded(): 1분 간격 자동 정리
12. Serialization Error Handling
파일: worker/src/ChatRoom.ts
증상: WebSocket attachment 직렬화 실패 시 silent failure
원인: serializeAttachment() 에러 처리 부재
수정:
private setSession(ws: WebSocket, session: Session): boolean {
try {
ws.serializeAttachment(session);
return true;
} catch (error) {
console.error('Failed to serialize session attachment:', error);
return false;
}
}
수정된 파일 목록
| 파일 | 수정 내용 |
|---|---|
frontend/src/hooks/useWebSocket.ts |
Race condition 수정 |
frontend/src/utils/sanitize.ts |
XSS URL 검증 강화 |
frontend/src/components/ChatRoom.tsx |
메시지 배열 제한 |
frontend/src/config.ts |
설정값 통일 및 추가 |
worker/src/ChatRoom.ts |
Storage retry, TOCTOU 수정, serialization 에러 처리 |
worker/src/AIManager.ts |
API key redaction, Zod validation |
worker/src/RateLimiter.ts |
Memory leak 수정 |
worker/src/Context7Client.ts |
Timeout 추가, cache cleanup |
테스트 권장사항
- WebSocket 연결 테스트: 빠른 연속 연결/해제 시 중복 연결 없음 확인
- XSS 테스트:
javascript:alert(1)같은 인코딩된 URL 차단 확인 - 메모리 테스트: 장시간 채팅 후 브라우저 메모리 사용량 확인
- Storage 테스트: DO storage 일시적 실패 시 retry 동작 확인
- 동시성 테스트: 동일 사용자 동시 요청 시 silence 체크 정상 동작 확인