-
Notifications
You must be signed in to change notification settings - Fork 0
Fix: 보안 취약점 개선 #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: 보안 취약점 개선 #18
Conversation
문제점: - JWT 키 길이가 32자 미만으로 보안 취약 - 예외 처리 시 시스템 정보 노출 위험 - 인증 실패 원인 추적 불가 수정사항: - JWT 키 최소 32자 길이 검증 추가 - 기본값/샘플 키 사용 방지 로직 구현 - 구체적 예외 타입별 로깅 강화 - JwtProvider에 ILogger 의존성 주입
문제점: - async 메서드에서 ConfigureAwait 누락 - UI 스레드 블로킹으로 인한 데드락 위험 - 높은 부하 상황에서 성능 저하 수정사항: - TokenService 모든 async 호출에 ConfigureAwait(false) 적용 - AuthService 비즈니스 로직 메서드 최적화 - SqlServerUserRepository 데이터베이스 작업 최적화 - 컨텍스트 캡처 방지로 스레드 풀 효율성 향상
문제점: - 1KB 작은 버퍼로 인한 성능 저하 - 장시간 연결에서 타임아웃 미처리 - 연결 해제 시 리소스 정리 불완전 - 하트비트 메커니즘 부재 수정사항: - 버퍼 크기 1KB → 4KB 증가로 성능 향상 - 30분 타임아웃으로 장시간 연결 관리 - ping/pong 하트비트 메시지 처리 추가 - 연결 종료 시 완전한 리소스 정리 - 초기 연결 확인 메시지 전송 - CancellationTokenSource 적절한 해제
문제점: - ChatRequestValidator에 TODO 주석으로 방치된 세션 검증 - 무효한 세션으로 채팅 요청 가능한 보안 취약점 - 세션 활동 시간 추적 부재 수정사항: - ValidateUserSessionAsync 메서드 구현 - Redis 기반 사용자 세션 유효성 검사 - 세션 접근 시 마지막 활동 시간 자동 업데이트 - 세션 만료 시간 2시간 설정 - 세션 스토리지 오류 시 그레이스풀 처리 - ConfigureAwait(false) 추가로 성능 최적화
문제점: - 보안 요구사항과 모범 사례 문서 부재 - 프로덕션 배포 시 보안 검증 기준 없음 - 보안 사고 시 대응 절차 미정의 - 개발팀 보안 코딩 가이드라인 부족 수정사항: - JWT, 데이터베이스, 인증 보안 요구사항 정의 - 프로덕션 보안 체크리스트 작성 - 보안 사고 대응 절차 수립 - 보안 모니터링 및 로깅 가이드라인 - 개발자 보안 코딩 표준 제시 - 최근 보안 개선사항 문서화
문제점: - 세션 검증에서 존재하지 않는 SessionInfo 프로퍼티 사용 - JwtProvider 생성자 변경으로 테스트 코드 오류 - WebSocketMessageType.Pong 미지원 - AuthResult.User null 허용 불가 수정사항: - SessionInfo 실제 구조에 맞춰 세션 검증 로직 단순화 - JwtProviderTests에 Mock ILogger 추가 - WebSocket Pong → Binary 메시지 처리로 변경 - AuthResult.User를 nullable로 변경 - 기존 ErrorCode.SESSION_EXPIRED 사용
Caution Review failedThe pull request is closed. WalkthroughWebSocketMiddleware에 수신 버퍼/타임아웃/프레이밍/핑퐁/종료 및 정리 로직을 추가하고 예외 처리와 로깅을 강화했습니다. 인증·토큰 관련 서비스에 ConfigureAwait(false)를 적용하고, JwtProvider에 ILogger 주입 및 키 검증을 도입했습니다. 채팅 요청 검증에 세션 유효성 검사를 추가했고, 관련 테스트와 DI 구성을 갱신했습니다. 보안 가이드 문서를 신규 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant M as WebSocketMiddleware
participant S as WebSocketService
Note over M: Accept WebSocket<br/>buffer=4096, CTS=30m
C->>M: WebSocket Upgrade
M->>C: "connected" 메시지 전송
M->>S: Register/Start Session
loop While Open && !CTS.IsCanceled
M->>M: ReceiveAsync(ct: CTS)
alt EndOfMessage == false
M->>M: MemoryStream에 프래그먼트 누적
else EndOfMessage == true
alt MessageType == Text
M->>M: 디코드 및 파싱
alt ping 감지("ping" 또는 JSON ping)
M-->>C: "pong" 전송
else 일반 텍스트
M->>S: 메시지 처리 위임(옵션)
end
else MessageType == Binary
M->>M: 로그 후 무시
else MessageType == Close
M->>M: 로그 후 루프 종료
end
end
end
M->>S: Disconnect/Unregister
M->>C: CloseAsync
M->>M: CTS Dispose
note over M: finally에서 리소스 정리
sequenceDiagram
autonumber
participant Client
participant V as ChatRequestValidator
participant SS as ISessionStorage
participant U as IUserRepository
participant B as IBalanceService
Client->>V: ValidateAsync(command)
V->>SS: GetSessionsByUserIdAsync(userId)
alt 세션 없음
V-->>Client: throw ValidationException(SESSION_EXPIRED)
else 세션 있음
V->>U: 사용자/캐릭터 조회
V->>B: 토큰 잔액 조회
alt 잔액 0 또는 예상비용 미만
V-->>Client: throw ValidationException(INSUFFICIENT_CREDIT_BALANCE)
else 유효
V-->>Client: 성공 반환
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches and finishing touches and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
ProjectVG.Tests/Services/Chat/Validator/ChatRequestValidatorTests.cs (2)
94-115
: 테스트 이름과 기대 동작이 불일치합니다 (빈 프롬프트).메서드명은 예외를 기대(ShouldThrowValidationException)하지만 본문은 정상 통과를 기대합니다. 오해를 유발하므로 이름을 실제 기대에 맞춰 수정하세요.
- public async Task ValidateAsync_EmptyUserPrompt_ShouldThrowValidationException() + public async Task ValidateAsync_EmptyUserPrompt_ShouldPassValidation()
123-149
: 테스트 이름과 기대 동작이 불일치합니다 (공백 프롬프트).위와 동일하게 메서드명과 검증 내용이 상충합니다. 실제 기대에 맞춰 이름을 정정하세요.
- public async Task ValidateAsync_WhitespaceOnlyUserPrompt_ShouldThrowValidationException() + public async Task ValidateAsync_WhitespaceOnlyUserPrompt_ShouldPassValidation()ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs (1)
131-146
: 중요: JWT 키의 위험한 기본값 사용 — 프로덕션 보안 이슈환경설정이 없을 때 하드코딩된 기본 키로 폴백하면 프로덕션에서 동일 키가 재사용되어 심각한 보안 취약점이 됩니다. 기본값을 제거하고, 미설정 시 예외를 던지거나 Development에서만 허용하도록 변경하세요.
- var jwtKey = Environment.GetEnvironmentVariable("JWT_SECRET_KEY") ?? - configuration["JWT_SECRET_KEY"] ?? - configuration["JWT:SecretKey"] ?? - Environment.GetEnvironmentVariable("JWT_KEY") ?? - "your-super-secret-jwt-key-here-minimum-32-characters"; + var jwtKey = Environment.GetEnvironmentVariable("JWT_SECRET_KEY") + ?? configuration["JWT_SECRET_KEY"] + ?? configuration["JWT:SecretKey"] + ?? Environment.GetEnvironmentVariable("JWT_KEY"]; + if (string.IsNullOrWhiteSpace(jwtKey)) + { + throw new InvalidOperationException("JWT secret key must be configured via env or configuration (JWT_SECRET_KEY or Jwt:SecretKey)."); + } // 환경변수 치환 문자열이 그대로 남아있는 경우 처리 if (jwtKey.StartsWith("${") && jwtKey.EndsWith("}")) { var envVarName = jwtKey.Substring(2, jwtKey.Length - 3); - jwtKey = Environment.GetEnvironmentVariable(envVarName) ?? - "your-super-secret-jwt-key-here-minimum-32-characters"; + jwtKey = Environment.GetEnvironmentVariable(envVarName) + ?? throw new InvalidOperationException($"Missing environment variable for JWT key: {envVarName}"); }
🧹 Nitpick comments (35)
ProjectVG.Application/Services/Auth/IUserAuthService.cs (1)
45-45
: AuthResult.User를 nullable로 변경 — API 계약 영향 점검 필요
- 프런트/컨트롤러/Swagger에서 User가 null일 수 있음을 반영하세요.
- Refresh 토큰 갱신 시 User를 의도적으로 null 반환하는지(혹은 반드시 조회해 채우는지) 정책을 문서화하면 혼선을 줄일 수 있습니다.
ProjectVG.Application/Services/Auth/AuthService.cs (5)
33-49
: ConfigureAwait(false) 적용은 LGTM. 입력 검증은 IsNullOrWhiteSpace 권장
- ConfigureAwait(false) 통일 적용은 좋습니다.
- guestId 검증은 공백 문자열도 차단하기 위해 string.IsNullOrWhiteSpace 사용을 권장합니다.
48-52
: FinalizeLoginAsync의 provider 매개변수는 미사용 — 제거 권장메서드 시그니처 간소화로 가독성과 유지보수성을 높일 수 있습니다.
- return await FinalizeLoginAsync(user, "guest").ConfigureAwait(false); + return await FinalizeLoginAsync(user).ConfigureAwait(false);- private async Task<AuthResult> FinalizeLoginAsync(UserDto user, string provider) + private async Task<AuthResult> FinalizeLoginAsync(UserDto user)
74-81
: Refresh 후 사용자 조회 로직은 합리적이나 성능 관점 점검RefreshAccessTokenAsync에서 토큰 검증/스토리지 확인 후 다시 userId를 파싱/조회합니다. 성능에 민감하다면 TokenService가 userId를 함께 반환하도록 확장하는 방안도 고려해 볼 수 있습니다(선택).
94-97
: 미사용 변수 제거LogoutAsync에서 userId를 조회하지만 사용하지 않습니다. 제거해 불필요한 호출을 줄이세요.
- if (revoked) { - var userId = await _tokenService.GetUserIdFromTokenAsync(refreshToken).ConfigureAwait(false); - } + // 토큰이 성공적으로 폐기되었음을 상위 레이어에서 로깅/감사처리할 필요가 있다면 여기서 수행하세요.
100-106
: 게스트 UUID 생성: 충돌 가능성은 낮으나 정책 명시 권장SHA-256 해시의 앞 16자 사용은 사실상 충분하지만, 충돌 시 처리(중복 UID 생성 실패 대응)를 정책/문서로 명시해 두면 운영 리스크를 줄일 수 있습니다.
ProjectVG.Infrastructure/Auth/TokenService.cs (3)
27-32
: Refresh 토큰 저장 실패 시 예외 전파 — 예외 타입/매핑 일관성 정렬 필요InvalidOperationException 전파는 합리적입니다. 다만 API 응답 규격(에러 코드/HTTP 상태)과의 일관성을 위해 도메인 예외(ProjectVGException 파생 등)로 감싸거나 상위 계층에서 500으로 매핑되는지 확인하세요.
24-27
: 만료 시간 매직 넘버(15, 1440) 제거 권장구성값(JwtOptions 등)으로 옮겨 운영/환경별 조정이 가능하도록 하세요.
Also applies to: 75-76
73-92
: Refresh 시 토큰 로테이션 고려(보안 강화 옵션)현재 리프레시 토큰을 재사용합니다. 재사용 공격 완화와 세션 하이재킹 대응을 위해 Refresh 시:
- 새 Refresh 토큰 발급
- 기존 토큰 즉시 폐기(원자적 교체)
전략을 검토하세요.ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs (2)
24-28
: 읽기 전용 조회에 AsNoTracking 적용으로 성능 개선읽기 전용 경로(GetAll/GetByUsername/GetByEmail/GetByProviderId/GetByProvider/GetByUID)는 추적 불필요합니다. 트래킹 오버헤드를 줄이세요.
return await _context.Users - .Where(u => u.Status == AccountStatus.Active) + .AsNoTracking() + .Where(u => u.Status == AccountStatus.Active) .OrderBy(u => u.Username) .ToListAsync().ConfigureAwait(false);-return await _context.Users.FirstOrDefaultAsync(u => u.Username == username && u.Status != AccountStatus.Deleted).ConfigureAwait(false); +return await _context.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Username == username && u.Status != AccountStatus.Deleted).ConfigureAwait(false);-return await _context.Users.FirstOrDefaultAsync(u => u.Email == email && u.Status != AccountStatus.Deleted).ConfigureAwait(false); +return await _context.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Email == email && u.Status != AccountStatus.Deleted).ConfigureAwait(false);-return await _context.Users.FirstOrDefaultAsync(u => u.ProviderId == providerId && u.Status != AccountStatus.Deleted).ConfigureAwait(false); +return await _context.Users.AsNoTracking().FirstOrDefaultAsync(u => u.ProviderId == providerId && u.Status != AccountStatus.Deleted).ConfigureAwait(false);-return await _context.Users.FirstOrDefaultAsync(u => u.Provider == provider && u.ProviderId == providerId && u.Status != AccountStatus.Deleted).ConfigureAwait(false); +return await _context.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Provider == provider && u.ProviderId == providerId && u.Status != AccountStatus.Deleted).ConfigureAwait(false);-return await _context.Users.FirstOrDefaultAsync(u => u.UID == uid && u.Status != AccountStatus.Deleted).ConfigureAwait(false); +return await _context.Users.AsNoTracking().FirstOrDefaultAsync(u => u.UID == uid && u.Status != AccountStatus.Deleted).ConfigureAwait(false);Also applies to: 37-38, 42-43, 47-48, 52-53, 57-58
24-28
: 조회 필드 인덱스 점검 권장Username/Email/ProviderId/Provider/UID 필드에 인덱스가 있어야 조회 성능이 보장됩니다. DB 마이그레이션/인덱스 상태를 확인하세요.
Also applies to: 35-58
ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs (4)
40-50
: 사용자/캐릭터 존재 검증 병렬화로 대기시간 감소(선택사항)두 검증은 독립적이므로 병렬 실행 후 결과 확인을 고려하세요.
예시:
- var userTask = _userService.ExistsByIdAsync(...);
- var charTask = _characterService.CharacterExistsAsync(...);
- await Task.WhenAll(userTask, charTask);
52-66
: 크레딧 검증 조건 단순화 가능(선택)0 토큰 케이스를 별도로 로깅하려는 의도가 아니라면
currentBalance < ESTIMATED_CHAT_COST
한 번으로 충분합니다. 로깅을 분리 유지할 거라면 현 구조도 무방합니다.
91-95
: 세션 스토리지 장애 시 우회 허용 — 모니터링/알림 보강 권장세션 검증을 건너뛰는 정책이라면, 장애 감지용 메트릭/알림(에러 카운터, 레이트, 서킷 브레이커)을 반드시 추가하세요.
18-20
: ESTIMATED_CHAT_COST 구성화 권장상수 10m은 환경/상품에 따라 변할 수 있습니다. 구성값 또는 피처 플래그로 분리하면 운영 탄력성이 올라갑니다.
ProjectVG.Tests/Services/Chat/Validator/ChatRequestValidatorTests.cs (2)
53-54
: 세션 검증 추가 및 호출 검증: 전반적으로 적절합니다. 다만 누락된 Verify 보강 제안유효 요청 시나리오에서 Character 조회를 Stubbing 했으므로 실제 호출 여부까지 검증하면 테스트 신뢰도가 올라갑니다.
await _validator.ValidateAsync(command); // Should not throw _mockSessionStorage.Verify(x => x.GetSessionsByUserIdAsync(command.UserId.ToString()), Times.Once); _mockUserService.Verify(x => x.ExistsByIdAsync(command.UserId), Times.Once); _mockCharacterService.Verify(x => x.CharacterExistsAsync(command.CharacterId), Times.Once); + _mockCharacterService.Verify(x => x.GetCharacterByIdAsync(command.CharacterId), Times.Once); _mockCreditManagementService.Verify(x => x.GetCreditBalanceAsync(command.UserId), Times.Once);
Also applies to: 67-71
430-443
: 세션 픽스처 헬퍼 추가: 좋습니다. 비정상 경로 헬퍼도 추가 권장세션 부재/만료 시나리오를 쉽게 구성할 수 있도록 “SetupNoSession(Guid userId)” 같은 헬퍼를 추가하고, 세션이 없을 때 Validation이 실패하는 경로도 별도 테스트로 보강하세요. 필요 시 초안 제공 가능합니다.
ProjectVG.Infrastructure/Auth/JwtProvider.cs (3)
40-56
: JWT 키 검증을 대소문자/공백 무시로 보강하고 에러 메시지 명확화 제안placeholder 탐지는 OrdinalIgnoreCase로, 키는 Trim 후 길이 검사를 권장합니다.
- private static void ValidateJwtKey(string jwtKey) + private static void ValidateJwtKey(string jwtKey) { - if (string.IsNullOrEmpty(jwtKey)) + if (string.IsNullOrWhiteSpace(jwtKey)) { throw new ArgumentException("JWT key cannot be null or empty", nameof(jwtKey)); } - if (jwtKey.Length < 32) + jwtKey = jwtKey.Trim(); + if (jwtKey.Length < 32) { throw new ArgumentException("JWT key must be at least 32 characters long for security", nameof(jwtKey)); } - if (jwtKey.Contains("fallback") || jwtKey.Contains("default") || jwtKey.Contains("sample")) + if (jwtKey.Contains("fallback", StringComparison.OrdinalIgnoreCase) + || jwtKey.Contains("default", StringComparison.OrdinalIgnoreCase) + || jwtKey.Contains("sample", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("JWT key appears to be a fallback/default value. Use a secure random key in production", nameof(jwtKey)); } }
65-67
: 키 바이트 인코딩은 UTF8 사용 권장ASCII는 비 ASCII 문자를 손실시킬 수 있습니다. 일반적으로 UTF8을 사용합니다.
- var key = Encoding.ASCII.GetBytes(_jwtKey); + var key = Encoding.UTF8.GetBytes(_jwtKey);(세 곳 모두 동일 변경)
Also applies to: 99-101, 128-129
142-146
: 토큰 alg 검증 추가 제안유효성 검증 후 토큰 헤더 알고리즘이 기대값(HS256)인지 추가 확인을 권장합니다.
- var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); - return principal; + var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); + if (validatedToken is JwtSecurityToken jwt && + !string.Equals(jwt.Header.Alg, SecurityAlgorithms.HmacSha256, StringComparison.Ordinal)) + { + _logger.LogWarning("Unexpected JWT alg: {Alg}", jwt.Header.Alg); + return null; + } + return principal;Also applies to: 147-156
ProjectVG.Tests/Auth/JwtProviderTests.cs (2)
200-224
: 테스트 이름 대비 만료 시간 차이는 실제로 검증되지 않습니다. exp 비교 추가 권장두 토큰의 만료 시간이 다른지 직접 비교하도록 보강하세요.
public void AccessTokenAndRefreshToken_ShouldHaveDifferentExpirationTimes() { // Arrange var userId = Guid.NewGuid(); // Act var accessToken = _jwtProvider.GenerateAccessToken(userId); var refreshToken = _jwtProvider.GenerateRefreshToken(userId); // Assert accessToken.Should().NotBe(refreshToken); // 토큰 내용 검증 - var accessPrincipal = _jwtProvider.ValidateToken(accessToken); - var refreshPrincipal = _jwtProvider.ValidateToken(refreshToken); + var accessPrincipal = _jwtProvider.ValidateToken(accessToken); + var refreshPrincipal = _jwtProvider.ValidateToken(refreshToken); accessPrincipal.Should().NotBeNull(); refreshPrincipal.Should().NotBeNull(); var accessTokenType = accessPrincipal!.FindFirst("token_type")!.Value; var refreshTokenType = refreshPrincipal!.FindFirst("token_type")!.Value; accessTokenType.Should().Be("access"); refreshTokenType.Should().Be("refresh"); + + // exp 비교 + var handler = new JwtSecurityTokenHandler(); + var accessJwt = handler.ReadJwtToken(accessToken); + var refreshJwt = handler.ReadJwtToken(refreshToken); + accessJwt.ValidTo.Should().BeBefore(refreshJwt.ValidTo); }
1-32
: JWT 키 유효성 테스트 추가 제안짧은 키, placeholder 키에 대해 생성자에서 예외가 발생하는지 테스트를 추가하세요.
@@ public class JwtProviderTests { @@ } + + public class JwtProviderKeyValidationTests + { + private readonly Mock<ILogger<JwtProvider>> _mockLogger = new(); + private readonly string _issuer = "ProjectVG"; + private readonly string _audience = "ProjectVG"; + + [Fact] + public void Ctor_ShortKey_ShouldThrow() + { + Action act = () => new JwtProvider("short-key", _issuer, _audience, 15, 1440, _mockLogger.Object); + act.Should().Throw<ArgumentException>(); + } + + [Fact] + public void Ctor_PlaceholderKey_ShouldThrow() + { + var key = "this-is-a-default-placeholder-key-that-is-long-enough"; + Action act = () => new JwtProvider(key, _issuer, _audience, 15, 1440, _mockLogger.Object); + act.Should().Throw<ArgumentException>(); + } + }ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs (2)
156-159
: JwtProvider 등록 시 Key 일관성 유지이미
jwtSettings.Key = jwtKey
로 설정하므로, 생성자에도jwtSettings.Key
를 사용해 단일 소스 유지가 좋습니다.- return new JwtProvider(jwtKey, jwtSettings.Issuer, jwtSettings.Audience, jwtSettings.AccessTokenExpirationMinutes, jwtSettings.RefreshTokenExpirationMinutes, logger); + return new JwtProvider(jwtSettings.Key, jwtSettings.Issuer, jwtSettings.Audience, jwtSettings.AccessTokenExpirationMinutes, jwtSettings.RefreshTokenExpirationMinutes, logger);
180-214
: Redis 즉시 연결 시도는 시작 지연/불안정 환경에서 문제를 유발할 수 있습니다애플리케이션 시작 시 동기 연결을 시도하면 스타트업이 지연되거나 실패할 수 있습니다. Lazy 연결(요청 시 최초 연결) 또는 백그라운드 재시도 전략을 고려하세요.
ProjectVG.Api/Middleware/WebSocketMiddleware.cs (4)
111-116
: 버퍼/타임아웃 하드코딩 제거: 구성 가능하도록 전환 권장버퍼 크기(4KiB), 30분 타임아웃은 워크로드에 따라 과소/과대일 수 있습니다. IConfiguration 기반으로 주입하거나 옵션 패턴으로 노출하세요.
120-127
: 웰컴 메시지 직렬화는 문자열 결합 대신 JSON 직렬화를 사용하세요안전한 JSON 직렬화를 통해 이스케이프/포맷 오류를 방지할 수 있습니다.
- var welcomeMessage = System.Text.Encoding.UTF8.GetBytes($"{{\"type\":\"connected\",\"userId\":\"{userId}\"}}"); + var welcomeObj = new { type = "connected", userId }; + var welcomeJson = System.Text.Json.JsonSerializer.Serialize(welcomeObj); + var welcomeMessage = System.Text.Encoding.UTF8.GetBytes(welcomeJson);
84-87
: Authorization 헤더 파싱을 대소문자 무시로 보강“Bearer ” 접두어는 대소문자 변형이 올 수 있습니다. OrdinalIgnoreCase 비교를 사용하세요.
- if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ")) - return authHeader.Substring("Bearer ".Length).Trim(); + if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + return authHeader.Substring("Bearer ".Length).Trim();
79-83
: 운영 환경에서의 QueryString 토큰 허용은 지양 권장URL/로그에 노출 위험이 있습니다. 운영 환경에서는 Authorization 헤더만 허용하도록 제한하는 옵션을 고려하세요.
docs/SECURITY.md (7)
18-23
: DB 재시도/타임아웃 정책에 상한 및 회로차단기(circuit breaker)/지터를 추가하세요.현 설정은 장애 시 지연 증폭을 야기할 수 있습니다. 전체 예산 시간, 지터, 단건 비결정적 연산의 멱등성 요구를 명시하세요.
- **Connection Resilience**: Enhanced retry policies with exponential backoff -- **Connection Timeout**: 120-second timeout for database operations -- **Retry Logic**: 5 attempts with up to 30-second delays + - **Connection Timeout**: Operation-specific timeouts; enforce an overall budget (e.g., 20–30s typical). + - **Retry Logic**: 5 attempts with jittered exponential backoff (e.g., decorrelated jitter). + - **Circuit Breaking**: Trip after sustained failures to shed load and allow recovery. + - **Idempotency**: Retries only for idempotent operations or with deduplication keys.
40-47
: 환경 보안 체크리스트에 시크릿 관리 솔루션을 추가하세요.환경변수 외 KMS/Key Vault/Secrets Manager 사용, 정기 롤테이션 정책을 포함하세요.
- [ ] OAuth2 client secrets stored securely - [ ] API keys for external services (TTS, LLM, Memory) secured + - [ ] Secrets managed via KMS/Key Vault/Secrets Manager; rotation policy documented
48-54
: 모니터링 항목에 임계값/알림 기준을 추가하세요.경보 남용/침묵을 방지하려면 구체적 SLO·임계값(예: 5m 이동평균, p95)과 샘플링/집계 정책을 명시하세요.
- [ ] Failed authentication attempts rate-limited - [ ] Session hijacking patterns detected + - [ ] Alert thresholds defined (e.g., JWT failures > X/min, DB retries p95 > Y) + - [ ] Log sampling and aggregation policies documented
93-105
: 보안 로깅 예시에 PII/비밀 마스킹과 샘플링 가이드를 명시하세요.현재 수준은 괜찮지만 운영 안전장치가 있으면 더 좋습니다.
_logger.LogWarning("Authentication failed: {Reason}", reason); +// Mask PII/secrets and consider sampling for high-volume events.
116-122
: 보안 테스트 항목에 토큰 회전/재사용 탐지, 키 회전(kid) 시나리오를 추가하세요.실제 사고 대응력을 높입니다.
2. **Authorization Testing**: Role-based access, resource ownership +3. **Token Rotation/Reuse**: Refresh rotation with reuse detection; ensure reuse triggers revocation. +4. **Key Rotation**: Validate kid-based signing key rollover without downtime. -3. **Input Validation Testing**: Boundary conditions, malformed data -4. **Session Testing**: Session hijacking prevention, timeout handling -5. **Error Handling Testing**: Information leakage prevention +5. **Input Validation Testing**: Boundary conditions, malformed data +6. **Session Testing**: Session hijacking prevention, timeout handling +7. **Error Handling Testing**: Information leakage prevention
137-142
: WebSocket 30분 타임아웃이 인프라(로드밸런서/프록시) 유휴 타임아웃과 일치하는지 확인 필요.불일치 시 연결이 중간에서 끊길 수 있습니다. 문서에 정합성 확인 노트를 추가하세요.
- **Added connection timeout handling (30 minutes) + - **Connection timeout**: 30 minutes (verify alignment with LB/proxy idle timeouts; adjust if needed)
143-147
: 세션 실시간 검증과 함께 장애/다운스트림 오류 시 폴백·성능 가드를 추가하세요.예: 캐시 실패 시 fail-closed/short-circuit, 음수 캐시(negative cache) 등.
- **Implemented real-time session validation - **Added session activity tracking - **Graceful handling of session storage failures - **Enhanced session-based authentication for critical operations + - **Resilience**: Negative caching for invalid sessions; bounded retries and short TTL fallback.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
ProjectVG.Api/Middleware/WebSocketMiddleware.cs
(1 hunks)ProjectVG.Application/Services/Auth/AuthService.cs
(4 hunks)ProjectVG.Application/Services/Auth/IUserAuthService.cs
(1 hunks)ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs
(2 hunks)ProjectVG.Infrastructure/Auth/JwtProvider.cs
(2 hunks)ProjectVG.Infrastructure/Auth/TokenService.cs
(5 hunks)ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs
(2 hunks)ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs
(3 hunks)ProjectVG.Tests/Auth/JwtProviderTests.cs
(2 hunks)ProjectVG.Tests/Services/Chat/Validator/ChatRequestValidatorTests.cs
(15 hunks)docs/SECURITY.md
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs (4)
ProjectVG.Application/Services/Auth/AuthService.cs (4)
Task
(27-49)Task
(51-66)Task
(68-86)Task
(88-99)ProjectVG.Application/Services/User/UserService.cs (12)
Task
(19-43)Task
(45-56)Task
(58-62)Task
(64-68)Task
(70-74)Task
(76-80)Task
(82-86)Task
(88-88)Task
(89-89)Task
(90-90)Task
(91-91)Task
(93-94)ProjectVG.Common/Exceptions/NotFoundException.cs (5)
NotFoundException
(3-24)NotFoundException
(5-8)NotFoundException
(10-13)NotFoundException
(15-18)NotFoundException
(20-23)ProjectVG.Domain/Common/BaseEntity.cs (1)
Update
(16-19)
ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs (1)
ProjectVG.Infrastructure/Auth/JwtProvider.cs (2)
JwtProvider
(19-183)JwtProvider
(28-38)
ProjectVG.Application/Services/Auth/AuthService.cs (3)
ProjectVG.Application/Services/User/UserService.cs (13)
Task
(19-43)Task
(45-56)Task
(58-62)Task
(64-68)Task
(70-74)Task
(76-80)Task
(82-86)Task
(88-88)Task
(89-89)Task
(90-90)Task
(91-91)Task
(93-94)Task
(96-107)ProjectVG.Application/Services/User/IUserService.cs (3)
Task
(7-7)Task
(8-8)Task
(10-10)ProjectVG.Application/Services/Auth/IUserAuthService.cs (1)
AuthResult
(39-46)
ProjectVG.Api/Middleware/WebSocketMiddleware.cs (3)
ProjectVG.Infrastructure/Realtime/WebSocketConnection/WebSocketClientConnection.cs (2)
Task
(26-30)Task
(35-38)ProjectVG.Application/Services/Session/ConnectionRegistry.cs (1)
Unregister
(28-38)ProjectVG.Application/Services/Session/IConnectionRegistry.cs (1)
Unregister
(16-16)
🔇 Additional comments (11)
ProjectVG.Application/Services/Auth/AuthService.cs (1)
54-57
: 초기 크레딧 지급의 멱등성(Idempotency) 보장 확인로그인마다 GrantInitialCreditsAsync가 호출됩니다. 최초 1회만 지급되도록 내부에서 멱등성을 보장하는지 확인해주세요(중복 지급 방지).
ProjectVG.Infrastructure/Auth/TokenService.cs (2)
59-67
: 스토리지 기반 유효성 검사 LGTM토큰 포맷/타입 검증 후 스토리지 검증 흐름이 명확합니다.
96-97
: 토큰 폐기 호출에 ConfigureAwait(false) 적용 LGTM단일 호출로 명확합니다.
ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs (1)
30-33
: Update/Delete 경로에서는 AsNoTracking 적용 금지수정용 엔터티 추적이 필요하므로 현 상태 유지가 맞습니다. 그대로 두세요.
Also applies to: 75-76, 95-96
ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs (1)
37-39
: 세션 유효성 검증 도입 — 방향성 좋습니다요청 초기에 세션을 확인하는 흐름은 타당합니다. 이후 검증 실패 시 빠르게 중단되어 비용을 줄입니다.
ProjectVG.Tests/Services/Chat/Validator/ChatRequestValidatorTests.cs (1)
334-342
: 음수 잔액 케이스: 로그 메시지와 예외 메시지의 기준 일치 여부 확인 필요예외 메시지는 “현재 잔액: -5 토큰”을 기대하지만, 로그 검증은 “잔액 부족 (0 토큰)”으로 0으로 정규화된 값을 기대합니다. 실제 Validator가 음수를 0으로 정규화하는지(로그·예외 둘 다) 확인해 일관되게 맞추세요. 필요하면 테스트를 한쪽 기준으로 통일하세요.
ProjectVG.Infrastructure/Auth/JwtProvider.cs (2)
26-39
: ILogger 주입과 키 유효성 검증 진입점 추가: 적절합니다.
1-5
: 네임스페이스 의존성 확인이 파일 범위에
using Microsoft.Extensions.Logging;
이 없으면 전역 using에 의존합니다. 전역 using이 없다면 명시적으로 추가하세요.using System.Text; using Microsoft.IdentityModel.Tokens; +using Microsoft.Extensions.Logging;
ProjectVG.Tests/Auth/JwtProviderTests.cs (1)
14-15
: ILogger 주입을 반영한 생성자 업데이트: 적절합니다.Also applies to: 23-31
ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs (1)
56-75
: EF Core 재시도/타임아웃/마이그레이션 설정 강화: 적절합니다.ProjectVG.Api/Middleware/WebSocketMiddleware.cs (1)
170-186
: 정리 루틴(Disconnect → Unregister → Close) 순서와 예외 처리: 모범적입니다.
## 🔐 Critical Security Requirements | ||
|
||
### JWT Token Security | ||
- **Key Length**: JWT secret key MUST be at least 32 characters long |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
JWT 비밀키 요구사항을 “문자 수”가 아닌 “바이트(비트) 엔트로피”로 명확화하세요.
현재 “32 characters” 표기는 구현에 따라 오해 소지가 있습니다. HS256 기준 최소 256-bit(32 bytes) 엔트로피를 요구한다고 명시하고, 예시에도 이를 강조해 주세요.
- - **Key Length**: JWT secret key MUST be at least 32 characters long
+ - **Key Length**: JWT secret key MUST have at least 32 bytes (256 bits) of entropy
```bash
-# Example: Generate secure JWT key
-JWT_SECRET_KEY=$(openssl rand -base64 32)
+# Example: Generate secure JWT key (32 bytes entropy)
+# Produces ~43-char base64 string representing 32 random bytes.
+JWT_SECRET_KEY=$(openssl rand -base64 32)
+# Note: Treat as opaque; do not double-encode/decode accidentally.
Also applies to: 12-15
<details>
<summary>🤖 Prompt for AI Agents</summary>
In docs/SECURITY.md around lines 6 and also apply same change to lines 12-15,
replace the ambiguous "32 characters" JWT secret requirement with a clear
requirement of 256-bit (32 bytes) entropy; update the explanatory example text
to state that the example generates 32 bytes of entropy (e.g., base64 output ~43
characters) and add a short note to treat the value as opaque (do not
double-encode/decode) while keeping the example command as-is.
</details>
<!-- fingerprinting:phantom:triton:chinchilla -->
<!-- This is an auto-generated comment by CodeRabbit -->
- **Key Strength**: Avoid default, fallback, or sample keys in production | ||
- **Key Management**: Store JWT keys in secure environment variables only | ||
- **Token Validation**: All JWT validation failures are logged for security monitoring | ||
- **Token Headers**: Multiple headers supported (Authorization, X-Forwarded-Authorization, etc.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
다중 토큰 헤더 수용은 헤더 스푸핑/우선순위 문제를 유발할 수 있습니다. 기본은 Authorization만 허용하고, 프록시 신뢰 경계에서만 예외를 두세요.
우선순위, 허용 조건(신뢰된 프록시/게이트웨이 IP 범위), 비활성화 방법을 문서화해 주세요.
- - **Token Headers**: Multiple headers supported (Authorization, X-Forwarded-Authorization, etc.)
+ - **Token Headers**: Default: Authorization only.
+ - Optional: X-Forwarded-Authorization when requests traverse a trusted gateway/proxy.
+ - Define clear precedence (Authorization > X-Forwarded-Authorization) and restrict by source.
+ - Provide a configuration flag to disable non-standard headers in production.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
- **Token Headers**: Multiple headers supported (Authorization, X-Forwarded-Authorization, etc.) | |
- **Token Headers**: Default: Authorization only. | |
- Optional: X-Forwarded-Authorization when requests traverse a trusted gateway/proxy. | |
- Define clear precedence (Authorization > X-Forwarded-Authorization) and restrict by source. | |
- Provide a configuration flag to disable non-standard headers in production. |
🤖 Prompt for AI Agents
docs/SECURITY.md around lines 10 to 10: the current note allowing multiple token
headers is a security risk; change the guidance to require Authorization as the
default header and only allow alternative headers (e.g.,
X-Forwarded-Authorization) behind explicitly trusted proxy boundaries, and
document the header precedence order, how to specify and validate trusted
proxy/gateway IP ranges, and a clear toggle to disable alternative headers
altogether. Update the section to list the canonical header to use, a priority
list of headers when multiple are present, instructions for configuring trusted
proxy IP ranges and how to verify requests came through them, and a short note
on how operators can fully disable multi-header support in their deployments.
### Input Validation Security | ||
- **Data Annotations**: Comprehensive validation rules on all DTOs | ||
- **Business Logic Validation**: Domain-specific validation in Application layer | ||
- **SQL Injection Prevention**: Entity Framework parameterized queries | ||
- **XSS Protection**: JSON serialization prevents script injection | ||
- **CSRF Protection**: OAuth2 state parameter validation | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
브라우저 쿠키 기반 세션 사용 시 CSRF 대책을 별도로 명시하세요.
OAuth2 state만으로 일반 API CSRF가 해결되진 않습니다.
- **CSRF Protection**: OAuth2 state parameter validation
+ - **CSRF Protection**:
+ - OAuth2 redirect: validate 'state' parameter.
+ - Browser cookie sessions: SameSite=Lax/Strict, anti-CSRF tokens (double-submit or synchronizer).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
### Input Validation Security | |
- **Data Annotations**: Comprehensive validation rules on all DTOs | |
- **Business Logic Validation**: Domain-specific validation in Application layer | |
- **SQL Injection Prevention**: Entity Framework parameterized queries | |
- **XSS Protection**: JSON serialization prevents script injection | |
- **CSRF Protection**: OAuth2 state parameter validation | |
### Input Validation Security | |
- **Data Annotations**: Comprehensive validation rules on all DTOs | |
- **Business Logic Validation**: Domain-specific validation in Application layer | |
- **SQL Injection Prevention**: Entity Framework parameterized queries | |
- **XSS Protection**: JSON serialization prevents script injection | |
- **CSRF Protection**: | |
- OAuth2 redirect: validate 'state' parameter. | |
- Browser cookie sessions: SameSite=Lax/Strict, anti-CSRF tokens (double-submit or synchronizer). | |
🤖 Prompt for AI Agents
In docs/SECURITY.md around lines 31 to 37, the CSRF section only cites OAuth2
state but does not address CSRF mitigations for browser cookie–based sessions;
update the document to explicitly describe required protections for cookie
sessions: enforce Secure and HttpOnly flags on session cookies, set SameSite=Lax
or Strict where compatible, implement and validate per-session CSRF tokens
(synchronizer token pattern) or double-submit-cookie technique for unsafe HTTP
methods, validate Origin/Referer headers on state-changing requests, and ensure
CORS is restricted to trusted origins; also note rotating/expiring tokens and
server-side verification steps for each protected endpoint.
- **XSS Protection**: JSON serialization prevents script injection | ||
- **CSRF Protection**: OAuth2 state parameter validation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“JSON 직렬화가 XSS를 방지한다”는 표현은 부정확합니다. 컨텍스트 기반 출력 인코딩과 CSP를 명시하세요.
현재 문구는 오해를 야기합니다. 출력 컨텍스트(HTML, 속성, URL, JS) 별 인코딩과 CSP/Trusted Types 등을 권장하도록 수정하세요.
- - **XSS Protection**: JSON serialization prevents script injection
+ - **XSS Protection**: Use context-appropriate output encoding (HTML/attr/URL/JS), set correct
+ Content-Type, avoid rendering untrusted HTML, enforce CSP (and optionally Trusted Types),
+ and sanitize only as a last resort for known schemas.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
- **XSS Protection**: JSON serialization prevents script injection | |
- **CSRF Protection**: OAuth2 state parameter validation | |
- **XSS Protection**: Use context-appropriate output encoding (HTML/attr/URL/JS), set correct | |
Content-Type, avoid rendering untrusted HTML, enforce CSP (and optionally Trusted Types), | |
and sanitize only as a last resort for known schemas. | |
- **CSRF Protection**: OAuth2 state parameter validation |
🤖 Prompt for AI Agents
In docs/SECURITY.md around lines 35-36, the statement "JSON serialization
prevents XSS" is inaccurate; update the XSS protection section to recommend
context-sensitive output encoding for each output context (HTML, attribute, URL,
JavaScript), and mention Content Security Policy (CSP) and Trusted Types as
additional mitigation. Replace the single-line claim with explicit guidance:
describe proper encoding strategies per context, note input validation/escaping
is not a substitute for encoding, and add CSP/Trusted Types as defense-in-depth
recommendations; keep CSRF line unchanged.
- [ ] Session hijacking patterns detected | ||
|
||
### Code Security Standards | ||
- [ ] ConfigureAwait(false) used in all async service methods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
ASP.NET Core 앱 전역 ‘ConfigureAwait(false)’ 강제는 권장되지 않습니다. 라이브러리 경계에서만 사용으로 제한하세요.
문구를 완화해 맥락 의존성/가독성 저하를 피하세요.
- - [ ] ConfigureAwait(false) used in all async service methods
+ - [ ] Use ConfigureAwait(false) in library/infrastructure code that must not capture context.
+ - [ ] Not generally required in ASP.NET Core request pipeline code.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
- [ ] ConfigureAwait(false) used in all async service methods | |
- [ ] Use ConfigureAwait(false) in library/infrastructure code that must not capture context. | |
- [ ] Not generally required in ASP.NET Core request pipeline code. |
🤖 Prompt for AI Agents
In docs/SECURITY.md around line 56, the checklist item that mandates using
ConfigureAwait(false) for all async service methods should be softened: remove
the absolute requirement and replace it with guidance that ConfigureAwait(false)
is not recommended globally in ASP.NET Core, and should be used only at library
boundaries when preserving SynchronizationContext is unnecessary; update the
text to explain the context-dependent tradeoffs and either remove the checkbox
or convert it into a non-mandatory best-practice note with a short rationale.
### JWT Compromise Response | ||
1. **Immediate**: Rotate JWT secret key | ||
2. **Revoke**: All existing refresh tokens in Redis | ||
3. **Audit**: Review authentication logs for suspicious activity | ||
4. **Monitor**: Enhanced logging for unusual patterns | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
JWT 사고 대응에 키 회전 전략(kid/JWKS), Access Token 블록리스트/재발급 정책을 포함하세요.
Refresh 토큰 폐기만으로는 즉시 무력화가 어렵습니다.
1. **Immediate**: Rotate JWT secret key
2. **Revoke**: All existing refresh tokens in Redis
3. **Audit**: Review authentication logs for suspicious activity
4. **Monitor**: Enhanced logging for unusual patterns
+5. **Key Rotation**: Use key IDs (kid) and maintain JWKS with overlapping keys during rotation.
+6. **Access Token Mitigation**: Temporarily blocklist affected jti/subject pairs; enforce iat > rotation time.
+7. **Refresh Rotation**: Enforce refresh token rotation with reuse detection and revoke on reuse.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
### JWT Compromise Response | |
1. **Immediate**: Rotate JWT secret key | |
2. **Revoke**: All existing refresh tokens in Redis | |
3. **Audit**: Review authentication logs for suspicious activity | |
4. **Monitor**: Enhanced logging for unusual patterns | |
### JWT Compromise Response | |
1. **Immediate**: Rotate JWT secret key | |
2. **Revoke**: All existing refresh tokens in Redis | |
3. **Audit**: Review authentication logs for suspicious activity | |
4. **Monitor**: Enhanced logging for unusual patterns | |
5. **Key Rotation**: Use key IDs (kid) and maintain JWKS with overlapping keys during rotation. | |
6. **Access Token Mitigation**: Temporarily blocklist affected jti/subject pairs; enforce iat > rotation time. | |
7. **Refresh Rotation**: Enforce refresh token rotation with reuse detection and revoke on reuse. |
🤖 Prompt for AI Agents
In docs/SECURITY.md around lines 64 to 69, the JWT compromise response is
incomplete: add a key rotation strategy using KID and a JWKS endpoint (describe
steps to rotate keys, publish new KID, and fallback/rollout rules), and add an
Access Token blocklist and re-issuance policy (how to immediately blacklist
existing access tokens, criteria for forced re-issue, and short-term token
expiry/refresh rules) alongside the existing refresh-token revocation
instructions; update the list to include these steps and brief operational
playbook entries for execution.
ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs
Outdated
Show resolved
Hide resolved
- EndOfMessage 확인으로 완전한 메시지 수신 보장 - MemoryStream 사용한 프래그먼트 누적 처리 - 긴 메시지 손실 방지 및 데이터 무결성 보장 - ping/pong 메시지 처리 로직 개선 (JSON 구조 지원) 기존: 첫 번째 프래그먼트만 처리하여 메시지 손실 가능 개선: do-while 루프로 완전한 메시지 복원
- ChatRequestValidator.ValidateUserSessionAsync에서 Any() + Count() 패턴을 단일 ToList() + Count로 최적화 - 세션 스토리지 조회 시 중복 열거/쿼리 방지로 성능 개선 - 모든 기존 테스트 통과 (235/235)
- WebSocketMiddleware에서 연결 확인 메시지에서 userId 제거 - 보안상 민감한 사용자 식별자 정보 클라이언트 노출 방지 - 연결 상태만 전달하도록 메시지 형식 변경: {"type":"connected","status":"success"} - 모든 테스트 통과 (235/235)
Summary by CodeRabbit