Skip to content

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

Description

@sorafujitani

Summary

When the text content passed to Satori exactly matches a property name that exists on Object.prototype (e.g. "constructor", "toString", "valueOf"), the text is not rendered. Instead, an <image> element with an invalid href is emitted in the SVG output.

Real-world example

The title "TypeScriptで使えるキーワードの中で一番長いのはconstructorで11文字" — the word "constructor" is missing from the rendered OGP image:

Image

Reproduction

import satori from "satori";

const fontData = await fetch(
  "https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.8/files/inter-latin-700-normal.woff",
).then((r) => r.arrayBuffer());

const svg = await satori(
  { type: "div", props: { children: "constructor" } },
  {
    width: 600,
    height: 100,
    fonts: [{ name: "Inter", data: fontData, weight: 700, style: "normal" }],
  },
);

console.log(svg);
// Expected: <path d="..."> (text rendered as glyph paths)
// Actual:   <image href="function Object() { [native code] }" .../>

Affected strings

Any string that is an exact match for an Object.prototype property name:

Text SVG output (href)
constructor function Object() { [native code] }
toString function toString() { [native code] }
valueOf function valueOf() { [native code] }
hasOwnProperty function hasOwnProperty() { [native code] }
__proto__ [object Object]

Substrings or case variations are not affected (e.g. "Constructor", "constructors" all render correctly).

Hypothesis

An internal object is being used as a dictionary with bracket-notation lookup (e.g. obj[text]). When the key matches an inherited Object.prototype property, a truthy value is returned instead of undefined, causing the text to enter an unintended code path (image rendering instead of text rendering).

Workaround

Insert a zero-width space (\u200B) within the affected word before passing it to Satori:

const safeText = text.replace(
  /\b(constructor|toString|valueOf|hasOwnProperty|isPrototypeOf|propertyIsEnumerable|toLocaleString)\b/g,
  (m) => m.slice(0, 3) + "\u200B" + m.slice(3),
);

Environment

  • satori: 0.19.1
  • Node.js: v22.15.0
  • OS: macOS (Darwin 23.6.0)
  • Font: any (reproduced with Inter, Noto Sans JP)

I'm happy to submit a PR for this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions