SEC2-084: shipped() lets a catalogue key override the build's languageTag and direction #39

Closed
opened 2026-10-17 10:40:21 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-10-16 in a read-only security review of postulo-chromium at b6c1009, checked by a second, independent pass.

Severity: Informational · Category: Input Validation · Reference: CWE-20 (Improper Input Validation); OWASP A05:2021 (Security Misconfiguration)

Location:

  • scripts/build.mjs:33-39
  • src/lib/i18n.js:31-33, 70-72 (where the values are used: Accept-Language, documentElement.lang/dir, every toLocale… call)

Issue

export function shipped(catalogue, language) {
  return {
    languageTag: { message: language.tag, description: "This catalogue's language: set by the build." },
    direction: { message: language.direction, description: "ltr or rtl: set by the build." },
    ...catalogue,
  };
}

The comment above it explains that these two values were moved out of the catalogues precisely
because "a translator, or machine translation, could 'translate' ltr". The spread puts the
catalogue last, so a catalogue that carries either key wins over the build. No catalogue in
src/_locales carries them today (0 of 40), and Weblate offers translators only the keys en
has, so this is latent. If one came back (a catalogue imported by hand, or en regaining the
key), the value would feed Accept-Language, lang/dir, and toLocaleDateString(tag), which
throws on a tag that is not one and would stop the popup from drawing.

Impact

None today; a one-line trap for the next person who touches the catalogues.

Fix

Spread first, set after: { ...catalogue, languageTag: {...}, direction: {...} }.

Done when test/locales.test.mjs has a case where the catalogue carries
languageTag: { message: "nonsense" } and the shipped one still says language.tag.

#10–#13 (closed) are the language roll-out and do not cover the override.

Found on 2026-10-16 in a read-only security review of `postulo-chromium` at `b6c1009`, checked by a second, independent pass. **Severity:** Informational · **Category:** Input Validation · **Reference:** CWE-20 (Improper Input Validation); OWASP A05:2021 (Security Misconfiguration) **Location:** - `scripts/build.mjs:33-39` - `src/lib/i18n.js:31-33, 70-72` (where the values are used: `Accept-Language`, `documentElement.lang`/`dir`, every `toLocale…` call) ## Issue ```js export function shipped(catalogue, language) { return { languageTag: { message: language.tag, description: "This catalogue's language: set by the build." }, direction: { message: language.direction, description: "ltr or rtl: set by the build." }, ...catalogue, }; } ``` The comment above it explains that these two values were moved out of the catalogues precisely because "a translator, or machine translation, could 'translate' `ltr`". The spread puts the catalogue last, so a catalogue that carries either key wins over the build. No catalogue in `src/_locales` carries them today (0 of 40), and Weblate offers translators only the keys `en` has, so this is latent. If one came back (a catalogue imported by hand, or `en` regaining the key), the value would feed `Accept-Language`, `lang`/`dir`, and `toLocaleDateString(tag)`, which throws on a tag that is not one and would stop the popup from drawing. ## Impact None today; a one-line trap for the next person who touches the catalogues. ## Fix Spread first, set after: `{ ...catalogue, languageTag: {...}, direction: {...} }`. Done when `test/locales.test.mjs` has a case where the catalogue carries `languageTag: { message: "nonsense" }` and the shipped one still says `language.tag`. ## Related #10–#13 (closed) are the language roll-out and do not cover the override.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-chromium#39
No description provided.