#history#query-gateway#security#lockdown
ภาพรวม
ในยุค ADR-0006 session ของผู้เช่าทุกคนส่ง INSERT/UPDATE/DELETE ถึงตารางธุรกิจใดก็ได้ผ่าน /query/exec จึงข้ามกฎ Go (rule 10) การล็อกทำเป็นขั้น 2026-10-21 .. 2026-10-24 และแต่ละขั้นมีทางลัดที่พบทีหลัง
อาการ
- Q1 (239b0997): หน้าจอหรือคำขอทำมือเขียน
ic_trans,gl_journal,ic_inventory.balance_qtyได้ตรง ไม่ผ่านการคำนวณ/ลงบัญชี/transaction ของ Go - Q2 (239b0997, 3ef39d32): level ต่ำเขียนตารางธุรกิจได้ทุกตาราง และ
erp_user_datalist_configเก็บ SQL ที่ไปรันในเบราว์เซอร์ผู้ใช้คนอื่น - Q3 (04903202): route Next
process/barcode/[action]มีfallbackBarcodeActionยิงDELETE/INSERT/UPDATE ic_inventory_barcodeผ่าน/query/execBatchเองเมื่อ backend ตอบ 404 - Q4 (a47413ec):
/blob/insertรับชื่อตาราง+คอลัมน์จากผู้ใช้โดยไม่ผ่านกฎเดียวกับ/query/exec(ข้อความ commit) - Q5 (239b0997): SQL ที่ตัวตรวจอ่านเป้าหมายไม่ออก:
MERGE, ชื่อU&"...", CTE ที่ซ่อน write, คำสั่งไม่มีเป้าหมายชัด
สาเหตุ (root cause)
- Q1/Q2: นโยบายเดิมคุมแค่ "เป็น DML หรือไม่" ไม่คุม "ตารางไหน ใครเขียน" มีเพียง
ValidateUserLevelกับตารางระบบ 3 กลุ่ม - Q3: ทางสำรองฝั่ง Next เขียนตรงผ่าน gateway เป็นประตูหลังที่ Go ไม่เห็น (ผิด rule 9/10)
- Q4: ประตูเขียนมีสองบาน กฎล็อกอยู่บานเดียว
- Q5: เป้าหมายอ่านด้วย regex (
writeTargetPattern) ไวยากรณ์ที่ regex อ่านไม่ได้ต้อง "ปฏิเสธ" ไม่ใช่ "ปล่อย"
วิธีแก้
- Q1: handler เบราว์เซอร์ตั้ง
ClientRequest=trueแล้ว service เรียกValidateClientPolicy(level); ตารางเงิน/สต็อก/GL ปฏิเสธทุกคน (403ErrMoneyTableViaExec) และย้ายหน้าจอที่เขียนตารางเหล่านี้ไป/api/processในคอมมิตเดียวกัน (stock_beginning, tax_amend, point_doc, doc_no_next) - Q2: level >= 9 เขียนตารางอื่นได้; level < 9 เขียนได้เฉพาะ
legacyClientWritableกับ prefixmonitormap_นอกนั้นErrAdminOnlyWriteหลัก "ratchet": ย้ายหน้าจอไป Go แล้วถอดตารางพร้อมเพิ่มเคสเทสต์ (3ef39d32 ถอด datalist config ไปเซฟผ่าน/process/master/datalistsave) - Q3: ลบ fallback ทิ้ง route เหลือ proxy 49 บรรทัด
- Q4: เพิ่ม
ValidateClientInsert(table, columns, level)ใช้ตารางและ regex คอลัมน์ชุดเดียวกัน พร้อมเทสต์ level 1 ได้ 403 - Q5:
MERGEถูกปฏิเสธ,U&"..."และ "ไม่มีเป้าหมาย" เป็น admin-only,writeTargetsอ่านเป้าหมายใน CTE ด้วย
commit ที่เกี่ยวข้อง
239b0997— write_policy + allow-list + ย้ายการเขียนหน้าจอไป Go (2026-10-21)923fae86— เปลี่ยนเลข ADR เป็น 0011 (0006 ซ้ำ); checkout POS ใช้ลูกค้าเริ่มต้นของ posconfig เพราะCASHไม่ใช่ลูกค้า3ef39d32— ถอดerp_user_datalist_configออกจาก allow-list04903202— ถอด barcode SQL fallback ของ Next (2026-10-22)a47413ec— กฎเดียวกันที่/blob/insert(2026-10-24)
โค้ดปัจจุบันที่ป้องกัน
backend/internal/domain/query/write_policy.go:24moneyTablePattern,:64ValidateClientInsert,:77ValidateClientPolicybackend/internal/domain/query/write_policy_legacy.go:9รายการตารางที่ level ต่ำเขียนได้,:42,:55isLegacyClientWritable;query/query.go:63protectedSystemTablePatternapplication/query/service.go:105,146เรียก policy เมื่อClientRequest;adapter/inbound/http/query/handler.go:245,299ตั้งค่า true;blob/handler.go:98- เทสต์:
write_policy_test.go:8,51,65;backend/test/blob_integration_test.go:151 - ข้อสังเกตจากการอ่านโค้ด (ไม่ใช่ประวัติ):
router.go:85-88สร้างqueryWriteGuardเฉพาะเมื่อมีRightsถ้าไม่มีจะไม่ตรวจสิทธิ์ Add/Edit/Delete (นโยบายตารางยังทำงาน); HANDOFF §8 ระบุว่ายังมี master เขียนผ่าน query/exec
บทเรียน / กฎที่ได้
- ทุกประตูเขียน (exec, execBatch, blob insert, fallback ของ Next) ต้องผ่านฟังก์ชันตรวจเดียว
- อ่านเป้าหมายด้วย regex ไม่ได้ = ปฏิเสธ แล้วเพิ่มเทสต์ทุกรูปแบบที่หลุด
- ข้อมูลที่ผู้ใช้เก็บแล้วไปรันที่อื่น (SQL ใน datalist config) ห้ามอยู่ใน allow-list
- ย้ายหน้าจอไป Go ทุกครั้ง ถอดตารางจาก allow-list และเพิ่มเคสใน
TestValidateClientPolicy - fallback ฝั่ง proxy ที่เขียนข้อมูลเองคือช่องโหว่ ให้ error ตรง ๆ
ดูเพิ่ม
ADR-0011 ล็อก SQL เขียนจาก browser: ตารางเงิน/สต๊อก/GL ผ่าน /api/process เท่านั้น · ADR-0006 /query/exec รับเฉพาะ DML + ตารางระบบต้อง level>=9 (ถูกแทนด้วย ADR-0011) · ADR-0010 /blob/insert เก็บรูปเป็น bytea (ล้าสมัย: โค้ดใช้ MinIO แล้ว) · บทเรียนระบบสิทธิ์เมนู (Permission & Menu Rights) · บทเรียนธุรกรรมอะตอมมิกและ error ที่ถูกกลืน (Atomic Transactions & Swallowed Errors) · เช็กลิสต์ความปลอดภัย API: ตัวตน สิทธิ์ และช่องโหว่เว็บ (Secure API Checklist) · history-overview
ที่มาในโค้ด
- git:239b0997, git:923fae86, git:3ef39d32, git:04903202, git:a47413ec
- backend/docs/adr/0011-query-exec-write-lockdown.md
- backend/internal/domain/query/write_policy.go:24, :64, :77
- backend/internal/adapter/inbound/http/router.go:85-88