Skip to content

[Low][i18n] interpolate/ICU resolve inherited Object.prototype names via 'in' operator (contract violation) #174

Description

@JosunLP

Severity: 🟢 Low (correctness + minor info-shape leak)

Location

src/i18n/translate.ts:49-54 (interpolateif (key in params)); src/i18n/icu.ts:280 (arg node) and :284 (select node).

Description

Placeholder resolution uses the in operator, which walks the prototype chain, then String(params[key]). A placeholder whose name matches an Object.prototype member (toString, valueOf, constructor, hasOwnProperty, __proto__, …) is treated as "present" even when params is {}, and is substituted with the inherited value instead of being left as the literal {name}.

interpolate('Hello {toString}', {});
// => 'Hello function toString() { [native code] }'
// expected (documented contract): 'Hello {toString}'

Same for formatMessage('{x, select, other {{toString}}}', {}). Impact is limited (params is app-supplied and the placeholder must collide with a prototype member), so it is primarily a contract violation of "unmatched placeholders are left intact", with a minor info-shape leak (native function source) when a translation catalog is attacker/translator-controlled and the param is absent. Not a pollution or code-execution vector.

Suggested fix

Use an own-property check consistently at all three sites:

if (Object.prototype.hasOwnProperty.call(params, key)) {  }

Consider the same guard defensively in resolveKey (translate.ts:20-31), which currently traverses key segments without a prototype check (mitigated today only because messages are built with Object.create(null)).


Filed as part of a full-codebase security & correctness audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingi18nChanges to the i18n modulelow-priorityLow severity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions