04 · Scorecard — vtrc-common
บทสรุป debt จาก 01–03 — severity catalog แบบเจาะหลักฐาน + การแก้ไข
อ้างอิง branch: prod
Severity legend
- 🔴 Critical = active security/data-loss/availability risk
- 🟡 High = correctness/maintainability hazard, real impact
- 🟢 Medium = friction/hygiene, not yet dangerous
| Level | เกณฑ์ | Action |
|---|---|---|
| 🔴 Critical | active security/data-loss/availability risk | ต้องแก้ก่อน deploy ถัดไป |
| 🟡 High | correctness/maintainability hazard | วางแผนแก้ใน quarter นี้ |
| 🟢 Medium | friction/hygiene | แก้ตามโอกาส |
Summary table
| Debt ID | Severity | สั้น ๆ | หลักฐาน |
|---|---|---|---|
| SEC-COMMON-01 | 🔴 Critical | @Public() บน write endpoints + JWT secret หลุด → ระบบไฟล์/master data ไม่มี auth จริง | file.controller.ts:18,24, address.controller.ts:14-48, bank.controller.ts:13,19, .env:18 |
| SEC-COMMON-02 | 🟡 High | SQL injection ใน address raw SQL (provCode/amphCode) | address.repository.ts:82-94,117-130 |
| SEC-COMMON-03 | 🔴 Critical | .env commit + bake เข้า Docker image — MSSQL sa password + SECRET_KEY | .env:11,18, Dockerfile:19 |
| SEC-COMMON-04 | 🟡 High | CORS origin: '*' + public address/bank ขยาย attack surface จาก browser | main.ts:29-33 |
| QUAL-COMMON-01 | 🟢 Medium | path ชื่อ /testTumbol* แต่ใช้ใน production comment/flow | address.controller.ts:32-41 |
| QUAL-COMMON-02 | 🟢 Medium | libs/ copy-paste ข้าม repo ไม่ได้เป็น shared package จริง | เทียบ libs/jwt กับ chat-center-api |
| QUAL-COMMON-03 | 🟢 Medium | Address service swallow errors → client ได้ empty แทน HTTP error | address.service.ts:30-32 |
| QUAL-COMMON-04 | 🟢 Medium | Soft-delete ไม่ลบ disk → orphan files; compose prod ไม่ mount uploads | file.service.ts:96-106, docker-compose.yml:17-28 |
| OBS-COMMON-01 | 🟢 Medium | เอกสาร workspace เดิมเรียกผิดว่า npm library | ดู 02 §6–7 |
| OBS-COMMON-02 | 🟢 Medium | CI comment yarn test; TypeORM log ทุก query | bitbucket-pipelines.yml:17, app.module.ts:29 |
Severity: Critical (🔴)
SEC-COMMON-01 · Public write endpoints + weak JWT boundary
ที่:
vtrc-common/src/modules/file/file.controller.ts (branch: prod):18-28—@Public()บนsave/deletevtrc-common/src/modules/address/address.controller.ts (branch: prod):14-48— ทุก address routevtrc-common/src/modules/bank/bank.controller.ts (branch: prod):13-23— ทั้งสอง bank routevtrc-common/libs/jwt/jwt-auth.guard.ts (branch: prod):15-24— bypass เมื่อisPublicvtrc-common/.env (branch: prod):18—SECRET_KEYสำหรับ route ที่ยัง guard อยู่
Evidence:
@Public()
@Post('save')
saveFile(@Body() body: MultipleSaveFileDto) {
return this.service.saveFile(body);
}
@Public()
@Post('delete')
delete(@Body() body: PkFileDto) {
return this.service.delete(body);
}GET /file/:id และ POST /file/getFileArray ไม่มี @Public() แต่ JwtStrategy ใช้ secret จาก env เดียวกันที่ commit ใน git → ผู้ถือ secret forge JWT ได้โดยไม่ login
ผลกระทบ:
- อัปโหลดไฟล์ไม่จำกัดตัวตน (disk fill, malware storage)
- soft-delete ไฟล์ของผู้อื่นโดยเดา/รู้ id
- อ่าน master address/bank ได้เสมอ (อาจยอมรับได้ทางธุรกิจ แต่รวมกับ SQLi แล้วอันตราย)
การแก้ไข:
- ถอด
@Public()จากfile/saveและfile/delete— ให้benefit-apiส่ง service-to-service JWT หรือ mTLS / network policy - ถ้า address/bank ต้อง public จริงสำหรับ dropdown — คง
@Public()ได้ แต่ต้องแก้ SEC-COMMON-02 ก่อน - หมุน
SECRET_KEYและเลิกแชร์ข้าม repo - เพิ่ม authorization ระดับ record (เช่น ตรวจว่า file id เป็นของ module/caller ที่อนุญาต)
SEC-COMMON-03 · Credentials committed และ bake เข้า image
ที่:
vtrc-common/.env (branch: prod)ทั้งไฟล์ —DB_PASSWORD,SECRET_KEY, host ภายในvtrc-common/Dockerfile (branch: prod):19—COPY --from=develop /usr/src/app/.env .vtrc-common/docker-compose.yml (branch: prod):35-38— MariaDB passwords เพิ่มใน compose
Evidence: .env ถูก track ใน git (branch prod) และถูก copy เข้า production image ตอน build → ใคร pull image หรือ clone repo ได้ credential
ค่า DB_USER=sa + password ที่ใช้ร่วมกับ repo อื่นในกลุ่ม Domain (ดู docs/_audit-method/canonical-truths.md) → credential หลุดจาก service เดียวกระทบหลาย DB
ผลกระทบ:
- เข้า MSSQL
vtrc_commonได้ถ้า network เปิดถึง10.188.128.61 - Forge JWT เรียก route ที่ยัง guard อยู่
- Image registry leak = full secret leak
การแก้ไข:
- Rotate MSSQL password + JWT secret ทั้งหมดที่เกี่ยวข้อง
- ลบ
.envออกจาก git tracking + purge history - หยุด
COPY .envใน Dockerfile — inject ผ่าน runtime secret / env ของ orchestrator - แยก DB account ต่อ service แทน
sa
Severity: High (🟡)
SEC-COMMON-02 · SQL injection ใน AddressRepository
ที่:
vtrc-common/src/modules/address/address.repository.ts (branch: prod):81-94—findNativeTumbolByProvinceCodeAndAumphurCodevtrc-common/src/modules/address/address.repository.ts (branch: prod):117-130—findSubDistirctByProvCodeAndDistrictCode- Entry:
address.controller.ts:32-36(/testTumbol),:44-48(/listSubDistrict...) - Input DTO:
tumbol.req.dto.ts:4-12—@IsString()เท่านั้น
Evidence:
WHERE p.province_code = '${provCode}'
AND a.aumphur_code = '${amphCode}'ทั้งสองเมธอดเป็น @Public() → unauthenticated SQLi บน DB vtrc_common ด้วยสิทธิ์บัญชีจาก .env (sa)
เปรียบเทียบจุดที่ถูกต้องในไฟล์เดียวกัน: findQueryBuilderTumbolByProvinceCodeAndAumphurCode ใช้ .setParameter('provCode', provCode) (:109-113)
การแก้ไข:
- แทนที่ทั้งสองเมธอดด้วย QueryBuilder parameterized หรือ
query(sql, params) - เปลี่ยน route
/testTumbolให้ชี้ไป V3 ที่ปลอดภัย (หรือลบ native path) - เพิ่ม regression test ที่ส่ง quote/semicolon ใน
provCodeแล้วคาดว่าไม่เกิด syntax error จาก injection - พิจารณา WAF / parameterized-only lint rule ใน CI
SEC-COMMON-04 · CORS wildcard บน public APIs
ที่: vtrc-common/src/main.ts (branch: prod):29-33
app.enableCors({
origin: '*',
allowedHeaders: '*',
methods: 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
});รวมกับ @Public() address/bank และ SQLi → หน้าเว็บใดก็ทำ cross-origin request ไปยัง endpoint ที่ inject ได้โดยไม่ต้องมี browser CSRF token ของ VTRC
การแก้ไข: จำกัด origin เป็น allowlist ของ vtrc.redcross.or.th, vtrcbackoffice.redcross.or.th, UAT hosts, และ origin ของ mobile WebView ถ้ามี — หรือบังคับให้ frontend เรียกผ่าน BFF/vtrc-api แทนการเรียกตรง
Severity: Medium (🟢)
QUAL-COMMON-01 · ชื่อ endpoint /testTumbol*
ที่: address.controller.ts:32-41
ชื่อสื่อว่าเป็นของทดลอง แต่ testTumbol เรียก native SQL path ที่ frontend/mobile อาจยังอ้าง (และเป็นจุด SQLi) — เสี่ยงถูกลบโดยเข้าใจผิด หรือถูกมองข้ามตอน security review
การแก้ไข: rename เป็น /listTumbolNative หรือ deprecate แล้วให้ทุก client ใช้ /listTumbol / QueryBuilder path
QUAL-COMMON-02 · libs copy-paste ไม่ใช่ shared library
ที่: vtrc-common/libs/jwt/*, libs/log/*, libs/exception/* เทียบกับ chat-center-api / job-scheduler-api
ชื่อ repo ว่า "common" แต่ไม่ได้ publish เป็น package — แก้บั๊ก guard ต้องไล่หลาย repo (ดู OBS ใน volume index)
การแก้ไข: ถ้าต้องการ shared code จริง — แยก private npm; ถ้าไม่ — เปลี่ยนชื่อเอกสาร/repo ให้สื่อว่าเป็น microservice ไม่ใช่ library
QUAL-COMMON-03 · Address errors ถูกกลืน
ที่: address.service.ts:30-32,43-45,55-57,... — catch แล้ว log ไม่ rethrow
Client ได้ 200 พร้อม body ว่าง/undefined เมื่อ DB พัง — ยากต่อ debug ฝั่ง frontend
การแก้ไข: throw InternalServerErrorException หรือให้ filter จัดการ เหมือน Bank/File
QUAL-COMMON-04 · File soft-delete + volume hygiene
ที่: file.service.ts:96-106 ไม่ลบ disk; getFile ไม่เช็ค isCancel (:79-90); compose prod ไม่ mount ./uploads (docker-compose.yml:17-28)
ผลกระทบ: disk เต็มช้า ๆ; ไฟล์ที่ "ลบ" แล้วยังดาวน์โหลดได้; container recreate อาจสูญไฟล์ถ้าไม่มี persistent volume
การแก้ไข: hard-delete หรือ job เก็บกวาด; เช็ค isCancel; mount PVC ในทุก environment ที่รันจริง
OBS-COMMON-01 · Canonical truth เดิมผิด
เอกสาร workspace รุ่นก่อนระบุว่า vtrc-common เป็น npm dependency ของ NestJS services — ขัดกับ Dockerfile, .env, consumer HTTP, และการไม่มี dependency ใน package.json ของ repo อื่น
สถานะปัจจุบันใน docs/_audit-method/canonical-truths.md และ volume-06 index แก้แล้วให้ตรงโค้ด — คู่มือเล่มนี้เป็น deep dive ยืนยันหลักฐาน
OBS-COMMON-02 · CI ไม่รัน test + query logging
bitbucket-pipelines.yml:17—#- yarn testcomment ปิดapp.module.ts:29—logging: ['error', 'query']ในทุก environment รวม production ตาม config ปัจจุบัน
การแก้ไข: เปิด test ใน pipeline สำหรับ address repository (โดยเฉพาะ parameterization); จำกัด query log เฉพาะ non-prod
UNVERIFIED register
| รายการ | ทำไมยังยืนยันไม่ได้จาก repo นี้ |
|---|---|
| เจตนาเดิมของชื่อ "common" | ไม่มี ADR / commit message ในเอกสารนี้ยืนยันว่าเคยตั้งใจเป็น library แล้วเปลี่ยนเป็น service |
job-scheduler-api / recruitment-api จะเรียกในอนาคตหรือโค้ดถูกลบ | เหลือแค่ env/config |
Column name mapping createdDate vs create_date บน MSSQL จริง | ต้องเทียบ live schema หรือผลลัพธ์ convert script |
Production volume ของ uploads/ | compose ไม่สะท้อน cluster จริง |
ว่า /testTumbol ยังถูกเรียกจาก production frontend ชุดปัจจุบัน | mobile/web ที่ grep เจอใช้ listTumbol เป็นหลัก; /testTumbol อาจเป็น legacy path |
Cross-reference ไป volume อื่น
| หัวข้อ | ที่ควรอ่านต่อ |
|---|---|
| Secret sharing ข้าม Domain services | docs/_audit-method/canonical-truths.md, Vol 17 debt index |
Consumer benefit-api file flow | docs/volume-06-domain-microservices/benefit-api/04-auth-integrations |
Frontend urlCommon | docs/volume-04-frontends/vtrc-web/01-bootstrap-apollo |
| Pattern SQLi ซ้ำในกลุ่ม | volume-06 index ข้อ 5 |
Sign-off checklist (สำหรับ docs-verify)
- [x] Branch ที่อ้าง =
prod(58 files) - [x] ยืนยันไม่ใช่ npm library จาก package.json + Dockerfile + grep consumers
- [x] Map ครบ 3 modules (address / bank / file)
- [x] ระบุ SQL injection 2 จุดพร้อม safe counterpart ในไฟล์เดียวกัน
- [x] Consumer active 4 + vestigial 3
- [x] Debt IDs ใช้ prefix
SEC-COMMON-/QUAL-COMMON-/OBS-COMMON-