Taps can be unreliable inside this app's built-in browser. For the full experience, use its menu to open this page in your browser.
Skip to content

Putting A Stranger's Name On An Image

A devlog by Neeraj Kotwani, who builds every game on Elipar.

Cake Slice is a timing game: a knife sweeps round a cake and you tap to drop each cut, trying to leave even wedges. Five rounds, more guests each time.

The reason it exists is the bit after the game. You type the birthday person's name, play, and send them the result — so what they see in WhatsApp is a cake with their name and your score as candles. Every other share on this site is a score, which is a dare aimed back at the sender. This is the first one addressed to the person opening it.

That makes it the first place on this site where a visitor's own typed text is rendered into an image served from our domain, and that is a materially different risk from a leaderboard name.

Why this is stricter than the comment filter

The site already has a filter for comments. It masks a word list and strips links, and carries on. That is the right call for a comment box.

It is the wrong call here, for one reason: the image is what a stranger sees before they have clicked anything, and it carries this site's name. So the rules are deliberately tighter.

The name is never stored — it lives only in the URL the sharer chose to send, and nothing on the server or in the database records it. The character set is a whitelist rather than a blocklist, which is what makes a URL or markup impossible rather than merely filtered; there is no colon, slash, angle bracket or dot left to build one out of. And a name containing a slur is rejected outright rather than masked, because "Happy Birthday F***!" rendered onto a card is a worse outcome than an unpersonalised one.

One module does this and three paths call it: the share-link builder, the page metadata, and the image route. A name that passes one cannot fail another, so the preview title and the preview image cannot disagree about what the card says.

Three bugs in my own sanitiser

All three found by writing adversarial cases, none by re-reading the code. The worst one made a whole feature dead:

The leetspeak folding was dead code. I folded characters like 1 to i and @ to a before checking the word list — except the whitelist had already stripped those characters, so by the time the check ran there was nothing left to fold. The folding has to run on the raw input, before anything is removed. It read as a thoughtful extra layer and did nothing at all.

A missing-name returned a real value. Sanitising the star count returned 1 when given nothing — and 1 is a genuine result the game awards. So "no card" and "one candle" collapsed into the same thing.

A bare domain slipped the filter. Something like evil.com with no scheme passed the character check. Fixed by reusing the comment filter's existing URL pattern — and that needed care, because the pattern carries the global flag, so test() advances its internal index and answers differently on alternate calls. It is built fresh per call now. A regex with /g used for a boolean test is a genuinely nasty bug: it works, then does not, then works.

The limitation no font fixes

I wanted non-Latin names to work. They do not, and the reason is worth being precise about because the obvious fix is wrong.

The renderer behind the image route does no complex-script shaping. A Devanagari name renders — every glyph is present, nothing is missing — but unshaped: conjuncts do not form and a pre-posed vowel sign is not reordered, so the codepoints come out in logical order. To anyone who can read it, that is a misspelling.

Verified by rendering the real card and looking at it, not assumed. Adding a font does not help; the gap is shaping, not coverage.

A birthday card with the name spelled wrong is worse than one with no name on it. So non-Latin names are rejected rather than silently mangled, and the game says English letters only up front and warns while you type rather than letting the name vanish between typing and sharing. Accents are fine — the whitelist is Latin script plus combining marks, so José renders perfectly.

If that renderer ever gains shaping, widening the pattern and deleting one function is the whole change. There is a comment saying so.

The frame re-checks the name, and is stricter still

The game runs in a sandboxed iframe with its own URL, which is directly reachable. So the shared module has already cleaned anything the site forwards — but nothing has cleaned a URL typed straight at the frame.

My first version reused the parent's strip rule there, and a name of markup plus a domain rendered a cheerful birthday card for "bEvlb evilcom": salvaged into something plausible-looking. It rejects outright now. Anything outside the whitelist kills the whole name.

The game is a standalone static file with no build step, so it cannot import the module — two copies, which is the classic drift hazard. Making the copy strictly more conservative is what makes the drift safe: it can never reject a name the site would actually send.

Stars are clamped rather than rejected, which looks inconsistent and is not. A mistyped link showing a friendly card beats showing nothing, and refusing an out-of-range number stops nobody from editing a URL they were handed. My first test asserted the opposite and the test was wrong, not the code.

And the signal that tells the parent page "this run is building a card, do not draw your own score banner over it" rides on every round's report, while the "card is ready now" signal rides only on the last. An earlier version sent both at the end, so round four's banner was still on screen covering the finished card when round five landed. The source comment describing that still said "final report only" while the code did the right thing — fixed, because a comment that argues for the bug is how the bug comes back.

Cake Slice is free in a browser, no account, no download. Nothing you type is kept.

We use cookies to keep games running and, once ads are live, to show relevant content. See our Privacy Policy for details.