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:
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.
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 invalidhrefis emitted in the SVG output.Real-world example
The title
"TypeScriptで使えるキーワードの中で一番長いのはconstructorで11文字"— the word "constructor" is missing from the rendered OGP image:Reproduction
Affected strings
Any string that is an exact match for an
Object.prototypeproperty name:href)constructorfunction Object() { [native code] }toStringfunction toString() { [native code] }valueOffunction valueOf() { [native code] }hasOwnPropertyfunction 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 inheritedObject.prototypeproperty, a truthy value is returned instead ofundefined, 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:Environment
I'm happy to submit a PR for this.