diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 276b7db..535867d 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,7 @@ **Vulnerability:** Missing Content Security Policy (CSP) header. **Learning:** Static HTML sites often neglect CSP, leaving them exposed to XSS or data injection if they later add dynamic content or if third-party scripts are compromised. **Prevention:** Always include a strict baseline CSP (`default-src 'self'`) in `index.html` for static sites to establish defense-in-depth from the start. +## 2024-10-24 - Fix prototype pollution risk in i18n language parsing +**Vulnerability:** Unvalidated user input from `location.search` was used directly as an object key (`messages[query]`). This allowed an attacker to supply `?lang=__proto__` or `?lang=valueOf`, resulting in `messages[query]` returning built-in objects or functions rather than the intended language strings. +**Learning:** Checking for truthiness like `messages[query] ? query : ...` fails securely when dealing with inherited Object properties. Without a whitelist, user input accessing un-prototyped object keys is dangerous. +**Prevention:** Always validate external input against a strict whitelist (e.g. `['ko', 'en'].includes(query)`) before using it in application logic, or ensure dictionaries are created without prototypes (`Object.create(null)`). diff --git a/i18n.js b/i18n.js index 75bc0f3..4729431 100644 --- a/i18n.js +++ b/i18n.js @@ -286,10 +286,14 @@ const messages = { }; function preferredLanguage() { + const allowed = ["ko", "en"]; const query = new URLSearchParams(window.location.search).get("lang"); + if (allowed.includes(query)) return query; + const saved = localStorage.getItem("cwl-language"); - const browser = navigator.language?.toLowerCase().startsWith("ko") ? "ko" : "en"; - return messages[query] ? query : messages[saved] ? saved : browser; + if (allowed.includes(saved)) return saved; + + return navigator.language?.toLowerCase().startsWith("ko") ? "ko" : "en"; } function setLanguage(lang) {