From 5e200f12b894f01f8db6c3d34ec04a27849a28ec Mon Sep 17 00:00:00 2001 From: Vitalii <2556969@gmail.com> Date: Fri, 15 May 2026 16:23:51 -0400 Subject: [PATCH] fix: text matching Object.prototype property names silently not rendered 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 `` 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 `` element. All existing emoji and basic tests continue to pass. --- src/satori.ts | 9 ++++++++- test/emoji.test.tsx | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/satori.ts b/src/satori.ts index fbd9f1b8..496e284a 100644 --- a/src/satori.ts +++ b/src/satori.ts @@ -73,7 +73,14 @@ export default async function satori( root.setJustifyContent(Yoga.JUSTIFY_FLEX_START) root.setOverflow(Yoga.OVERFLOW_HIDDEN) - const graphemeImages = { ...options.graphemeImages } + // Null-prototype object so that `graphemeImages[text]` lookups for strings + // that match `Object.prototype` property names (e.g. "constructor", + // "toString", "valueOf") return `undefined` instead of inherited methods. + // See https://github.com/vercel/satori/issues/746. + const graphemeImages: Record = Object.assign( + Object.create(null), + options.graphemeImages + ) // Some Chinese characters have different glyphs in Chinese and // Japanese, but their Unicode is the same. If the user needs to display // the Chinese and Japanese characters simultaneously correctly, the user diff --git a/test/emoji.test.tsx b/test/emoji.test.tsx index 5a25f90d..01dae7b4 100644 --- a/test/emoji.test.tsx +++ b/test/emoji.test.tsx @@ -116,4 +116,18 @@ describe('Emojis', () => { expect(await toImage(svg)).toMatchImageSnapshot() }) + + // https://github.com/vercel/satori/issues/746 + it('should render text that matches Object.prototype property names', async () => { + const svg = await satori(
constructor toString valueOf
, { + width: 200, + height: 100, + fonts, + }) + // The text used to be silently replaced with the inherited prototype + // method, e.g. ``. + expect(svg).not.toContain('[native code]') + expect(svg).not.toContain('[object Object]') + expect(svg).not.toContain('