สร้างกลไก "ไม่ให้ทำ" สำหรับผู้ช่วย AI เขียนโค้ด — จากคำแนะนำสู่การบังคับ แล้วพบว่าประตูนั้นเองก็มีช่องโหว่ข้าง
บทนำ
ผมใช้ผู้ช่วย AI เขียนโค้ดเป็นประจำทุกวัน เมื่อฝ่ายที่รับมอบงานเปลี่ยนไป ปัญหาคือจะทำให้ฝ่ายตรงข้ามปฏิบัติตาม “สิ่งที่อยากให้ระวัง” ของเราได้อย่างไร
คนส่วนใหญ่จะเริ่มด้วย คำแนะนำ ก่อน เขียนไว้ในไฟล์การตั้งค่า (เช่น CLAUDE.md) ว่า “ก่อนการดำเนินการทำลายล้าง ให้สำรองข้อมูลก่อน” นี่คือก้าวแรกที่ถูกต้อง แต่คำแนะนำมีช่องโหว่ ไม่มีประตูตรวจสอบว่าถูกปฏิบัติตามหรือไม่ ในหน้างานที่ยุ่ง คำแนะนำสักวันหนึ่งจะถูกข้ามไปโดยไม่มีใครรู้ตัว
จริงๆ แล้วผมเคยทำลายฐานข้อมูลความจำของตัวเองด้วยวิธีนั้น (เรื่องราวการทำลายเวกเตอร์ DB สำหรับความจำสองครั้ง) ผมตัดสินใจแล้วว่า “จะสำรองข้อมูล” แต่กลับดำเนินการย้ายข้อมูลที่ทำลายล้างไปโดยไม่เคยสร้างข้อมูลสำรองจริงเลย คำแนะนำมีอยู่ แต่การบังคับไม่มี
บทความนี้เป็นเรื่องราวของการยกระดับคำแนะนำนั้นให้เป็น การบังคับ และภาคต่อที่พบว่าประตูที่ยกระดับขึ้นมานั้นเองก็มีช่องโหว่ข้างเปิดอยู่
ข้อจำกัดของชั้นคำแนะนำ
กฎที่เขียนไว้ในไฟล์การตั้งค่าเปรียบเสมือน “คำขอร้อง” การจะปฏิบัติตามหรือไม่หลังจากอ่านแล้ว ขึ้นอยู่กับการตัดสินใจในขณะนั้น ไม่มีกลไกที่หยุดในทันทีที่กำลังจะทำการดำเนินการทำลายล้าง แล้วตรวจสอบว่า “มีข้อมูลสำรองจริงหรือไม่”
คำขอดำเนินการทำลายล้าง (ลบ, ย้าย, เขียนทับ …)
│
├─[แค่คำแนะนำ]─▶ การตั้งค่าบอกว่า "สำรองก่อน" ─▶ ไม่มีการตรวจสอบ ─▶ ถูกข้าม ─▶ เกิดอุบัติเหตุ
│
└─[มีชั้นบังคับ]─▶ หยุดก่อนดำเนินการทันที (สิ่งที่บทความนี้จะสร้าง)
สร้างชั้นบังคับ
ผู้ช่วยตัวนี้มี hook (PreToolUse) ที่แทรกก่อนที่เครื่องมือจะทำงานได้ ผมจึงใส่ประตูไว้ตรงนั้น ตรวจจับรูปแบบคำสั่งทำลายล้างแล้วหยุดการดำเนินการ พร้อม รวบรวมหลักฐานการสำรองข้อมูลด้วยเครื่องจักรและนำเสนอ
หลักฐานคือสิ่งเหล่านี้ เช่น สถานะ Git ของไดเรกทอรีทำงาน (มีการเปลี่ยนแปลงที่ยังไม่ commit หรือไม่ commit ล่าสุดเมื่อไหร่) มีไดเรกทอรีปลายทางสำรองอยู่หรือไม่ วันที่ล่าสุดของมันคือเมื่อไหร่ hook จะตรวจสอบสิ่งเหล่านี้โดยอัตโนมัติแล้วแนบไปกับกล่องยืนยัน
คำขอดำเนินการทำลายล้าง
│
▼
ชั้นบังคับ (hook) หยุดก่อนดำเนินการทันที
│
├─▶ รวบรวมหลักฐานการสำรองข้อมูล
│ ├─ สถานะ Git (ยังไม่ commit / commit ล่าสุด)
│ └─ มีปลายทางสำรองหรือไม่ / วันที่ล่าสุด
│
└─▶ ยืนยันพร้อมหลักฐาน (ask)
├─ มี ─▶ ดำเนินการต่อ
└─ ไม่มี ─▶ หยุด
ตรงนี้มีการตัดสินใจอยู่หนึ่งอย่าง คือจะทำให้ประตูเป็น “ปฏิเสธแบบไม่ต้องถาม (deny)” หรือ “แสดงหลักฐานแล้วให้คนยืนยัน (ask)” ผมเลือก ask การให้เครื่องปฏิเสธทุกอย่างจะหยุดแม้แต่การดำเนินการที่ถูกต้อง ทำให้หน้างานหยุดชะงัก เครื่องมุ่งเน้นแค่การรวบรวมหลักฐาน การตัดสินใจสุดท้ายให้คนดูหลักฐานแล้วตัดสิน ด้วยวิธีนี้ คำแนะนำที่เคยเป็น “สำรองข้อมูลก่อน” จึงกลายเป็นประตูที่มีหลักฐานแนบมาด้วย
ประตูนั้นเองมีช่องโหว่ข้างเปิดอยู่
พอสร้างประตูเสร็จก็อยากจะวางใจ แต่ประตูที่สร้างขึ้นมาจริงๆ ปิดช่องโหว่ได้หรือไม่ เป็นอีกเรื่องหนึ่ง
หลังจากประกอบชั้นบังคับเสร็จ ผมอ่านเอกสารทางการของผู้ช่วยในส่วนที่เกี่ยวกับกลไก — hook, agent, สิทธิ์, เส้นทางการดำเนินการคำสั่ง — ทั้งหมดอย่างละเอียด ประเด็นสำคัญที่อ่านได้ ผมเก็บไว้ในฐานความรู้ของตัวเอง — เวกเตอร์ DB ที่ทำลายไปสองครั้งที่กล่าวถึงตอนต้น (ความจำส่วนตัวที่ค้นหาได้) — ในรูปแบบที่ดึงกลับมาใช้ทีหลังได้ ผมจัดการว่าอ่านไปถึงไหนแล้วด้วยตารางรายการ เพื่อไม่ให้มีส่วนที่อ่านตกหล่น แล้วผม ตรวจสอบการตั้งค่าของตัวเองด้วยความรู้นั้น ผลคือพบช่องโหว่ที่ร้ายแรงที่สุด
hook ของชั้นบังคับตอบสนองแค่กับเครื่องมือเฉพาะอย่างหนึ่ง (shell คือ Bash) เท่านั้น การดำเนินการทำลายล้างแบบเดียวกัน แต่มาจากเส้นทางอื่น — shell อื่น (PowerShell) หรือการแก้ไขไฟล์โดยตรง — hook จะมองว่า “ไม่อยู่ในขอบเขต” แล้วปล่อยผ่านไปเฉยๆ ข้างๆ ประตูที่คิดว่ากำลังป้องกันอยู่ กลับมีช่องโหว่ข้างเปิดอยู่
【ก่อน】ชั้นบังคับมองเห็นแค่ "เครื่องมือเฉพาะอย่างหนึ่ง (Bash)"
คำสั่งอันตราย(Bash) ─▶ hook ตรวจจับได้ ─▶ หยุด ○
เส้นทางอื่น(PowerShell / แก้ไขไฟล์โดยตรง) ─▶ hook ปล่อยผ่าน ─▶ เกิดอุบัติเหตุ × ← ช่องโหว่ข้าง
【หลัง】ปิดทุกเส้นทาง
Bash / PowerShell / แก้ไขไฟล์ ─┐
├─▶ hook ตรวจจับได้ ─▶ หยุด ○
(ไม่ว่าจะมาจากเส้นทางไหน ก็ต้องผ่านประตูนี้) ┘
มาตรการรับมือคือทำให้ hook รองรับทุกเส้นทาง และยังเพิ่มประตูที่บังคับด้วยเครื่องจักรในฝั่งการแก้ไขไฟล์ด้วยว่า “หยุดถ้าจะเขียนทับไฟล์ที่ป้องกันไว้โดยไม่มีการสำรอง” ประตูจะมีความหมายก็ต่อเมื่อวางไว้ที่ ทุกทางเข้า ที่นำไปสู่สิ่งที่ต้องการปกป้องเท่านั้น
เส้นที่คำแนะนำปกป้องไม่ได้
เส้นที่ต้องปกป้อง ให้ปกป้องด้วยการบังคับ ไม่ใช่คำแนะนำ “ระมัดระวัง” ปกป้องได้แค่ในสภาวะปกติเท่านั้น เส้นที่ต้องไม่ถูกข้ามในหน้างานที่ยุ่ง ไม่ได้ถูกปกป้องด้วยความระมัดระวัง แต่ด้วยประตูที่หยุดการดำเนินการ
ทดสอบประตูจากหลายมุมมอง พอสร้างประตูเสร็จแล้ว ให้ตรวจสอบไม่ใช่แค่ “หยุดได้ถูกต้องหรือไม่” แต่ต้องตรวจสอบ “หลบเลี่ยงได้หรือไม่” ด้วย การยืนยันแค่กรณีปกติ (การดำเนินการอันตรายถูกหยุด) เพียงอย่างเดียวจะไม่พบช่องโหว่ข้าง (เส้นทางอื่นปล่อยผ่าน) การที่จะพังหรือไม่ ขึ้นอยู่กับว่าหลบเลี่ยงได้หรือไม่
อ่านกลไกทั้งหมดก่อนที่จะควบคุมมัน ประตูที่สร้างขึ้นจากความเข้าใจบางส่วน จะทิ้งช่องโหว่แบบมองเห็นแค่เครื่องมือประเภทเดียวจากทั้งหมดที่จัดการอยู่ไว้ได้อย่างง่ายดาย ต่อเมื่อเข้าใจจากสเปกว่าอะไรที่อาจกลายเป็นเส้นทางการดำเนินการได้บ้าง ถึงจะนับทางเข้าทั้งหมดได้
คำแนะนำหนึ่งบรรทัดว่า “สำรองข้อมูลก่อน” นั้นใครก็ copy ได้ แต่การสร้างประตูที่หยุดเส้นนั้นไว้จริงๆ ที่ทุกทางเข้าของทุกเส้นทางการดำเนินการ แล้วตรวจสอบว่าไม่มีช่องโหว่ข้างหลงเหลืออยู่ งานนั้น copy ไม่ได้ ต้องสร้างเป็นกลไกด้วยตัวเองและถือครองมันไว้
บทความที่เกี่ยวข้อง
- กรณีจริงที่คำแนะนำเพียงอย่างเดียวปกป้องไม่ได้ และผมทำลายฐานข้อมูลความจำไปสองครั้ง อยู่ใน เรื่องราวการทำลายเวกเตอร์ DB สำหรับความจำสองครั้ง
- ความพยายามในภาพรวมของการเลี้ยงดูผู้ช่วย AI ให้เป็นคู่หู รวบรวมไว้ใน บันทึกการปฏิบัติงานของผู้ช่วย AI (สารบัญซีรีส์)
- นี่คือ hook เดียวกัน แต่บทความนี้เป็นเรื่องการบังคับที่ไม่ให้ทำการดำเนินการอันตราย ส่วนกลไกการปลุกความทรงจำที่ทำให้ AI นึกถึง “สิ่งที่ควรนึกถึงตอนนี้” ทุกเทิร์น เขียนไว้ใน ฉีด “สิ่งที่ควรนึกถึงตอนนี้” เข้าไปใน AI ทุกเทิร์น — ชั้นการเปิดใช้งานของ RAG ส่วนตัว