Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
**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)`).
## 2024-06-21 - Fix SecurityError crash from strict privacy modes
**Vulnerability:** Unhandled exceptions when accessing `localStorage` in strict browser privacy modes (e.g., when cookies are blocked).
**Learning:** Browsers throw a `SecurityError` when `localStorage` is accessed and the user has blocked third-party cookies or is in a strict privacy mode. If unhandled, this crashes the executing script, leading to a degraded user experience (DoS-like behavior for privacy-conscious users).
**Prevention:** Always wrap `localStorage.getItem` and `localStorage.setItem` in `try-catch` blocks to fail securely and fall back to sensible defaults.
14 changes: 11 additions & 3 deletions i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,12 @@ function preferredLanguage() {
const query = new URLSearchParams(window.location.search).get("lang");
if (allowed.includes(query)) return query;

const saved = localStorage.getItem("cwl-language");
if (allowed.includes(saved)) return saved;
try {
const saved = localStorage.getItem("cwl-language");
if (allowed.includes(saved)) return saved;
} catch (error) {
// Fail securely: ignore localStorage errors in strict privacy modes
}

return navigator.language?.toLowerCase().startsWith("ko") ? "ko" : "en";
}
Expand Down Expand Up @@ -355,7 +359,11 @@ function setLanguage(lang) {
}
});

localStorage.setItem("cwl-language", lang);
try {
localStorage.setItem("cwl-language", lang);
} catch (error) {
// Fail securely: ignore localStorage errors
}
currentLang = lang;
}

Expand Down
Loading