แอปพลิเคชันของคุณมีระบบสิทธิ์ตามบทบาท (role-based permission) ระบบจัดการเอกสารของคุณมี ACL ระดับโฟลเดอร์ แต่ RAG chatbot ของคุณไม่มีทั้งสองอย่าง — มันมี vector index เดียวที่ใช้ร่วมกัน และพร้อมจะส่งข้อมูลเงินเดือนให้ใครก็ตามที่ถามคำถามถูกจังหวะ
นี่ไม่ใช่เรื่องสมมติ นี่คือข้อผิดพลาดด้านสถาปัตยกรรมที่พบบ่อยที่สุดในระบบ RAG ระดับองค์กร และแทบไม่เคยถูกจับได้ตอน demo เพราะ demo ใช้ชุดเอกสารเดียวกับผู้ใช้คนเดียว แต่ระบบจริงมีข้อมูล HR สัญญาทางกฎหมาย เอกสารบอร์ดบริหาร และพนักงาน 200 คน — และทันทีที่พนักงานสองคนมีสิทธิ์ต่างกันบนโฟลเดอร์เดียวกัน RAG pipeline ส่วนใหญ่ก็จะเงียบๆ หยุดบังคับใช้สิทธิ์ใดๆ เลย
ช่องโหว่: UI รู้เรื่องสิทธิ์ แต่ retrieval ไม่รู้
รูปแบบที่พบซ้ำๆ คือ:
- บริษัทสร้าง RAG chatbot บนคลังเอกสารภายใน (Google Drive, SharePoint, Confluence หรือระบบ DMS)
- หน้าแชทตรวจสอบตัวตนผู้ใช้ถูกต้อง และแสดง รายการ เอกสารตามสิทธิ์ได้อย่างสวยงาม
- แต่ vector index เบื้องหลัง chatbot ถูกสร้างขึ้นครั้งเดียวจากทุกอย่าง — HR การเงิน กฎหมาย วิศวกรรม — โดยไม่มีบันทึกระดับ chunk ว่าใครมีสิทธิ์เห็นอะไรบ้าง
- ผู้ใช้ถามคำถาม ขั้นตอน retrieval ค้นหาทั่วทั้ง index หาความคล้ายเชิงความหมายที่ใกล้ที่สุด เจอ chunk จากเอกสารที่ผู้ใช้ไม่เคยได้รับสิทธิ์เข้าถึง แล้วส่งให้ LLM เป็น context ทันที
- LLM ทำหน้าที่ของมันถูกต้อง — ตอบคำถามโดยใช้ chunk นั้น รวมถึงรายละเอียดที่ผู้ใช้ไม่ควรเห็น
ไม่มี error ไม่มีการแจ้งเตือนการตรวจสอบ หน้าจอไม่แม้แต่จะแสดงเอกสารต้นทาง จึงไม่มีใครสังเกตว่า retrieval ข้ามขอบเขตสิทธิ์ไปแล้ว — จนกว่าจะกลายเป็นข้อค้นพบในการตรวจสอบความปลอดภัย หรือแย่กว่านั้นคือการสอบถามจากหน่วยงานกำกับดูแล
นี่คือเหตุผลที่ "เรามี SSO และ role-based access บนแอปแล้ว" ไม่ใช่คำกล่าวเดียวกับ "RAG pipeline ของเราบังคับใช้การควบคุมสิทธิ์การเข้าถึงจริง" SSO ควบคุมว่าใคร login ได้ RBAC บนแอปควบคุมว่าเห็นหน้าจอไหนได้ แต่ไม่มีอันไหนแตะขั้นตอน retrieval ที่การค้นหาความคล้ายเชิงความหมายไม่รู้และไม่สนใจว่ากำลังอ่านเอกสารของใคร
ภายใต้ PDPA และมาตรา 59 ของ พ.ร.บ. การรักษาความมั่นคงปลอดภัยไซเบอร์ การรั่วไหลแบบนี้ไม่ใช่แค่ความน่าอายภายใน — มันคือเหตุการณ์ที่อาจต้องแจ้งต่อหน่วยงานกำกับดูแล โดยเฉพาะเมื่อข้อมูลที่รั่วเป็นข้อมูลส่วนบุคคลของพนักงานหรือลูกค้า
ทำไมช่องโหว่นี้ไม่ปรากฏตอนทดสอบ
สามเหตุผลที่ช่องโหว่การควบคุมสิทธิ์ใน RAG รอดจนถึง production:
- ข้อมูลทดสอบแบบ single-tenant — pilot ส่วนใหญ่ทดสอบกับชุดเอกสาร demo ที่ไม่มีระดับความลับต่างกัน จึงไม่มีอะไรให้รั่ว
- ทดสอบด้วยผู้ใช้คนเดียว — developer คนเดียว บัญชี admin คนเดียว ทดสอบเฉพาะ happy path ปัญหาจะปรากฏก็ต่อเมื่อมีผู้ใช้คนที่สองที่มีสิทธิ์แคบกว่าเริ่มถามคำถาม
- LLM ตอบด้วยความมั่นใจไม่ว่าจะถูกหรือผิด — การรั่วไหลของสิทธิ์ไม่มีลักษณะเหมือน error มันดูเหมือนระบบทำงานได้ดี chatbot ตอบได้แม่นยำ นั่นแหละคือปัญหา
สามรูปแบบที่ชั้น retrieval ที่บังคับใช้สิทธิ์ได้จริง
1. การกรองด้วย metadata ตอน query
วิธีแก้ขั้นต่ำสุด: ติด metadata การควบคุมสิทธิ์ที่สืบทอดจากเอกสารต้นทางให้ทุก chunk แล้วใช้เป็นตัวกรองแบบบังคับ ภายใน การค้นหาความคล้ายเชิงความหมาย — ไม่ใช่หลังจากนั้น
ต่อยอดจากโครงสร้าง document_chunks ใน บทความสอน pgvector ของเรา เพิ่มตาราง access เข้าไป:
-- กลุ่ม/บทบาทที่ได้รับอนุญาตให้เห็นเอกสารแต่ละฉบับ
CREATE TABLE document_acl (
document_id BIGINT REFERENCES documents(id) ON DELETE CASCADE,
principal TEXT NOT NULL, -- เช่น 'role:finance', 'user:alice@company.com', 'group:leadership'
PRIMARY KEY (document_id, principal)
);
CREATE INDEX ON document_acl (principal);
def semantic_search_with_acl(
query: str,
user_principals: list[str], # เช่น ["user:alice@company.com", "role:finance", "group:apac"]
top_k: int = 5,
) -> list[dict]:
query_embedding = get_embedding(query)
conn = psycopg2.connect(DB_URL)
cur = conn.cursor()
try:
cur.execute(
"""
SELECT dc.id, dc.content, d.title, d.source_url,
1 - (dc.embedding <=> %s::vector) AS similarity
FROM document_chunks dc
JOIN documents d ON d.id = dc.document_id
WHERE EXISTS (
SELECT 1 FROM document_acl a
WHERE a.document_id = d.id
AND a.principal = ANY(%s)
)
ORDER BY dc.embedding <=> %s::vector
LIMIT %s
""",
[query_embedding, user_principals, query_embedding, top_k],
)
return cur.fetchall()
finally:
cur.close()
conn.close()
ตัวกรองทำงาน ภายใน query ไม่ใช่การตรวจสอบผลลัพธ์หลังจากดึงมาแล้ว ความแตกต่างนี้สำคัญ: การกรองหลัง retrieval หมายความว่าเนื้อหาที่ต้องห้ามถูกดึงเข้าหน่วยความจำของแอปพลิเคชันไปแล้วก่อนตัดสินใจทิ้ง — การรั่วไหลเกิดขึ้นแล้วที่ชั้นเครือข่าย และหาก post-filter มีบั๊ก เนื้อหานั้นก็ยังไปถึง LLM อยู่ดี
2. Row-level security เป็นแนวป้องกันสำรอง
การกรองที่ชั้นแอปพลิเคชันน่าเชื่อถือเท่ากับทุก code path ที่ query ตารางนั้นเท่านั้น สคริปต์ใดๆ เครื่องมือ admin หรือการเชื่อมต่อในอนาคตที่ลืมเงื่อนไข WHERE EXISTS จะเปิดช่องโหว่อีกครั้ง Postgres Row-Level Security (RLS) บังคับใช้กฎเดียวกันที่ชั้นฐานข้อมูล จึงครอบคลุมแม้กระทั่ง query ที่นักพัฒนาแอปไม่ได้คาดคิดไว้:
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;
CREATE POLICY chunk_access_policy ON document_chunks
USING (
EXISTS (
SELECT 1 FROM document_acl a
WHERE a.document_id = document_chunks.document_id
AND a.principal = ANY(current_setting('app.user_principals')::text[])
)
);
# ตั้งค่า principals ของ session ก่อนรัน query ใดๆ บน connection นี้
cur.execute("SET app.user_principals = %s", [user_principals])
นี่คือการป้องกันหลายชั้น ไม่ใช่การแทนที่ตัวกรองระดับแอปพลิเคชัน — มันคือสิ่งที่หยุดบั๊กด้านสิทธิ์ใน endpoint หนึ่งไม่ให้กลายเป็นการรั่วไหลข้อมูลทั้งระบบ
3. จัดการกับการเปลี่ยนแปลงสิทธิ์และ embedding ที่ล้าสมัย
Chunk และ embedding เป็นข้อมูลนิ่งเมื่อถูกเขียนแล้ว แต่สิทธิ์ไม่นิ่ง — พนักงานย้ายออกจากทีมการเงิน สัญญาถูกจัดประเภทใหม่ สิทธิ์ของผู้รับเหมาที่พ้นสภาพควรหายไปทันที มีสองสิ่งที่ต้องทำ:
- ซิงค์ ACL ไม่ใช่ re-embed เนื้อหา — เมื่อสิทธิ์เปลี่ยน ให้อัปเดต
document_aclโดยตรง ไม่จำเป็นต้อง re-embed เอกสาร เพราะตาราง ACL แยกจากข้อมูล vector การเพิกถอนสิทธิ์คือการเขียน metadata ไม่ใช่การรัน pipeline ใหม่ - งาน sync จากแหล่งข้อมูลจริง — หากเอกสารอยู่ใน Google Drive หรือ SharePoint ให้รันงานตามกำหนดเวลาที่ดึงสิทธิ์โฟลเดอร์/ไฟล์ปัจจุบันมากระทบยอดกับ
document_aclเพราะ ACL มักเบี่ยงเบนจากระบบต้นทางเร็วกว่าที่ทีมส่วนใหญ่คาดไว้ โดยเฉพาะเมื่อมีการสืบทอดสิทธิ์แบบซ้อนโฟลเดอร์
ภาพรวมสถาปัตยกรรม
flowchart TD
A["คำถามผู้ใช้ + identity token"] --> B["ระบุ principals ของผู้ใช้\nบทบาท กลุ่ม สิทธิ์ตรง"]
B --> C["Embed คำถาม"]
C --> D["ค้นหาความคล้ายแบบกรอง\nตรวจสอบ ACL ภายใน query"]
E["ตาราง document_acl"] --> D
F["ซิงค์สิทธิ์จากระบบต้นทาง\nDrive, SharePoint, DMS"] --> E
D --> G["เฉพาะ chunk ที่ได้รับอนุญาต"]
G --> H["LLM สร้างคำตอบ"]
H --> I["บันทึกการตรวจสอบ retrieval\nใคร อะไร เมื่อไหร่ principal ที่ตรงกัน"]
การตรวจสอบย้อนหลัง: สิ่งที่ทีม compliance ต้องการจริงๆ
ชั้น retrieval ที่รู้จักสิทธิ์ให้สิ่งที่ชั้นที่ไม่รู้จักสิทธิ์ให้ไม่ได้ — log ที่สามารถอธิบายได้ ทุกเหตุการณ์ retrieval ควรบันทึกตัวตนผู้ query, principal ที่ระบุสำหรับตัวตนนั้น และเอกสารใดบ้างที่มีสิทธิ์ถูกส่งคืน — ไม่ว่าจะเป็น match อันดับต้นหรือไม่ก็ตาม เมื่อการตรวจสอบความปลอดภัยหรือหน่วยงานกำกับดูแลถามว่า "คุณรู้ได้อย่างไรว่าระบบ AI ของคุณไม่ได้เปิดเผยข้อมูลที่จำกัดสิทธิ์" log นี้คือคำตอบ ไม่ใช่คำสัญญา
คำถามที่พบบ่อย
นี่ไม่ใช่สิ่งที่การตรวจสอบสิทธิ์ที่ front-end จัดการอยู่แล้วหรือ?
ไม่ใช่ การตรวจสอบที่ front-end ควบคุมว่าผู้ใช้คลิกอะไรได้ แต่ไม่มีการมองเห็นว่าขั้นตอน retrieval ดึงอะไรเข้า context window ของ LLM ภายใน นี่คือจุดบังคับใช้สองจุดที่แยกกัน และปกติมีแค่จุดเดียวที่ถูกสร้างขึ้นจริง
วิธีนี้ทำให้ retrieval ช้าลงไหม?
ตัวกรอง EXISTS แบบมี index บน document_acl เพิ่ม overhead ระดับหลักหน่วยมิลลิวินาที เมื่อ document_acl.principal มี index ซึ่งน้อยมากเมื่อเทียบกับเวลาสร้าง embedding และการประมวลผลของ LLM
ถ้าสิทธิ์ซับซ้อนกว่า flat role — โฟลเดอร์ซ้อนกัน กลุ่มที่สืบทอด สิทธิ์ชั่วคราว — ทำยังไง?
จำลองแต่ละเส้นทางการสืบทอดเป็นแถวหนึ่งใน document_acl ตอน sync แทนที่จะคำนวณการสืบทอดตอน query แผ่ลำดับชั้นให้แบนตอนงาน sync แล้วให้ query เองเป็นแค่การตรวจสอบสมาชิกภาพแบบง่าย
ถ้าเอกสารของเราไม่ได้ลับขนาดนั้น ยังต้องทำไหม?
ถ้าคลังเอกสารผสมเนื้อหาที่มีระดับความลับใดๆ — HR กฎหมาย การเงิน ข้อมูลส่วนบุคคลลูกค้า — กับเนื้อหาทั่วไป ต้องทำ รูปแบบความล้มเหลวไม่ใช่แค่ "น่าอายเป็นครั้งคราว" แต่เป็นการเปิดเผยข้อมูลที่อธิบายยากภายหลัง เพราะไม่มีอะไรในระบบตั้งค่าสถานะว่าผิดปกติ
กำลังสร้างหรือตรวจสอบ RAG pipeline ที่ต้องบังคับใช้การควบคุมสิทธิ์การเข้าถึงจริง — ไม่ใช่แค่ UI ที่มีสิทธิ์อยู่หน้า index ที่ไม่มีสิทธิ์อะไรเลย? ติดต่อเราที่ hello@simplico.net — เราออกแบบและติดตั้งสถาปัตยกรรม RAG ระดับ production ให้ลูกค้าองค์กรทั่วไทย ญี่ปุ่น และเอเชียตะวันออกเฉียงใต้
บทความล่าสุด
- ทำไมโรงงานถึงกลัวโครงการ ERP ล้มเหลว — และตัวช่วยที่แก้ปัญหานี้ได้ July 15, 2026
- ทำไมผู้ผลิตเซมิคอนดักเตอร์และอิเล็กทรอนิกส์ในเอเชียตะวันออกเฉียงใต้กำลังโตเกินกว่าที่ MES แบบเดิมจะรองรับได้ July 15, 2026
- ปกป้อง Agentic SOC: Prompt Injection, การปนเปื้อน Log และภัยคุกคามภายในรูปแบบใหม่ July 15, 2026
- จากดีโปทุเรียนสู่ดีโปรีไซเคิล: simpliDepot จัดการธุรกิจศูนย์รับซื้อวัสดุรีไซเคิลได้อย่างไร July 7, 2026
- ตี 3.47 น.: เบื้องหลังเหตุการณ์จริงที่ถูกจับได้โดย SOC Stack แบบโอเพนซอร์ส July 2, 2026
- แอปชาร์จ EV ที่คุณไม่ต้องสร้างเอง: ระบบ QR Code กับ OCPP ID Tag July 2, 2026
