diff --git a/.jules/sentinel.md b/.jules/sentinel.md index fa902ea..6bb5fc6 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -47,3 +47,7 @@ **Vulnerability:** 사용자 입력값(`lang`)을 검증 없이 `console.warn`과 같은 로그 함수에 그대로 보간하여 출력할 경우, 로그 인젝션(Log Forging) 공격에 노출될 수 있음. **Learning:** 사용자 입력이 포함된 문자열을 직접 보간하면 악의적인 페이로드가 로그 파일에 주입되어 로그 분석 시스템을 방해하거나 다른 취약점을 연계할 수 있음. **Prevention:** 로그를 남길 때는 검증되지 않은 외부 입력값을 동적으로 문자열에 주입(Interpolation)하는 대신, 사전에 정의된 정적이고 안전한 메시지로 대체해야 함. +## 2026-08-26 - SSR 호환성 및 안전한 실패(Fail Securely) 보장을 위한 환경 검증 추가 +**Vulnerability:** 브라우저 API(`localStorage`) 접근 시 실행 환경(예: SSR)에 대한 검증 누락으로 인한 Unhandled Exception(가용성 저하) 위험. +**Learning:** 공통 유틸리티 스크립트에서 `typeof window !== 'undefined' && window.localStorage`와 같은 환경 유효성 검사를 선행하면, SSR 환경이나 제한된 브라우저 설정에서도 스크립트가 중단되지 않고 안전하게 실패(Fail Securely)하며 견고성이 향상됨. +**Prevention:** 브라우저 전용 API를 사용할 때는 항상 환경 검증 방어 로직을 추가하여 예측 불가능한 스크립트 크래시를 방지할 것. diff --git a/CHANGELOG.md b/CHANGELOG.md index e097a55..b57db32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## [Unreleased] +- **보안 개선**: `i18n.js`에서 `localStorage` 접근 전 `typeof window !== 'undefined'` 환경 검증을 추가하여 SSR 환경에서의 견고성을 높이고 안전하게 실패(Fail Securely)하도록 개선했습니다. - **보안 개선**: `i18n.js`에서 잘못된 언어 요청 시 `console.warn` 메시지에 사용자 입력값이 직접 포함되지 않도록 수정하여 로그 인젝션(Log Injection) 취약점을 제거했습니다. - **성능 개선**: `.skip-link` 애니메이션을 `top`에서 `transform: translateY()`로 변경하여 전환 중 레이아웃 재계산을 줄일 수 있도록 했습니다. 실제 효과는 브라우저별 측정 대상입니다. - **렌더링 힌트 정합성**: 첫 화면의 eager 이미지와 단일 LCP 후보에서 강제 `decoding="async"`를 제거해 HTML 표준의 기본 `auto` 판단에 맡기고, 지연 로드 이미지에는 비동기 디코딩 힌트를 유지했습니다. 정적 테스트가 eager, lazy, LCP 후보 집합의 존재와 조합을 검증하며, 실제 LCP 효과는 배포 후 실측 대상으로 유지합니다. diff --git a/i18n.js b/i18n.js index 00b81d3..eec5d5d 100644 --- a/i18n.js +++ b/i18n.js @@ -301,8 +301,10 @@ function preferredLanguage() { if (allowed.includes(query)) return query; try { - const saved = localStorage.getItem("cwl-language"); - if (allowed.includes(saved)) return saved; + if (typeof window !== 'undefined' && window.localStorage) { + const saved = localStorage.getItem("cwl-language"); + if (allowed.includes(saved)) return saved; + } } catch (error) { // Fail securely: ignore localStorage errors in strict privacy modes } @@ -385,7 +387,9 @@ function setLanguage(lang) { }); try { - localStorage.setItem("cwl-language", lang); + if (typeof window !== 'undefined' && window.localStorage) { + localStorage.setItem("cwl-language", lang); + } } catch (error) { // Fail securely: ignore localStorage errors }