2way-vtrc-api — Scorecard
Legend:
- Critical = active security/data-loss/availability risk
- High = correctness/maintainability hazard, real impact
- Medium = friction/hygiene, ยังไม่เป็นอันตรายระดับ production-breaking ทันที
2way-vtrc-api (branch: prod) เป็น repo ที่มีความเสี่ยงสูงในกลุ่ม 2way Platform เพราะเป็น backend หลักของฝั่งพนักงานจริง (ยืนยันจาก 2way-meeting-backoffice) และมี auth bypass ที่ชัดเจน
Score summary
| Severity | จำนวน (ในตารางด้านล่าง) |
|---|---|
| Critical | 5 |
| High | 7 |
| Medium | 6 |
Security
| ID | Severity | คำอธิบาย | หลักฐาน (file:line) | การแก้ไข |
|---|---|---|---|---|
| SEC-2WAYVTRC-01 | Critical | Master bypass token แบบ static string "testnaja" ข้ามการยืนยันตัวตนของ checkBypassTokenMiddleWare — ใครที่อ่านซอร์สเรียก endpoint ที่ใช้ middleware นี้ได้ทันที (เช่น addNewIncident, list/update ส่วนใหญ่) | middleware/checkbypasstoken.ts:24 | ลบเงื่อนไข token == "testnaja"; ตรวจ access log ย้อนหลัง; rotate token ที่อาจถูกใช้คู่กับ bypass |
| SEC-2WAYVTRC-01b | Critical | Master bypass "testnaja" ซ้ำใน checkBypassTokenLineMiddleWare — ข้าม LINE token verify ทั้งชุดของ LIFF endpoints | middleware/checkByPassTokenLine.ts:30 | ลบเงื่อนไขเดียวกันฝั่ง LINE middleware |
| SEC-2WAYVTRC-02 | Critical | POST /2way/getProfile ไม่มี middleware (auth ถูก comment ปิด) รับ lineID จาก body แล้วคืน identityCard, birthDate, และเติม fullName/tel จาก cu-central — PII ไม่มี session binding | routes/TwoWayAPI/userLine/getProfile.ts:34-63, 79-133 | เปิด checkBypassTokenLineMiddleWare กลับ; ดึง lineID จาก verified LINE profile เท่านั้น ไม่รับจาก body |
| SEC-2WAYVTRC-02b | Critical | POST /2way/getProfile/cu พฤติกรรมเดียวกัน (auth comment ปิด) | routes/TwoWayAPI/userLineCu/getProfile.ts:35-63 | แก้แบบเดียวกับ SEC-2WAYVTRC-02 |
| SEC-2WAYVTRC-03 | Critical | Credential MongoDB production (username/password/host) commit ในซอร์ส — แม้ connection ถูก comment ปิดใน entrypoint credential ก็หลุดแล้ว | util/mongo.config.ts:2-8 | rotate credential; ย้ายไป secret manager/env; ลบจาก git history ถ้า policy บังคับ |
| SEC-2WAYVTRC-03b | Critical | Credential MSSQL (sa + password + host 10.188.128.61) hardcode ใน util/env.ts บล็อก Prod ที่ active — ไม่ได้อ่านจาก .env | util/env.ts:17-32 | ย้ายไป env/secret; rotate password ของ sa; หลีกเลี่ยงใช้ sa เป็น app user |
| SEC-2WAYVTRC-04 | High | JWT SECRET ใน util/jwt.config.ts เป็น string literal ไม่มี env override — คนละตัวจาก SECRET_JWT_TWOWAY | util/jwt.config.ts:2, เทียบ checkbypasstoken.ts:65 | รวม secret เหลือชุดที่ชัดเจน อ่านจาก env ทั้งหมด แล้ว rotate |
| SEC-2WAYVTRC-05 | High | Sequelize encrypt: false — เชื่อม MSSQL แบบไม่เข้ารหัส TLS | util/db.config.ts:8-11 | เปิด encrypt: true พร้อม certificate ที่ถูกต้อง |
| SEC-2WAYVTRC-06 | Medium | CORS ตั้ง Access-Control-Allow-Origin: * พร้อม Access-Control-Allow-Credentials: true พร้อมกัน — ขัดกับพฤติกรรมเบราว์เซอร์มาตรฐาน และสื่อเจตนาเปิดกว้างเกินจำเป็น | app.ts:87-99 | whitelist origin ที่ต้องการ; อย่าใช้ * กับ credentials |
| SEC-2WAYVTRC-07 | Critical | POST /2way/updateIncident มีแค่ validatorUpdateIncident — ไม่มี bypass/LINE middleware; อัปเดตสถานะ incident ได้โดยไม่ต้องมี token | routes/TwoWayAPI/incident/updateIncident.ts:23 | ใส่ checkBypassTokenMiddleWare (หรือ role ที่เหมาะสม) ก่อน handler |
| SEC-2WAYVTRC-08 | Critical | SSO integration credentials + AES key/IV hardcode ใน loginSSO.ts รวมถึง SSO_PASSWORD และ endpoint IP ภายใน | routes/TwoWayAPI/sso/loginSSO.ts:21-27 | ย้ายไป env/secret; rotate SSO app password และ AES key; ตรวจว่า key เก่าถูกใช้ที่อื่นหรือไม่ |
| SEC-2WAYVTRC-09 | High | passport-jwt strategy เรียก done(null, true) เสมอ — ไม่ bind user จาก payload; การมี JWT ที่ sign ด้วย SECRET เท่ากับผ่าน | middleware/jwtpassport.ts:9-12 | validate payload (user id / claims) จริง; reject ถ้าไม่พบ user |
| SEC-2WAYVTRC-10 | Medium | Middleware log ทั้ง req.headers และ token ลง console | checkbypasstoken.ts:16-17, checkByPassTokenLine.ts:19-20 | ลบหรือ redact; ใช้ structured logger ระดับที่เหมาะสม |
Correctness / data integrity
| ID | Severity | คำอธิบาย | หลักฐาน | การแก้ไข |
|---|---|---|---|---|
| CORR-2WAYVTRC-01 | High | createIncident insert หลายตาราง (Remark → IncidentRunning → Incident → Attachment) โดยไม่มี sequelize.transaction() — fail กลางทางเหลือ orphan | createIncident.ts:38-151 (ไม่พบ transaction) | ครอบด้วย transaction + rollback |
| CORR-2WAYVTRC-01b | High | Attachment loop ใช้ .map(async ...) โดยไม่ await รวม — response Success อาจกลับก่อนไฟล์ถูก insert | createIncident.ts:104-127 | ใช้ await Promise.all(...) หรือ for-loop await |
| CORR-2WAYVTRC-02 | High | Mongo connection ถูก comment ปิดใน app.ts แต่ socket chat ยังเขียนผ่าน Mongoose schema | app.ts:10 เทียบ lib/Socket.func.ts + app.ts:277-282 | ยืนยันว่า chat ยังใช้จริง; เปิด connection หรือถอด socket/chat code |
| CORR-2WAYVTRC-03 | High | jwtExpiration: 60 / jwtRefreshExpiration: 120 ถูก comment ว่า for test แต่ยังเป็นค่าที่ export จริง — token อายุสั้นผิดปกติสำหรับ production login flows ที่ใช้ jwt.config | util/jwt.config.ts:6-8 | ตั้งค่า production ที่สมเหตุสมผลผ่าน env |
| CORR-2WAYVTRC-04 | Medium | setRunningNo นับจาก Incident.count แล้ววนเช็คซ้ำ — concurrent create อาจชนกันก่อน unique constraint (ถ้ามี) | createIncident.ts:243-269 | ใช้ sequence/transaction isolation หรือ DB constraint + retry |
| CORR-2WAYVTRC-05 | Medium | Business error หลายจุดส่ง HTTP 200 พร้อม status.code ไม่ใช่ 4xx/5xx — client ที่เช็คแค่ HTTP status จะเข้าใจผิดว่าสำเร็จ | createIncident.ts:30-34, loginSSO.ts:37-42 | แยก HTTP status ให้ตรงกับผล; คง envelope เดิมถ้าต้อง backward compatible |
Quality / legacy / ops
| ID | Severity | คำอธิบาย | หลักฐาน | การแก้ไข |
|---|---|---|---|---|
| QUAL-2WAYVTRC-01 | Medium | Mock routers สไตล์ JSONPlaceholder ยัง mount ที่ /2way/TwoWayAPI และ /2way/MeetingAPI — ตอบข้อมูลปลอม/Success โดยไม่แตะ DB | app.ts:110-113, routes/TwoWayAPI/index.ts:196-259, routes/MeetingAPI/user.ts | ลบ mount และไฟล์ mock ถ้ายืนยันว่าไม่มี consumer |
| QUAL-2WAYVTRC-02 | Medium | ชื่อ package "express" version 1.0.0 ไม่ตรง repo | package.json:2-3 | เปลี่ยน name/version ให้ตรง |
| QUAL-2WAYVTRC-03 | Medium | Swagger title/description ยังเป็น JSONPlaceholder tutorial; server URL localhost:8001 | app.ts:20-42 | เขียน metadata จริงของ 2way-vtrc-api |
| QUAL-2WAYVTRC-04 | Medium | Dockerfile EXPOSE 3000 แต่แอป listen 8001 และ compose map 8001:8001 | Dockerfile:13, app.ts:8, docker-compose.yml:11-12 | แก้ EXPOSE ให้ตรง 8001 |
| LEG-2WAYVTRC-01 | Medium | รัน ts-node app.ts ใน production — ไม่มี compile step; ช้าและพลาด type error ตอน build | package.json:7 | เพิ่ม tsc แล้วรัน node dist/app.js |
| LEG-2WAYVTRC-02 | Medium | Dependencies ค้าง: mysql/mysql2 ทั้งที่ dialect เป็น mssql; request (deprecated); tslint คู่กับ eslint | package.json:40-41,47,70 | ลบของที่ไม่ใช้; แทนที่ request |
| OBS-2WAYVTRC-01 | Medium | Sentry tracesSampleRate: 1.0 และ DSN hardcode — cost/noise สูง; สลับ env ยาก | app.ts:53-67 | อ่าน DSN จาก env; ลด sample rate ใน production |
| OBS-2WAYVTRC-02 | Medium | Sentry errorHandler ลงทะเบียนก่อน route mounts — ต่างจากลำดับที่ Sentry แนะนำ | app.ts:74-77 vs mounts ที่ 101+ | ย้าย errorHandler ไปหลัง routes; คง captureException ใน catch ที่จำเป็น |
Auth surface matrix (สรุปเพื่อจัดลำดับแก้)
| กลุ่ม endpoint | Auth จริง | ความเสี่ยงหลัก |
|---|---|---|
| ส่วนใหญ่ของ incident/feedback/survey/faqs/announcement (admin) | checkBypassTokenMiddleWare | ถูกบายพาสด้วย "testnaja" |
Line variants (*Line, *ByUserID, accept/read) | checkBypassTokenLineMiddleWare | ถูกบายพาสด้วย "testnaja" |
getProfile, getProfile/cu | ไม่มี | PII leak |
updateIncident | ไม่มี (มีแค่ validator) | แก้สถานะได้โดยไม่ต้อง login |
SSO (loginSSO, verify OTP non-emp, …) | ไม่มีบน route (login-style) | credential SSO hardcode ในซอร์ส |
Mock /2way/TwoWayAPI, /2way/MeetingAPI | ไม่มี / ไม่สำคัญ | ข้อมูลปลอมหลอก consumer |
| WebHook LINE | LINE platform → webhook | ตรวจ signature แยก (ไล่เพิ่มถ้า audit webhook เต็ม) |
| Chat HTTP | mount ปิด | — |
| Chat socket | ไม่มี auth ใน Socket.func ที่อ่านได้ | ใคร connect socket ได้ส่ง sendMessage |
ลำดับ remediation ที่แนะนำ (จากความรุนแรง + ผลกระทบผู้ใช้)
- ลบ
"testnaja"ทั้งสอง middleware (SEC-01 / 01b) และใส่ auth ให้updateIncident(SEC-07) - ปิด/แก้
getProfile+getProfile/cu(SEC-02 / 02b) - Rotate และย้าย MSSQL / Mongo / SSO / JWT secrets ออกจากซอร์ส (SEC-03 / 03b / 08 / 04)
- ใส่ transaction ให้
createIncident(CORR-01) - ตัดสินใจเรื่อง chat: เปิด Mongo หรือถอด socket (CORR-02)
- ถอด mock routers (QUAL-01)
- จัด Dockerfile port + ts-node → compiled JS (QUAL-04 / LEG-01)
สิ่งที่ตรวจแล้ว vs ยังไม่ตรวจ
ตรวจแล้วจากซอร์สบน branch prod:
- entrypoint mounts ใน
app.ts - middleware bypass ทั้งสองไฟล์
createIncident,getProfile,getProfile/cu,updateIncident,loginSSOdb.config.ts/env.ts/mongo.config.ts/jwt.config.ts- socket chat + comment mongo require
.env.prodkeys (ไม่ยืนยันว่า runtime โหลดไฟล์นี้โดยตรง — boot อ่าน.env)
ยังไม่ตรวจในเอกสารชุดนี้:
- ทุกบรรทัดใน
routes/TwoWayAPI/index.ts(~1849 บรรทัด) ทีละ mock handler - webhook signature validation แบบครบทุก event type
- ว่า frontend ตัวใดยังเรียก
/2way/TwoWayAPI/*จริง - penetration test บน network ของ IP ใน
env.ts
สรุปเชิงภาพรวม
2way-vtrc-api คือจุดที่มี debt ด้าน auth และ secret management หนาแน่นในกลุ่ม 2way Platform และเป็น backend ที่ฝั่งพนักงานพึ่งพาจริงมากกว่า 2way-api — ลำดับแก้ที่เร่งด่วนที่สุดคือ master bypass token, unauthenticated profile/PII, unauthenticated updateIncident, และ credential ที่ commit ในซอร์ส