Skip to content

04 · Scorecard — vtrc-common

บทสรุป debt จาก 0103 — 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
🔴 Criticalactive security/data-loss/availability riskต้องแก้ก่อน deploy ถัดไป
🟡 Highcorrectness/maintainability hazardวางแผนแก้ใน quarter นี้
🟢 Mediumfriction/hygieneแก้ตามโอกาส

Summary table

Debt IDSeverityสั้น ๆหลักฐาน
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🟡 HighSQL 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🟡 HighCORS origin: '*' + public address/bank ขยาย attack surface จาก browsermain.ts:29-33
QUAL-COMMON-01🟢 Mediumpath ชื่อ /testTumbol* แต่ใช้ใน production comment/flowaddress.controller.ts:32-41
QUAL-COMMON-02🟢 Mediumlibs/ copy-paste ข้าม repo ไม่ได้เป็น shared package จริงเทียบ libs/jwt กับ chat-center-api
QUAL-COMMON-03🟢 MediumAddress service swallow errors → client ได้ empty แทน HTTP erroraddress.service.ts:30-32
QUAL-COMMON-04🟢 MediumSoft-delete ไม่ลบ disk → orphan files; compose prod ไม่ mount uploadsfile.service.ts:96-106, docker-compose.yml:17-28
OBS-COMMON-01🟢 Mediumเอกสาร workspace เดิมเรียกผิดว่า npm libraryดู 02 §6–7
OBS-COMMON-02🟢 MediumCI comment yarn test; TypeORM log ทุก querybitbucket-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 / delete
  • vtrc-common/src/modules/address/address.controller.ts (branch: prod):14-48 — ทุก address route
  • vtrc-common/src/modules/bank/bank.controller.ts (branch: prod):13-23 — ทั้งสอง bank route
  • vtrc-common/libs/jwt/jwt-auth.guard.ts (branch: prod):15-24 — bypass เมื่อ isPublic
  • vtrc-common/.env (branch: prod):18SECRET_KEY สำหรับ route ที่ยัง guard อยู่

Evidence:

typescript
@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 แล้วอันตราย)

การแก้ไข:

  1. ถอด @Public() จาก file/save และ file/delete — ให้ benefit-api ส่ง service-to-service JWT หรือ mTLS / network policy
  2. ถ้า address/bank ต้อง public จริงสำหรับ dropdown — คง @Public() ได้ แต่ต้องแก้ SEC-COMMON-02 ก่อน
  3. หมุน SECRET_KEY และเลิกแชร์ข้าม repo
  4. เพิ่ม 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):19COPY --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

การแก้ไข:

  1. Rotate MSSQL password + JWT secret ทั้งหมดที่เกี่ยวข้อง
  2. ลบ .env ออกจาก git tracking + purge history
  3. หยุด COPY .env ใน Dockerfile — inject ผ่าน runtime secret / env ของ orchestrator
  4. แยก DB account ต่อ service แทน sa

Severity: High (🟡)

SEC-COMMON-02 · SQL injection ใน AddressRepository

ที่:

  • vtrc-common/src/modules/address/address.repository.ts (branch: prod):81-94findNativeTumbolByProvinceCodeAndAumphurCode
  • vtrc-common/src/modules/address/address.repository.ts (branch: prod):117-130findSubDistirctByProvCodeAndDistrictCode
  • Entry: address.controller.ts:32-36 (/testTumbol), :44-48 (/listSubDistrict...)
  • Input DTO: tumbol.req.dto.ts:4-12@IsString() เท่านั้น

Evidence:

typescript
WHERE p.province_code = '${provCode}'
AND   a.aumphur_code = '${amphCode}'

ทั้งสองเมธอดเป็น @Public() → unauthenticated SQLi บน DB vtrc_common ด้วยสิทธิ์บัญชีจาก .env (sa)

เปรียบเทียบจุดที่ถูกต้องในไฟล์เดียวกัน: findQueryBuilderTumbolByProvinceCodeAndAumphurCode ใช้ .setParameter('provCode', provCode) (:109-113)

การแก้ไข:

  1. แทนที่ทั้งสองเมธอดด้วย QueryBuilder parameterized หรือ query(sql, params)
  2. เปลี่ยน route /testTumbol ให้ชี้ไป V3 ที่ปลอดภัย (หรือลบ native path)
  3. เพิ่ม regression test ที่ส่ง quote/semicolon ใน provCode แล้วคาดว่าไม่เกิด syntax error จาก injection
  4. พิจารณา WAF / parameterized-only lint rule ใน CI

SEC-COMMON-04 · CORS wildcard บน public APIs

ที่: vtrc-common/src/main.ts (branch: prod):29-33

typescript
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 test comment ปิด
  • app.module.ts:29logging: ['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 servicesdocs/_audit-method/canonical-truths.md, Vol 17 debt index
Consumer benefit-api file flowdocs/volume-06-domain-microservices/benefit-api/04-auth-integrations
Frontend urlCommondocs/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-