ทำไม RAG Pipeline ของคุณถึงยังหลุดข้อมูลที่ไม่ควรหลุด: การควบคุมสิทธิ์การเข้าถึงที่ชั้น Retrieval

July 18, 2026

แอปพลิเคชันของคุณมีระบบสิทธิ์ตามบทบาท (role-based permission) ระบบจัดการเอกสารของคุณมี ACL ระดับโฟลเดอร์ แต่ RAG chatbot ของคุณไม่มีทั้งสองอย่าง — มันมี vector index เดียวที่ใช้ร่วมกัน และพร้อมจะส่งข้อมูลเงินเดือนให้ใครก็ตามที่ถามคำถามถูกจังหวะ

นี่ไม่ใช่เรื่องสมมติ นี่คือข้อผิดพลาดด้านสถาปัตยกรรมที่พบบ่อยที่สุดในระบบ RAG ระดับองค์กร และแทบไม่เคยถูกจับได้ตอน demo เพราะ demo ใช้ชุดเอกสารเดียวกับผู้ใช้คนเดียว แต่ระบบจริงมีข้อมูล HR สัญญาทางกฎหมาย เอกสารบอร์ดบริหาร และพนักงาน 200 คน — และทันทีที่พนักงานสองคนมีสิทธิ์ต่างกันบนโฟลเดอร์เดียวกัน RAG pipeline ส่วนใหญ่ก็จะเงียบๆ หยุดบังคับใช้สิทธิ์ใดๆ เลย

ช่องโหว่: UI รู้เรื่องสิทธิ์ แต่ retrieval ไม่รู้

รูปแบบที่พบซ้ำๆ คือ:

  1. บริษัทสร้าง RAG chatbot บนคลังเอกสารภายใน (Google Drive, SharePoint, Confluence หรือระบบ DMS)
  2. หน้าแชทตรวจสอบตัวตนผู้ใช้ถูกต้อง และแสดง รายการ เอกสารตามสิทธิ์ได้อย่างสวยงาม
  3. แต่ vector index เบื้องหลัง chatbot ถูกสร้างขึ้นครั้งเดียวจากทุกอย่าง — HR การเงิน กฎหมาย วิศวกรรม — โดยไม่มีบันทึกระดับ chunk ว่าใครมีสิทธิ์เห็นอะไรบ้าง
  4. ผู้ใช้ถามคำถาม ขั้นตอน retrieval ค้นหาทั่วทั้ง index หาความคล้ายเชิงความหมายที่ใกล้ที่สุด เจอ chunk จากเอกสารที่ผู้ใช้ไม่เคยได้รับสิทธิ์เข้าถึง แล้วส่งให้ LLM เป็น context ทันที
  5. 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 ให้ลูกค้าองค์กรทั่วไทย ญี่ปุ่น และเอเชียตะวันออกเฉียงใต้

บทความล่าสุด

Ready to talk about your project?

Share goals and constraints. We'll assemble architects and engineers to move fast with you.

Get in touch