Skip to content

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" เลยในทางเทคนิค — ต่างกันแค่ iss claim ที่เป็นข้อมูลใน 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 เต็มรูป:

RepoEndpoint ที่หลุดควรเป็น boundary แบบไหน
2way-vtrc-apiPOST /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-apiLINE 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 log
  • vtrc-rc-backoffice รูปแบบเดียวกันผ่าน /pathLogin/:token/:refreshToken (C3)
  • vtrc-backoffice/vtrc-backoffice-new เก็บ token ใน localStorage แบบไม่มี expiry (isRemember=1 hardcode) — boundary ระหว่าง "session ปัจจุบัน" กับ "token ที่อยู่ตลอดไปแม้ปิด browser" หายไป

7. Hardcoded backdoor — boundary ระหว่าง "production" กับ "debug/test" หายไปในซอร์สที่ deploy จริง

RepoBackdoor
2way-apiusername/password hardcode สำหรับ login (SEC-2WAYAPI-01)
2way-vtrc-apistatic master bypass token "testnaja" — ชื่อ token เองก็บอกว่าเป็นของทดสอบ แต่ทำงานได้ใน prod (SEC-2WAYVTRC-01)
cu-central-api/auth/signinbypass (SEC-CU-04)
recruitment-webhardcoded 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