Skip to content

fix: text matching Object.prototype property names silently not rendered (closes #746)#758

Open
Vitalini wants to merge 1 commit into
vercel:mainfrom
Vitalini:fix/grapheme-images-prototype-lookup
Open

fix: text matching Object.prototype property names silently not rendered (closes #746)#758
Vitalini wants to merge 1 commit into
vercel:mainfrom
Vitalini:fix/grapheme-images-prototype-lookup

Conversation

@Vitalini

Copy link
Copy Markdown

Summary

Fixes #746.

graphemeImages was created as a plain object via { ...options.graphemeImages }, which means it inherits from Object.prototype. When the text being rendered exactly matches an inherited property name (e.g. `constructor`, `toString`, `valueOf`, `hasOwnProperty`), the lookup graphemeImages[text] returned the inherited method instead of `undefined`. Downstream code then treated that function as an image URL and emitted `<image href="function Object() { [native code] }" ...>` in the SVG output instead of rendering the text as glyph paths.

Repro (from the issue)

await satori({ type: 'div', props: { children: 'constructor' } }, { width: 600, height: 100, fonts: [...] })
// Before: <image href=\"function Object() { [native code] }\" .../>
// After:  <path d=\"...\"> (text rendered as glyph paths)

Affected strings: any exact match of an `Object.prototype` property — `constructor`, `toString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable`, `toLocaleString`, `proto`. Real-world example from the issue: titles containing the word "constructor" silently dropped from OGP images.

Fix

Change the backing storage in `src/satori.ts` from `{ ...options.graphemeImages }` to `Object.assign(Object.create(null), options.graphemeImages)`. The resulting object has no prototype, so bracket lookups for non-own properties return `undefined` as expected, and the text falls through to the normal glyph-rendering path. No behaviour change for legitimate user-provided emoji/image keys.

Test plan

  • Added a case in `test/emoji.test.tsx` that renders `constructor toString valueOf` without any graphemeImages and asserts the SVG contains no `[native code]`, `[object Object]`, or `` element.
  • All existing emoji, basic, and error tests continue to pass (22 passing across the three files).

@Vitalini Vitalini requested a review from shuding as a code owner May 15, 2026 20:24
@vercel

vercel Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
satori-playground Canceled Canceled Open in v0 Jun 12, 2026 9:49pm

Fixes vercel#746.

`graphemeImages` was created as a plain object via `{ ...options.graphemeImages }`,
which means it inherits from `Object.prototype`. When the text being rendered
exactly matches an inherited property name (e.g. "constructor", "toString",
"valueOf", "hasOwnProperty"), the lookup `graphemeImages[text]` returned the
inherited method instead of `undefined`. Downstream code then treated that
function as an image URL and emitted `<image href="function Object() { [native
code] }" ...>` instead of rendering the text as glyph paths.

The fix changes the backing storage to a null-prototype object via
`Object.assign(Object.create(null), options.graphemeImages)`. Bracket lookups
for non-own properties now return `undefined` as expected, and the text falls
through to the normal glyph-rendering path. No behaviour change for legitimate
emoji/image keys.

Test plan: new case in `test/emoji.test.tsx` that renders the text
"constructor toString valueOf" without any graphemeImages and asserts the SVG
contains no `[native code]`, `[object Object]`, or `<image>` element. All
existing emoji and basic tests continue to pass.
@Vitalini Vitalini force-pushed the fix/grapheme-images-prototype-lookup branch from 7cf7621 to 5e200f1 Compare June 12, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Text matching Object.prototype property names (e.g. "constructor") is silently not rendered

1 participant