12.2 · Shared trust boundaries
Volume 12.1 บันทึกว่า "auth mechanism มีกี่แบบ" — บทนี้ตอบคำถามที่อันตรายกว่า: ในจุดที่ควรมี trust boundary (เช่น "service A ไม่ควรเชื่อ input จาก service B โดยไม่ตรวจสอบ") มีกี่จุดที่ boundary นั้นเลือนจนไม่มีจริง
1. Shared secret ทำให้ "authentication" กลายเป็น "possession of a copied string"
รายละเอียด cluster ทั้งหมดอยู่ที่ Volume 11.4 สรุปที่เกี่ยวกับ trust boundary โดยตรง:
- Cluster 1 (
vtrc-api+benefit-api+recruitment-api+ai-recruitment-api+pms-api) — 5 service เชื่อ JWT ที่เซ็นด้วย secret เดียวกัน แปลว่าไม่มี boundary ระหว่าง "token ที่ vtrc-api ออกให้ผู้ใช้ทั่วไป" กับ "token ที่ recruitment-api ออกให้ candidate" เลยในทางเทคนิค — ต่างกันแค่issclaim ที่เป็นข้อมูลใน payload ที่ signer เดียวกันเป็นคนเขียน ไม่ใช่กลไก crypto ที่แยกจริง ถ้าใครปลอมissให้เป็นredcross.or.th:vtrc(มี secret แล้วสร้าง JWT เองได้เลย) ทั้ง 5 service เชื่อว่าเป็น vtrc-api ทันที - Cluster 2/3 (
chat-center-api/job-scheduler-api/vtrc-common/workflow-api) — DB password ระดับsaเดียวกัน แปลว่าไม่มี boundary ระหว่าง "สิทธิ์ของchat-center-api" กับ "สิทธิ์ของworkflow-api" ที่ database engine เห็น — ทั้งคู่คือsaเหมือนกัน
2. apiKey เป็น device-type credential ไม่ใช่ user/session credential
vtrc-api ตรวจ header apiKey เทียบกับ API_DEVICE_KEY[].key ที่ map ไปหา APP_TYPE (WEB/MOBILE/WEB_ADMIN) — ค่านี้เป็น UUID คงที่ที่ compile ไว้ในทุก build ของ frontend/mobile ประเภทเดียวกัน ไม่ใช่ต่อ user ไม่ใช่ต่อ session
ผลกระทบต่อ trust boundary — ระบบไม่สามารถแยกแยะ "request จาก vtrc-web build จริง" กับ "request จากใครก็ตามที่ copy ค่า apiKey ออกจาก JS bundle" ได้เลย เพราะ apiKey เป็น public-ish value ที่ฝังใน client-side code ที่ทุกคน download ได้ — boundary ที่ดูเหมือนมี (ต้องมี apiKey ที่ถูกต้อง) จึงเป็น security theater มากกว่า boundary จริง (ดู Volume 3 vtrc-api และ canonical-truths.md สำหรับ field name ที่ถูกต้อง key ไม่ใช่ apiKey)
3. JWT payload ถูกเชื่อโดยไม่ verify signature (workflow-api)
จุดที่ชัดที่สุดในทั้งแพลตฟอร์ม — workflow-api อ่าน role จาก JWT payload โดยไม่มี global guard ที่ verify signature ก่อน (SEC-WORKFLOW-01) ต่างจากทุก NestJS service อื่นที่อย่างน้อยมี JwtAuthGuard เป็น default ทำให้ "trust boundary" ของ workflow-api ไม่ได้ขึ้นกับ cryptographic signature เลย แต่ขึ้นกับ ความหวังว่า client จะส่ง payload ที่ถูกต้อง — เทียบเท่ากับไม่มี authentication
boundary นี้อันตรายเป็นพิเศษเพราะ workflow-api มี DB connection ตรงไปยัง 4 database ของ owner อื่น (Volume 11.2) — การ impersonate role สูงที่ workflow-api เท่ากับได้สิทธิ์เขียนข้าม 4 ระบบพร้อมกัน
4. @Public() ที่ควรเป็น private กลายเป็นจุด blur boundary ระหว่าง "public-facing" กับ "internal-only"
ทีมพัฒนาแต่ละ repo ตัดสินใจเองว่า endpoint ไหนควร @Public() โดยไม่มี guideline กลาง ผลคือมี endpoint ที่ควรเป็น internal-only (เข้าถึงเฉพาะจาก service อื่นที่ trust กัน) กลับเปิดเป็น public เต็มรูป:
| Repo | Endpoint ที่หลุด | ควรเป็น boundary แบบไหน |
|---|---|---|
2way-vtrc-api | POST /2way/getProfile (SEC-2WAYVTRC-02) | ควรเป็น service-to-service เท่านั้น (เรียกจาก 2way-meeting-backoffice) ไม่ใช่ public internet |
pms-api | /budget/getEmployeeSalary (SEC-PMS-01) | ควรเป็น authenticated employee-only |
meeting-vtrc-api | /meeting/mockuser/one (SEC-MTVTRC-02) | ชื่อ endpoint บอกว่าเป็น mock/test แต่ทำงานจริงใน prod พร้อม PII จริง |
2way-line-service | ทุก endpoint ของ LineController (SEC-2WAYLINE-01) | ควรเป็น internal-only เพราะเป็น service ที่ push/multicast message ผ่าน LINE โดยตรง |
chat-center-api | LINE webhook inbound ไม่มี signature check | ควร verify LINE signature header ก่อนเชื่อ payload |
5. temp_pee_na_table — identity bridge ที่ไม่มี trust contract
ยกมาจาก Volume 11.3 เพราะเป็นทั้ง data-model และ trust-boundary issue พร้อมกัน — chat-center-api และ sso-api (สองกลุ่มคนละทีม) query ตารางเดียวกันใน dbHRMI_Center ที่ชื่อขึ้นต้นด้วย temp_ โดยไม่มี API contract, versioning, หรือแม้แต่เอกสารว่าใครเป็นเจ้าของตารางนี้จริง — boundary ระหว่าง "scratch/temp data" กับ "production identity source" หายไปโดยไม่มีใครตั้งใจ
6. Frontend token exposure = boundary ระหว่าง "authenticated session" กับ "browser history/log" เลือนไป
vtrc-backoffice-newส่ง token ผ่าน URL path ข้าม origin (SEC-BONEW-01) — token ที่ควรอยู่ใน header/cookie ที่ browser ป้องกันไม่ให้ third-party เห็น กลับไปอยู่ใน browser history, referrer header ที่อาจหลุดไปหา third-party resource, และ server access logvtrc-rc-backofficeรูปแบบเดียวกันผ่าน/pathLogin/:token/:refreshToken(C3)vtrc-backoffice/vtrc-backoffice-newเก็บ token ในlocalStorageแบบไม่มี expiry (isRemember=1hardcode) — boundary ระหว่าง "session ปัจจุบัน" กับ "token ที่อยู่ตลอดไปแม้ปิด browser" หายไป
7. Hardcoded backdoor — boundary ระหว่าง "production" กับ "debug/test" หายไปในซอร์สที่ deploy จริง
| Repo | Backdoor |
|---|---|
2way-api | username/password hardcode สำหรับ login (SEC-2WAYAPI-01) |
2way-vtrc-api | static master bypass token "testnaja" — ชื่อ token เองก็บอกว่าเป็นของทดสอบ แต่ทำงานได้ใน prod (SEC-2WAYVTRC-01) |
cu-central-api | /auth/signinbypass (SEC-CU-04) |
recruitment-web | hardcoded OTP bypass (SEC-RCWEB-03) |
ทั้ง 4 จุดมี pattern เดียวกัน — โค้ดที่เขียนไว้เพื่อ debug/test ในการพัฒนา แต่ไม่มี feature flag/environment check ป้องกันไม่ให้ทำงานใน production build
ภาพรวม — boundary ที่ "ดูเหมือนมี" vs "มีจริง"
| Boundary ที่ควรมี | สถานะจริง |
|---|---|
| Public internet ↔ internal service | เลือน — มี endpoint internal-only ที่หลุดเป็น public ≥ 6 จุด |
| Service A's data ↔ Service B's data | เลือน — 5+ service เชื่อ JWT secret เดียวกัน, 4 service เชื่อ DB password เดียวกัน |
| Authenticated user ↔ anonymous | เลือน — apiKey เป็น build-time constant ไม่ใช่ per-user, backdoor ≥ 4 จุด bypass ได้ทั้งหมด |
| Production ↔ debug/test code path | เลือน — backdoor/bypass token ที่ตั้งใจไว้เพื่อ debug ทำงานได้จริงใน prod build |
| Signed data ↔ unsigned data | เลือนที่ workflow-api — อ่าน payload โดยไม่ verify signature |
ข้อเสนอ remediation ระดับระบบ (ไม่ใช่รายจุด เพราะรายจุดมีอยู่แล้วใน Volume 3-9) ดู 12.4 Scorecard
รายการ Critical finding ทั้งหมดที่เป็นหลักฐานของบทนี้ ดู 12.3 Critical findings summary →