A stored HTML injection bug in the RamenHire job board let anyone fill out a public form and turn the team’s notification emails into a phishing platform.
How the bug slipped in
RamenHire runs on Next .js and Supabase and lets startups post jobs without logging in. Each submission triggers an email to the internal hiring team. The email body is built by stitching raw form fields—company name, job description, contact name—directly into an HTML string. No escaping or sanitisation happens before the string reaches the mail service.
Because the template treats user input as safe HTML, a malicious applicant can inject a fake “click here to verify” link. The payload stores on the server as part of the job listing, so every subsequent notification email carries the malicious code. An attacker can hide that link next to the real “View Dashboard” button, tricking a team member into revealing credentials or visiting a phishing site.
Why it mattered
The emails go to the core hiring crew, people who click links constantly to move candidates through the pipeline.
The technical gap
Escaping HTML isn’t a single step. Two contexts need protection:
- Text nodes – content between tags. Converting
<to<and>to>stops tags, but doesn’t stop an attacker from breaking out of an attribute value. - Attribute values – strings inside tags such as
href="…". Injecting a quote ("or') lets an attacker close the attribute early and insert their own markup.
The original code only handled plain text, leaving attribute injection wide open.
Fixing the problem
The developer added a small escapeHtml helper and applied it to every user-supplied value before interpolation, whether the value ends up in a text node or an attribute.
Verification steps
- Local testing – A suite of injection strings fed the template functions. Each test showed only escaped characters, no executable tags.
- Production testing – After deploying the patch, a payload was submitted through the live site, passing the Cloudflare Turnstile bot check. The resulting email landed in the developer’s inbox; the malicious link and any script tags appeared as garbled text, not clickable elements.
A note on Gmail: the service may still colour a URL blue and make it clickable, but that’s a client-side convenience and does not mean the HTML injection succeeded. The fix stops the injection from creating real buttons or running scripts.
What developers should watch
- Never trust data from public forms – Even innocuous-looking forms can produce stored output used elsewhere.
- Escape at the point of use – Apply context-aware escaping (text vs. attribute) right before rendering, not earlier in the pipeline.
- Test both client-side and server-side – Unit tests catch obvious failures; a manual end-to-end test through the live UI confirms defenses hold under real traffic.
- Be aware of email client quirks – Some clients auto-link URLs, creating a false sense of security. Verify the underlying HTML contains no active elements.
Bottom line
A single oversight in handling user input turned routine notification emails into a phishing vector. Introducing a comprehensive HTML-escaping routine and validating the fix locally and in production restored inbox integrity. The episode underscores a timeless lesson for any web app that generates HTML from untrusted data: proper sanitisation is non-negotiable, and ignoring it can send a direct line to your users’ inboxes.
