ภาพวาดโทนสว่างในครัวรับแสงแดด กองกระดาษ Markdown ถูกป้อนเข้าเครื่องรีดสีเขียวแล้วออกมาเป็นหน้าเว็บที่จัดวางเรียบร้อย มีตะแกรงร่อนอยู่ปลายทาง แต่กระดาษสีส้มแผ่นหนึ่งเล็ดลอดผ่านไปตกบนโต๊ะ โดยมีตัวการ์ตูนขนมปังกลมชี้ไปที่แผ่นนั้น

Bun 1.4 มี Markdown Parser ในตัว: เร็วและสะดวก แต่ยังต้องตั้งด่านความปลอดภัย

พี่โชว์Engineering

Markdown ดูเหมือนเรื่องเล็กจนถึงวันที่ระบบต้องแสดง README, release note หรือบทความหลายพันชิ้น แล้วเราพบว่าต้องดูแล dependency เพิ่มอีกตัว ปรับ renderer อีกชุด และตอบคำถามด้านความปลอดภัยอีกกอง

Bun 1.4 รวม parser นี้มาใน runtime ผ่าน Bun.markdown แล้ว จุดขายคือเรียกใช้ได้ทันที รองรับ GitHub Flavored Markdown และเลือกผลลัพธ์ได้ทั้ง HTML, callback renderer และ React element แต่คำว่า “มีในตัว” ไม่ได้แปลว่า “ปลอดภัยสำหรับ input ทุกชนิด” ครับ

Bun 1.4 ประกาศเมื่อ 20 สิงหาคม 2026 ส่วน เอกสาร Bun.markdown ระบุว่า API ยังอยู่ระหว่างพัฒนาและอาจเปลี่ยนได้ในอนาคต เราจึงควรมองมันเป็นเครื่องมือเร็วในครัว ไม่ใช่พนักงานตรวจอาหารที่รับผิดชอบทุกอย่าง

WHY: ลดชิ้นส่วนได้ แต่ต้องรักษา trust boundary

Bun.markdown.html() รับข้อความแล้วคืน HTML string เหมาะกับเนื้อหาที่เราเชื่อถือ เช่นเอกสารใน repository ที่ผ่าน review ส่วน Bun.markdown.render() เปิดให้กำหนด callback ของ heading, paragraph, link หรือ code block เอง และ Bun.markdown.react() สร้าง React elements พร้อมเปลี่ยน component ของ tag ต่าง ๆ ได้

ประโยชน์คือทีมไม่ต้องดึง parser แยกมาเพียงเพื่อแปลง Markdown พื้นฐาน และสามารถใช้ parser เดียวกันทำหน้าเว็บกับ output ใน terminal ได้ แต่เส้นแบ่งความไว้ใจยังอยู่ที่เดิม: เนื้อหาจาก editor ในทีมไม่เท่ากับข้อความจาก comment form หรือ profile ของผู้ใช้

HOW: รันจริงและตรวจพฤติกรรมที่ต้องระวัง

ตัวอย่างนี้ทดสอบสองเรื่องพร้อมกัน คือ Markdown ปกติแปลงได้ และ raw HTML ที่มี event handler ยังผ่านออกมา ซึ่งเป็นหลักฐานว่าต้องมีขั้น sanitize แยกเมื่อรับ input ที่ไม่ไว้ใจ

import assert from "node:assert/strict";

const trustedMarkdown = "# คู่มือ\n\nใช้ **Bun 1.4** ได้เลย";
const trustedHtml = Bun.markdown.html(trustedMarkdown);

assert.match(trustedHtml, /<h1>คู่มือ<\/h1>/);
assert.match(trustedHtml, /<strong>Bun 1\.4<\/strong>/);

const untrustedMarkdown = '<img src="missing" onerror="alert(1)">';
const unsafeHtml = Bun.markdown.html(untrustedMarkdown);

// ยืนยันพฤติกรรมตามเอกสาร: output ไม่ได้ sanitize ให้เรา
assert.match(unsafeHtml, /onerror="alert\(1\)"/);

console.log("Bun.markdown behavior verified");

การมี assert ทำให้ตัวอย่างนี้ไม่ได้แค่พิมพ์ผลสวย ๆ ถ้า API หรือ output เปลี่ยนจนสมมติฐานไม่จริง โปรแกรมจะจบด้วย error และ quality gate จะไม่รับบทความ

Parser option ไม่ใช่ sanitizer

เอกสารมีตัวเลือกอย่าง noHtmlBlocks, noHtmlSpans และ tagFilter ซึ่งช่วยควบคุมรูปแบบที่ parser ยอมรับ แต่ไม่ควรเรียกรวม ๆ ว่าเป็นระบบ sanitize

Sanitizer มีหน้าที่ตรวจ HTML ที่ได้ด้วย allowlist เช่นยอมรับ <strong> แต่ไม่ยอมรับ event handler, protocol อันตรายใน link หรือ attribute ที่ไม่จำเป็น ส่วน parser มีหน้าที่เข้าใจ Markdown และสร้างโครงสร้างผลลัพธ์ สองงานนี้เหมือนคนแปลเอกสารกับเจ้าหน้าที่ตรวจสัมภาระ ทำงานต่อกันได้แต่แทนกันไม่ได้

แนวทางใช้งานจึงแบ่งได้ง่าย ๆ:

  • เนื้อหา trusted และผ่าน code review: render เป็น HTML ได้ โดยยังควรมี Content Security Policy เป็นแนวป้องกันเสริม
  • เนื้อหาจากผู้ใช้: sanitize ด้วย allowlist ที่เหมาะกับ HTML ฝั่ง server ก่อนเก็บหรือก่อน render และทดสอบ payload อันตรายเป็น regression case
  • ถ้าไม่ต้องการ HTML เลย: ใช้ Bun.markdown.render() แล้วคืนเฉพาะรูปแบบข้อความหรือ element ที่เราอนุญาต
  • ถ้าใช้ React: ตรวจ component override และ URL policy เพิ่ม อย่าคิดว่าการได้ React element ทำให้ข้อมูลเชื่อถือได้เอง

เลือก API ให้ตรงงาน

html() เหมาะกับ static generation, email ที่มี pipeline sanitize ชัดเจน หรือระบบเอกสารภายใน render() เหมาะเมื่ออยากสร้าง output แบบ terminal หรือบังคับ markup ขององค์กร ส่วน react() เหมาะกับ React Server Components และหน้าที่ต้อง override link, heading หรือ code block

ยังมีข้อควรคิดเรื่องอายุ API เพราะเอกสารติดป้าย unstable ถ้าจะใช้ใน production ให้ห่อ Bun.markdown หลังฟังก์ชันของเราเอง เช่น renderTrustedDocument() แทนการเรียกกระจายทั่ว codebase วันหนึ่ง signature หรือ option เปลี่ยน เราจะมีประตูให้แก้เพียงไม่กี่จุด

Recap

Bun 1.4 ทำให้การแปลง Markdown เริ่มต้นได้เร็วขึ้นจริง และ API สามแบบครอบคลุมทั้ง HTML, custom output และ React แต่ความเร็วของ parser ไม่ได้ย้ายเส้นความไว้ใจให้เรา

จำประโยคเดียวก็พอ: parse ให้ถูก แล้ว sanitize ตามแหล่งที่มา ถ้า input มาจาก repository ที่ review แล้วกับ input มาจากอินเทอร์เน็ตเดินเข้าประตูเดียวกัน ระบบก็เปิดช่องโหว่ตั้งแต่การออกแบบ ไม่ใช่เพราะ parser ช้าหรือเร็วครับ