GHSA-r27j-894h-3w3p mcp-data-vis vulnerable to denial of service via unsanitized `select` key lookup on `Object.prototype` with `precompile: true`
Summary`icu-minify`'s runtime formatter resolves `select` branches by looking up the runtime value as a plain property on a prototype-bearing object. When the value coerces to a key that exists on `Object.prototype` (e.g. `toString`, `__proto__`, `constructor`, `hasOwnProperty`, `valueOf`), the lookup returns a truthy value that short-circuits the `?? options.other` fallback, and the downstream iterator crashes with `TypeError: nodes is not iterable`. Any consumer that forwards user input into a `{arg, select, …}` placeholder — a common idiom for `role`, `status`, `type`, `gender` — can be crashed per-request by supplying one of those keys. In Next.js SSR (via `next-intl` with `experimental.messages.precompile`) this yields a 500 for the affected render.DetailsVulnerable code pathsCompilation produces a plain object whose prototype chain includes all `Object.prototype` members:At runtime, the formatter looks up the user-controllable value directly on that object:Because `options` inherits from `Object.prototype`, lookups such as `options['toString']` return `Object.prototype.toString` — a truthy `Function`. The `?? options.other` fallback is therefore skipped, and the non-array, non-string branch is passed to `formatBranch`, which forwards it to `formatNodes`:Five bare-prototype keys reliably crash the formatter in production: `toString`, `__proto__`, `constructor`, `hasOwnProperty`, `valueOf` (plus `propertyIsEnumerable`, `isPrototypeOf`, `toLocaleString`). Note the development branch at line 237 (`throw new Error('No matching branch for select …')`) is bypassed because the inherited function is truthy — so this is not masked in development either.Why `formatPlural` is not affected`formatPlural` (format.tsx:246-284) looks safe for two independent reasons and does not need to be patched for this specific bug:Exact-match keys use the `=${value}` prefix (`exactKey = '=' + value`, line 263), so the attacker would need to supply e.g. `=toString`, which is not a member of `Object.prototype`.The category branch uses `formatOptions.formatters.getPluralRules(locale, {type}).select(value)` which returns a fixed enum (`zero|one|two|few|many|other`), never attacker-supplied.The bug is specific to the `select` path where the raw string value is used as the lookup key.Reachability**Direct consumers of `icu-minify`**: any code calling `format(compiled, locale, values, …)` where `values[arg]` for a `select` placeholder comes from user input is vulnerable with no additional preconditions.**`next-intl` users** who enable `experimental.messages.precompile` (`packages/next-intl/src/plugin/types.tsx:24`, wired in `packages/next-intl/src/plugin/getNextConfig.tsx:177-293`): the runtime at `packages/use-intl/src/core/format-message/format-only.tsx` forwards directly to `icu-minify/format`, so `t('msg', {role: req.query.role})` against a `{role, select, admin {…} other {…}}` message crashes the render.No middleware, type guard, escaping, or framework default stands between user input and the unsafe lookup — `values` reaches `format()` unmodified.PoCVerified dynamically against `packages/icu-minify/src/format.tsx` at commit `b4aa538` (v4.9.1) with vitest and `NODE_ENV=production`.Reproduction (drop into `packages/icu-minify/test/poc.test.ts` and run `pnpm exec vitest run test/poc.test.ts`):Observed output (each of the 5 keys):End-to-end Next.js scenario (illustrative — any attacker-controlled `role`/`status`/`type`/`gender` forwarded into a `select` placeholder triggers the same exception inside the server render):Impact**Availability**: An unauthenticated attacker can force a 500 response on any page or API route that formats a `select` ICU message using user-controllable input. Each request fails independently; there is no persistent state corruption or amplification beyond the malicious request.**Confidentiality / Integrity**: None. No data is leaked and no prototype write occurs — this is a prototype-chain *read* confusion, not a prototype pollution write.**Scope**: Any consumer of `icu-minify` that passes user input into a `select` branch is vulnerable. `next-intl` users are only exposed if they have opted into the experimental `experimental.messages.precompile` flag.**Preconditions**: Developer must forward untrusted input to a `{arg, select, …}` placeholder. This is a routine pattern (`role`, `status`, `gender`, `type`) and the library offers no documentation warning that `select` keys must be validated against prototype members.Recommended FixEither of the following (defense-in-depth suggests both). Both are one-line, minimal-churn fixes.Use a null-prototype map in `compileSelect` (and symmetrically in `compilePlural`) so that no `Object.prototype` keys can ever be resolved:Gate the runtime lookup with `Object.prototype.hasOwnProperty.call` so the `other` fallback is reached for any non-own key:Option 1 is preferable because it also survives future serialization round-trips (e.g. JSON-hydrated compiled messages) and removes the hazard at the source. Option 2 is a defensive backstop for any code path that constructs `SelectOptions` from arbitrary JSON at runtime.No regression is expected in tests — `compileSelect` never reads back through the prototype chain, and all existing lookups use own properties.
CVSS v3.1 score: 3.7