10.11 · Scorecard + remediation
บทสุดท้ายของ Volume 10 สรุป severity ทั้งหมด จัด priority remediation เป็น 4 wave และเชื่อมไปยัง modernization target ใน Volume 15
Severity summary
| Severity | Count | IDs |
|---|---|---|
| 🔴 Critical | 14 | SEC-1 (×5 endpoints), SEC-2 (webdeployment), SEC-3 (Conicle SSRF), SEC-4 (statLog SQLi), SEC-5 (GCP key), SEC-6 (prod device keys), SEC-7 (centralize .env), SEC-8 (cu-central .env), SEC-9 (REACT_APP bundle), SEC-10 (no rate limit), CORR-4 (RBAC), CORR-6 (syncModel), LEG-1 (Node 12) |
| 🟡 High | 12 | CORR-2 (txn swallow), PERF-3 (JSON scan), PERF-4 (N+1 RBAC), PERF-7 (phpMyAdmin), PERF-8 (network_mode), SEC-6 (MSSQL trust), LEG-3/4 (Apollo/React EOL), LEG-9 (jsonwebtoken), LEG-10 (PDF stack), LEG-12 (redis v3), LEG-15 (no lockfile) |
| 🟢 Medium | 8 | CORR-7/8, LEG-11/13/14/17/18/24/25 |
Scorecard ตาม dimension
| Dimension | Current State | After Wave 0-1 | After Wave 2-3 (target) |
|---|---|---|---|
| Authentication | 🔴 refresh decode ไม่ verify, JWT 'secret' default, no rate limit | 🟡 verify + hard-fail + rate limit | 🟢 signed refresh token rotation + lockout + MFA option |
| Authorization | 🔴 CORR-4 RBAC inconsistency, no default-deny | 🟡 Casbin policy + test matrix | 🟢 typed RoleGrant + cached permission set |
| Credential hygiene | 🔴 GCP key + prod device keys + .env in repo | 🟡 rotated + secret manager | 🟢 short-lived tokens + KMS-backed |
| Injection | 🔴 execSync × 5 + /webdeployment + statLog SQLi | 🟡 arg-array + parameterized SQL | 🟢 no shell-out in handlers + sandboxed renderer |
| File handling | 🔴 multer no filter + path traversal | 🟡 fileFilter + normalize | 🟢 object storage + signed URL |
| Network | 🔴 CORS *, phpMyAdmin exposed, network_mode host | 🟡 allow-list + bastion DB | 🟢 mTLS internal + zero-trust |
| Logging | 🔴 refreshToken + forceLoging injection | 🟡 redaction + structured | 🟢 PII denylist + sampled |
| Supply chain | 🔴 Node 12, axois, jsonwebtoken@8 | 🟡 Node 20 + clean deps | 🟢 Go modules + pinned + SBOM |
| Runtime | 🔴 introspection/playground prod | 🟡 gated by IS_PRODUCTION | 🟢 schema registry + rate limit + WAF |
Legend
- 🔴 Critical = active risk ต้องแก้ทันที
- 🟡 High = มี impact จริงแต่ต้องมีเงื่อนไข
- 🟢 Medium = hygiene/debt ยังไม่อันตรายทันที
Wave 0 — Stop-the-bleed (Weeks 0-2)
Quick wins ที่ลบ Critical risk โดยไม่ต้อง refactor architecture
| # | Action | Debt IDs | Effort | Chapter |
|---|---|---|---|---|
| 1 | Rotate GCP SA key + purge from git history | SEC-11 | 1d | 9.5 |
| 2 | Rotate prod device keys (ทั้ง 7) + ย้ายไป env | SEC-3 | 1-2d | 9.5 |
| 3 | Hard-fail at boot ถ้า JWT_SECRET missing | SEC-4 | 0.5d | 9.5 |
| 4 | Gate playground/introspection on !IS_PRODUCTION | SEC-2 | 0.5d | 9.10 |
| 5 | แก้ CORS typo + ลบ default cors() | SEC-5 (vtrc-api) | 0.5d | 9.8 |
| 6 | centralize-api CORS allow-list แทน * | SEC-5 | 0.5d | 9.8 |
| 7 | Replace execSync(qpdf) → execFile arg-array | SEC-1, PERF-1 | 2-3d | 9.6 |
| 8 | ลบ /webdeployment endpoint | SEC-2 (webdeploy) | 0.5d | 9.6 |
| 9 | Parameterize statLog.js SQL | SEC-4 (SQLi) | 1d | 9.6 |
| 10 | Remove phpMyAdmin from prod compose | PERF-7 | 0.5d | 9.8 |
| 11 | Disable syncModelToTable() in prod | CORR-6 | 0.5d | (Vol 8.4) |
| 12 | Purge typosquat/unused packages (axois, s, stream, findit) | SEC-9, LEG-13 | 1d | 9.10 |
| 13 | Rotate centralize-api DB password + ลบ .env | SEC-7 | 1d | 9.5 |
| 14 | Rotate cu-central-api credentials + ลบ .env.develop-test | SEC-8 | 1-2d | 9.5 |
| 15 | Rotate MSSQL sa password + ย้ายออกจาก REACT_APP_* | SEC-9 | 1d | 9.5 |
| 16 | network_mode: host → bridge + port mapping | PERF-8 | 0.5d | 9.8 |
รวม Wave 0 ≈ 2-3 สัปดาห์ — ลด Critical จาก 14 เหลือ 0
Wave 1 — Stabilize (Months 1-3)
Interim modernize บน Node.js stack ปัจจุบัน
| # | Action | Debt IDs | Effort | Chapter |
|---|---|---|---|---|
| 1 | Bump Node 12 → 20 LTS + ลบ esm shim | LEG-1, LEG-2 | 2-3 wk | 9.10 |
| 2 | Native ESM ("type": "module") | LEG-2 | 1 wk | 9.10 |
| 3 | Upgrade jsonwebtoken v9 + add algorithms: ['HS256'] | LEG-9, SEC (decode-vs-verify) | 1 wk | 9.3, 9.10 |
| 4 | Upgrade redis v4, multer v2, axios v1 | LEG-12, LEG-20 | 1-2 wk | 9.7, 9.10 |
| 5 | Add rate limit + structured logger + Redis session-indexed lookup | SEC-10, LEG-24, PERF-3 | 1-2 wk | 9.3, 9.9, 9.10 |
| 6 | Fix RBAC CORR-4 + add (role × menu × action) test matrix | CORR-4 | 1-2 wk | 9.4 |
| 7 | Frontend: Apollo cache-and-network + consolidate duplicate libs | PERF-2, LEG-26 | 1-2 wk | (Vol 3.2) |
| 8 | Replace ldapjs → ldapts + fix cu-central-api httpRequest.js | LEG-8, SEC-7, CORR-9 | 1 wk | 9.10 |
| 9 | multer fileFilter + path traversal fix | SEC (file) | 1 wk | 9.7 |
| 10 | Conicle SSRF allow-list + path normalization | SEC (SSRF) | 1 wk | 9.6, 9.7 |
| 11 | PII redaction ใน logger + ลบ console.log 77 ตัว | LEG-24, SEC (log) | 1 wk | 9.9 |
| 12 | Commit centralize-api/yarn.lock + add engines.node | LEG-15, LEG-18 | 0.5d | 9.10 |
รวม Wave 1 ≈ 3-4 เดือน — ปิด High ส่วนใหญ่
Wave 2 — Domain extraction → Go (Months 3-12)
Strategic rewrite ทีละ bounded context เริ่มจากที่ isolated ที่สุด
| Order | Bounded Context | Why first | Target |
|---|---|---|---|
| 1 | document-renderer (PDF/Excel/qpdf) | Self-contained + กำจัด root cause ของ SEC-1/PERF-1 | Go + chromedp + pdfcpu |
| 2 | identity (auth, session, RBAC, device keys) | ปลดล็อก context อื่น + แก้ SEC-3/4, CORR-4 | Go + Casbin + Postgres |
| 3 | payroll (slip, tax) | Read-mostly + clean boundary | Go + gqlgen, MSSQL adapter |
| 4 | time-attendance | High-volume read | Go + Redis cache |
| 5 | leave | Multi-level workflow | Go + saga |
| 6 | welfare (WDHospital) | ซับซ้อนที่สุด (13-state machine, 3,179 LOC controller) | Go + state-machine package |
| 7 | provident-fund, communication, organization, shared-kernel | โมดูลที่เหลือ | Go |
แต่ละ context ใช้ Hexagonal / DDD
cmd/ # driving adapters (GraphQL via gqlgen, REST via chi)
internal/<context>/
├── domain/ # entities, value objects, domain services
├── port/ # use-case interfaces
├── adapter/ # db (sqlc), redis, sftp, pdf, smtp
└── usecase/ # application servicesWave 3 — Frontend → Nuxt 3 (Months 6-15)
เริ่มหลัง identity + payroll Go services live (เพื่อให้ frontend ใหม่มี API เสถียร)
apps/web/ # แทน vtrc-web
└── src/
├── app/ # global shell, providers
├── processes/ # routing strategies (auth, onboarding)
├── pages/ # route-level
├── widgets/ # composite blocks
├── features/ # user actions
├── entities/ # domain slices
│ └── <slice>/api/ # co-located gql operations
└── shared/ # ui-kit, api, lib, config, i18nModernization guardrails
| # | Guardrail | Prevents | Chapter |
|---|---|---|---|
| G1 | No console.log ใน prod code — structured logger only | LEG-24 | 9.9 |
| G2 | No execSync / shell-out ใน request handlers — arg-array only | SEC-1, PERF-1 | 9.6 |
| G3 | No secrets in source — secret-manager or KMS | SEC-3, SEC-4, SEC-11 | 9.5 |
| G4 | engines.node/go.mod enforced — CI fail on mismatch | LEG-18 | 9.10 |
| G5 | Lockfile committed — CI fail on drift | LEG-15 | 9.10 |
| G6 | Strict types (Go strict-default; TS strict: true) | LEG-17 | (Vol 8) |
| G7 | Migrations forward-only — no ORM auto-sync ใน prod | CORR-6 | (Vol 8.4) |
| G8 | One design system, one icon set, one DnD lib, one date lib | LEG-26 | (Vol 3) |
| G9 | No _bk/_copy files — .gitignore + pre-commit | LEG-14 | — |
| G10 | PII never in URLs | SEC-8 | 9.6 |
| G11 | CORS allow-list explicit — never * | SEC-5 | 9.8 |
| G12 | Rate limit ทุก public endpoint | SEC-10 | 9.10 |
| G13 | Casbin policy test matrix สำหรับทุก (role × menu × action) | CORR-4 | 9.4 |
| G14 | Bounded contexts < 400 LOC/file — god-file fail CI | PERF-6, LEG-21, LEG-23 | (Vol 8) |
| G15 | i18n ที่ presentation layer — never ใน error strings | LEG-30 | — |
| G16 | No typosquat/git-URL deps — npm audit ใน CI | SEC-7, SEC-9 | 9.10 |
| G17 | Refresh token verify (not decode) + rotate on use | SEC (refresh) | 9.3 |
Cross-reference ไป volume อื่น
| Volume | Topic | Relevance |
|---|---|---|
| Volume 3.3 | Authentication & session internals | Mechanics (Volume 10 = vulnerability) |
| Volume 8.4 | Schema management + syncModelToTable | CORR-6 detail |
| Volume 8.9 | Identity across databases | Multi-DB auth mapping |
| Volume 8.10 | Indexing, transactions, performance | PERF-3, PERF-4, CORR-2 |
| Volume 8.11 | Data integrity risks | PII, soft delete |
| Volume 15 | Modernization | Go (Hex/DDD) target |
สิ่งที่ Volume นี้ไม่ได้ทำ
- ไม่ได้ run
npm audit/yarn audit— ต้องการ network install นอก scope static audit - ไม่ได้ audit TLS cipher suite ที่ edge nginx (config mount จาก host ไม่ได้อยู่ใน repo)
- ไม่ได้ cover OWASP top 10 ทั้งหมด — เน้นเฉพาะที่พบจริงใน codebase
- ไม่ได้ trace
createslip/createtaxend-to-end เพื่อยืนยันว่า qpdf RCE เป็น unauthenticated โดยแท้ — ต้อง follow-up
สรุป Volume 10
VTRC มี security posture ที่ 🔴 14 Critical + 12 High — ส่วนใหญ่มาจาก
- Secrets ใน repo (GCP key, prod device keys, .env, REACT_APP bundle) — Wave 0 แก้ได้เร็วที่สุด
- Injection (execSync × 5, /webdeployment, statLog SQLi) — Wave 0 + Wave 1
- Authentication bug (refresh decode, JWT 'secret' default, no rate limit) — Wave 0 + Wave 1
- Network exposure (CORS, phpMyAdmin, network_mode host) — Wave 0
Wave 0 (2-3 สัปดาห์) ลด Critical เหลือ 0 โดยไม่ต้อง refactor architecture ใหญ่ — เป็นจุดเริ่มที่คุ้มที่สุด
Wave 2-3 (ปฏิรูปเชิงโครงสร้าง) คือ modernization target ที่จะกำจัด root cause อย่างถาวร รายละเอียดอยู่ใน Volume 15