กับดัก Vibe Coding: 6 ช่องโหว่ React ที่ซ่อนอยู่
บทความนี้ครอบคลุมโครงการที่สร้างด้วย React + Next.js App Router หลักการความปลอดภัยเป็นสากล แต่ตัวอย่างโค้ดและไฟล์กำหนดค่า (
next.config.js,vercel.json) จะเฉพาะเจาะจงสำหรับสแต็กนี้
คุณขอให้ผู้ช่วย AI เขียนส่วนประกอบให้ มันเขียนให้ มันทำงานได้ การทดสอบผ่านหมด คุณคลิก Merge
และหลังจากนั้น — ข้อมูลรั่วไหล, คะแนน Google PageSpeed เป็นศูนย์, ข้อร้องเรียนจากผู้พิการทางสายตา และคำถามที่ว่า: “ทำไมเราไม่มี security headers?”
เครื่องมือเขียนโค้ด AI ไม่รู้บริบทการใช้งานจริงของคุณ (production) สำหรับพวกเขาไม่มีความแตกต่างระหว่างบทช่วยสอนสำหรับผู้เริ่มต้นกับแอปพลิเคชันทางการเงินที่มีผู้ใช้จริง ผลลัพธ์ที่ได้คือ พวกเขาเขียนโค้ดที่ “ทำงานได้” แต่แอบละเมิดกฎจาก OWASP Top 10 อย่างเงียบๆ
นี่คือ 6 ช่องโหว่ที่ผู้ช่วย AI มักสอดแทรกเข้าไปในโค้ด React + Next.js และวิธีค้นหาพวกมันใน 10 วินาที
Fact-Check: AI สร้างโค้ดที่มีช่องโหว่ได้อย่างไร
🔍 หลักฐาน · ความลำเอียงของข้อมูลฝึกฝน · การไม่เข้าใจบริบท
ตาม การวิเคราะห์พื้นที่เก็บข้อมูลสาธารณะของ GitHub พบว่าเปอร์เซ็นต์ที่สำคัญของการรั่วไหลด้านความปลอดภัยในโครงการ React มาจากรูปแบบทั่วไปที่พบในข้อมูลการฝึกฝน เช่น การใช้ dangerouslySetInnerHTML โดยไม่มีการทำความสะอาด (sanitization) หรือการใส่ค่าตัวแปรสภาพแวดล้อมลงในโค้ดฝั่งหน้าบ้านโดยตรง (hardcoding)
ในการทดสอบภายในของเราที่ WebValid พบว่าผู้ช่วยเขียนโค้ด AI (Cursor, Copilot, ChatGPT) ประสบความสำเร็จในการระบุและแก้ไขช่องโหว่ได้ก็ต่อเมื่อได้รับ รายงานที่มีโครงสร้าง จากเครื่องมือสแกนภายนอกเท่านั้น เมื่อถูกขอให้ “ตรวจสอบโค้ดนี้เพื่อความปลอดภัย” โดยไม่มีบริบทเพิ่มเติม โมเดลพลาดช่องโหว่ทางสถาปัตยกรรม (เช่น ปัญหาการรั่วไหลของ API key ที่อธิบายด้านล่าง) ถึง 68%
การใช้ dangerouslySetInnerHTML โดยไม่ทำความสะอาด
🔴 วิกฤต · การขโมยเซสชันผู้ใช้, ค่าปรับ GDPR · OWASP A03:2021 Injection
ผู้ช่วย AI ชอบรูปแบบนี้มาก เมื่อคุณขอให้ “แสดงผล HTML จาก API” พวกเขามักจะเขียนแบบนี้:
// ❌ โค้ด AI ที่ไม่ดี
function UserBio({ bio }: { bio: string }) {
return <div dangerouslySetInnerHTML={{ __html: bio }} />;
}
ถ้า bio มาจากผู้ใช้ — ยินดีด้วย คุณโดน XSS แล้ว ผู้โจมตีจะใส่ <script>document.cookie</script> และขโมยเซสชันของผู้ใช้ไป
// ✅ วิธีแก้ไข: ทำความสะอาดผ่าน DOMPurify
import DOMPurify from "dompurify";
function UserBio({ bio }: { bio: string }) {
const sanitizedBio = DOMPurify.sanitize(bio);
return <div dangerouslySetInnerHTML={{ __html: sanitizedBio }} />;
}
จากข้อมูล การค้นหาบน GitHub พบการใช้
dangerouslySetInnerHTMLโดยไม่ทำความสะอาดในโครงการ React นับพันโครงการ ผู้ช่วย AI ทำซ้ำรูปแบบนี้จากข้อมูลการฝึกฝน โดยไม่ได้ตั้งคำถามว่า “HTML นี้มาจากไหน?”
API key รั่วไหลใน Client Bundle
🔴 วิกฤต · ความสูญเสียทางการเงินโดยตรง · OWASP A02:2021 Cryptographic Failures
บาปที่พบบ่อยที่สุดของ Vibe Coding คือการที่ผู้ช่วย AI มักจะ “แก้ไข” การเรียกใช้ API ฝั่งไคลเอนต์ที่เสีย โดยการเพิ่มคำนำหน้า NEXT_PUBLIC_ ให้กับคีย์ลับของคุณ สิ่งนี้ทำให้การเรียกใช้งานผ่านได้ แต่มันกลับไปฝังคีย์ลับของคุณไว้ใน JavaScript bundle ที่เป็นสาธารณะ ซึ่งใครๆ ก็มองเห็นได้ผ่าน DevTools
// ❌ โค้ด AI ที่ไม่ดี — คีย์รั่วไหลเข้าไปใน JS bundle
"use client";
const response = await fetch("https://api.openai.com/v1/chat/completions", {
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`, // ❌ รั่วไหล
},
});
วิธีแก้ไข: ย้ายตรรกะที่ละเอียดอ่อนทั้งหมดไปยัง Server Action หรือ Route Handler ซึ่งตัวแปรสภาพแวดล้อมจะถูกเก็บไว้อย่างปลอดภัยที่ฝั่งเซิร์ฟเวอร์เท่านั้น
สำหรับการเจาะลึกเกี่ยวกับกลไกการรั่วไหลของ bundle และวิธีใช้การป้องกันขั้นสูง เช่น Next.js Taint API โปรดดูคู่มือที่ครอบคลุมของเรา: คู่มือการรั่วไหลของ API Key
ขาด Security Headers (CSP, X-Frame-Options, HSTS)
🟠 สูง · Clickjacking, การแย่งชิงโทเค็นผ่าน MitM · OWASP A05:2021 Security Misconfiguration
ผู้ช่วย AI ไม่ได้กำหนดค่า HTTP headers ให้ ซึ่งเข้าใจได้เพราะพวกเขาเขียนส่วนประกอบ ไม่ใช่การตั้งค่าเซิร์ฟเวอร์ ผลลัพธ์คือไซต์ของคุณอาจถูกฝังใน <iframe> บนโดเมนที่ประสงค์ร้าย ทำให้เกิดการโจมตีแบบ clickjacking ได้
สำหรับ Next.js + Vercel มี สองวิธี ในการเพิ่ม headers:
วิธีแรก — next.config.js (สำหรับการโฮสต์เอง):
// ✅ next.config.js
const securityHeaders = [
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; script-src 'self';",
},
];
module.exports = {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
วิธีที่สอง — vercel.json (สำหรับ Vercel — แนะนำ):
{
"headers": [
{
"source": "/:path*",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=63072000; includeSubDomains; preload"
},
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
]
}
]
}
WebValid จะช่วยตรวจสอบการมีอยู่ของ headers เหล่านี้ทั้งหมดในการตอบสนอง HTTP ได้ในการสแกนครั้งเดียว
console.log รั่วไหลข้อมูลที่ละเอียดอ่อนไปยัง Production
🟡 ปานกลาง · ข้อมูลส่วนบุคคลรั่วไหลไปยังเครื่องมือบันทึกภายนอก · OWASP A09:2021 Security Logging Failures
ระหว่างการทำ Vibe Coding มักจะมีโค้ดสำหรับการแก้ปัญหา (debug logs) หลงเหลืออยู่:
// ❌ โค้ด AI ที่ไม่ดี
async function loginUser(credentials: Credentials) {
console.log("Login attempt:", credentials); // รหัสผ่านรั่วไหลใน Log!
const user = await authService.login(credentials);
console.log("User logged in:", user); // โทเค็นรั่วไหลใน Log!
return user;
}
หาก Log ในการใช้งานจริง (production) ถูกรวบรวมไว้ในเครื่องมืออย่าง Sentry, Datadog หรือ Logtail — คุณเพิ่งจะ ส่งรหัสผ่านของผู้ใช้ไปยังบริการภายนอก
// ✅ วิธีแก้ไข: บันทึกเฉพาะข้อมูลที่ไม่ละเอียดอ่อน
import { createLogger } from "@your-scope/logger";
const logger = createLogger({ scope: "AuthService" });
async function loginUser(credentials: Credentials) {
logger.info("Login attempt", { email: credentials.email }); // เฉพาะ email
const user = await authService.login(credentials);
logger.info("Login successful", { userId: user.id }); // เฉพาะ ID
return user;
}
โครงสร้างทางเซมานติก (Semantic) และ ARIA ที่ผิดพลาด
🟡 ปานกลาง · เสียทราฟฟิก SEO, ละเมิดมาตรฐาน ADA · Accessibility Violation (WCAG 2.1)
ผู้ช่วย AI มักจะทำให้ทุกอย่างคลิกได้โดยการใส่ onClick ลงใน <div> ซึ่งมันรวดเร็วและดูเหมือนทำงานได้ แต่จะฆ่าทั้งความสามารถในการไต่เว็บ (crawlability) และการเข้าถึง (accessibility):
// ❌ โค้ด AI ที่ไม่ดี
function ProductCard({ product }: { product: Product }) {
return (
<div onClick={() => navigate(`/products/${product.id}`)}>
<div>{product.name}</div>
<div>{product.price}</div>
</div>
);
}
ปัญหาที่เกิดขึ้น:
- เครื่องอ่านหน้าจอ (Screen reader) จะไม่รู้ว่านี่คือองค์ประกอบที่ตอบโต้อีกได้ — ผู้พิการทางการเห็นจะไม่สามารถเข้าถึงผลิตภัณฑ์ได้
- Googlebot จะไม่สร้างกราฟของลิงก์ ผ่าน
onClick— จะสร้างผ่าน<a href>เท่านั้น - การไหลของ PageRank ภายในเว็บไซต์เสียไป
// ✅ วิธีแก้ไข: ใช้มาตรฐาน HTML ที่ถูกต้อง
function ProductCard({ product }: { product: Product }) {
return (
<article>
<a href={`/products/${product.id}`}>
<h2>{product.name}</h2>
<span>{product.price} $</span>
</a>
</article>
);
}
Server Actions ที่ไม่มีการตรวจสอบสิทธิ์
🔴 วิกฤต · การสั่งงานในนามของผู้ใช้อื่น · OWASP A01:2021 Broken Access Control
ผู้ช่วย AI มักจะสร้าง Server Actions แต่ลืมรวมการตรวจสอบสิทธิ์ (authorization checks) มาให้ด้วย
// ❌ โค้ด AI ที่ไม่ดี
"use server";
export async function deleteAccount(userId: string) {
await db.user.delete({ where: { id: userId } });
}
ใครๆ ก็สามารถส่ง POST request และลบบัญชีของคนอื่นได้
// ✅ วิธีแก้ไข: ตรวจสอบสิทธิ์ภายใน Action
"use server";
import { getServerSession } from "next-auth";
export async function deleteAccount(userId: string) {
const session = await getServerSession();
if (!session || session.user.id !== userId) {
throw new Error("Unauthorized");
}
await db.user.delete({ where: { id: userId } });
}
วิธีค้นหาทั้ง 6 ช่องโหว่ใน 10 วินาที
คุณสามารถตรวจสอบโค้ดด้วยตนเอง หรือใช้เวลาทั้งวันในการตรวจสอบอย่างละเอียด แต่มีวิธีที่เร็วกว่านั้น:
- รันโปรเจกต์ของคุณผ่าน ngrok เพื่อให้ได้ URL สาธารณะ
- วาง URL นั้นลงใน WebValid
- รับ
ai-fix-promptในรูปแบบ Markdown ที่พร้อมใช้งาน — นำไปวางในผู้ช่วย AI ของคุณและแก้ไขทุกอย่างใน 2 นาที
รายการตรวจสอบความปลอดภัยจาก Generative AI 6 ข้อของคุณ
- การจัดการอินพุต: AI ใช้
dangerouslySetInnerHTMLหรือไม่? - ตัวแปรสภาพแวดล้อม: มีคีย์ลับหลุดหรือไม่?
- ส่วนหัวความปลอดภัย: คุณได้กำหนดค่า
vercel.jsonหรือไม่? - ข้อมูลรั่วไหล: มีการใช้คำสั่ง
console.logโดยตรงหรือไม่? - SEO แบบโต้ตอบ: ตรวจสอบองค์ประกอบที่คลิกได้ของคุณ
- การตรวจสอบสิทธิ์การกระทำ: ทุกการกระทำ
'use server'เริ่มต้นด้วยการตรวจสอบสิทธิ์หรือไม่?
→ ทดสอบโปรเจกต์ของคุณได้ฟรีบน WebValid
เอกสารอ้างอิงอย่างเป็นทางการ
Security
Next.js
- Next.js: Security Headers
- Next.js: Environment Variables
- Next.js: Taint API
- Next.js: Server Actions Security