5 · Prioritized 90-day security plan
แผน 90 วันแปลง ROI จาก Vol 12.5 และ Top-20 runbook (Chapter 2) เป็น gate ที่วัดได้ — ไม่ใช่ wishlist
English: Days 0–30 = stop the bleeding (secrets + backdoors + open PII). Days 31–60 = injection + auth redesign. Days 61–90 = frontend/mobile hygiene + process so debt does not return.
กลับ Auth failures · Volume index
5.1 Guiding principles
| Principle | Source |
|---|---|
Deep IDs are canonical (SEC-* over shallow C1) | Vol 17.4 |
| Rotate shared secrets together | Vol 12.5; Chapter 3 |
| Do not re-enable meeting auth without fixing middleware | SEC-MTVTRC-05 + SEC-MTVTRC-06 |
| No inventing findings — only scorecard work | Volume contract |
| Mobile/OCR deep agents may still add Criticals | Vol 17.4 “in flight” — leave buffer in Day 61–90 |
5.2 Roles (suggested)
| Role | Owns |
|---|---|
| Security lead | Gate sign-off, rotation windows, IR hunts |
| Platform/Nest owners | benefit, recruit, pms, ai-recruit, chat, workflow, common, 2way-* |
| Legacy owners | vtrc-api, cu-central, centralize-api |
| Meeting owner | meeting-vtrc-api auth restore |
| Frontend owners | rc-backoffice, vtrc-web, backoffice-new, recruitment-web |
| Mobile owners | RSA removal + keystore |
| Docs maintainer | Reconcile Vol 17 counts after Day 90 |
5.3 Days 0–30 — Stop the bleeding
Goal
Eliminate active unauthenticated access to PII/salary/medical/admin and known backdoors; start coordinated secret rotation.
Work packages
WP-0 · War room kickoff (Day 0–2)
| Task | Output |
|---|---|
| Import Vol 12.5 clusters into IR board | Board with IDs from Chapters 1–4 |
| Build secret inventory (names only) | Chapter 3 matrix filled per canonical branch |
| Confirm deploy windows for Cluster A/B/C | Calendar holds |
WP-1 · Backdoors out (Day 1–7)
| ID | Owner hint | Done when |
|---|---|---|
SEC-2WAYAPI-01 | 2way | i3admin gone; logs hunted |
SEC-2WAYVTRC-01/01b | 2way-vtrc | testnaja gone |
SEC-CU-04 | cu-central | signinbypass gone |
SEC-RCWEB-03 | recruitment-web | OTP constant removed from prod bundle |
Map: Runbook R02–R04 + supplemental OTP.
WP-2 · Close highest-risk @Public() / open surfaces (Day 3–14)
| ID | Done when |
|---|---|
SEC-BENEFIT-03/04/05 | Medical/batch/upload require JWT |
SEC-PMS-01 | Salary endpoint requires auth |
SEC-RECRUIT-03 | Blacklist import requires auth |
CRIT-03 (start) | Plan + first controllers de-public; full finish may slip to Days 15–30 |
SEC-2WAYVTRC-02/07 | getProfile + updateIncident authenticated |
SEC-MTVTRC-02 | mockuser route removed from prod |
SEC-VTRC-API-03 | webdeployment locked or deleted |
WP-3 · Secret rotation wave 1 (Day 7–21)
| Cluster | Scope |
|---|---|
| A Domain JWT | benefit + recruit + ai-recruit + pms (+ verify vtrc-api) |
B sa | chat + jobsched + common + workflow (+ 2way-vtrc hardcode) |
| Meeting D | all three .env* sets |
| Billable G | OpenAI/remove.bg/GCP/SFTP/SMTP as listed in Chapter 3 |
Map: Runbook R01. Do not close JWT forge ticket until Cluster A cutover verified.
WP-4 · Meeting auth restore (Day 14–30)
| Step | ID |
|---|---|
Fix checkBypassMiddleWare verify | SEC-MTVTRC-06 |
Re-enable global /meeting/secure/* | SEC-MTVTRC-05 |
| Auth static files + corn-email hooks | SEC-MTVTRC-07 |
| Remove health-check real email abuse | SEC-MTVTRC-03 |
Day-30 gate (must all pass)
- [ ] Grep clean:
i3admin,testnaja,signinbypasson prod/uat canonical branches - [ ] Unauth curl to salary / wdHospitals add / blacklist import → 401
- [ ] Old Domain
SECRET_KEYforge → 401 on all Cluster A services - [ ] Meeting unauth secure mutation → 401
- [ ] IR log hunts for backdoor usage documented (even if empty)
TH: ถ้า Day-30 ยัง rotate ไม่ครบ — ห้ามประกาศ “ปิด Critical secrets” แต่ต้องมี exception list ที่ลงนาม
5.4 Days 31–60 — Injection + auth machinery
Goal
Close SQL/command injection clusters; make Nest/Express verify real; remove dangerous defaults.
WP-5 · SQL parameterization sprint (Day 31–50)
| ID | Repo | Notes |
|---|---|---|
SEC-BENEFIT-02 | benefit-api | Largest surface; extract shared builder QUAL-BENEFIT-01 |
SEC-2WAYAPI-03 | 2way-api | announces/surveys/complaints + dormant PMS cron SQL |
SEC-RECRUIT-02 | recruitment-api | parameterized EXEC |
SEC-CHAT-01 | chat-center-api | rooms/tags |
SEC-COMMON-02 | vtrc-common | address |
SEC-VTRC-API-07 | vtrc-api | statLog |
Add static check: rg / semgrep for query(`…${ patterns (Vol 18 old systemic pattern 2 / Vol 17.4).
WP-6 · Command execution hardening (Day 31–40)
| ID | Fix |
|---|---|
SEC-VTRC-API-01 | execFileSync argv array — R10 |
SEC-VTRC-API-04 | apiKey+JWT on download — R11 |
WP-7 · Auth redesign (Day 35–60)
| ID | Fix |
|---|---|
SEC-2WAYAPI-04 / 08 / SEC-2WAYLINE-01 | Remove public mutations; restore employees permission; LINE trust boundary |
SEC-2WAYAPI-05 | ignoreExpiration: false |
SEC-WORKFLOW-01 | Global JWT verify; stop role-from-sourceDB |
SEC-AIRECRUIT-02 | Role/scope on AI |
SEC-COMMON-01 | Remove public writes; service-to-service auth |
CORR-SSO-01 | Decision + implement real JWT or retire broken 2FA endpoints |
SEC-JOBSCHED-02 | Authenticate outbound cron POST |
SEC-CHAT-02/03 | WS auth + LINE signature |
Finish CRIT-03 | centralize-api no longer 100% public |
WP-8 · Fail-fast secrets in code (Day 40–55)
| ID | Fix |
|---|---|
SEC-VTRC-API-02 | No default 'secret' |
SEC-CU-01 | No empty JWT secret |
SEC-2WAYAPI-02 / SEC-2WAYVTRC-08 | No SSO/AES fallbacks / hardcoded SSO |
SEC-WORKFLOW-02 | Placeholder .env.example only |
Day-60 gate
- [ ] Injection PoCs from benefit/2way/recruit scorecards fail on UAT
- [ ]
ignoreExpiration: trueabsent from 2way auth verify paths - [ ] workflow rejects unsigned payloads
- [ ] centralize employee GETs require auth
- [ ] SSO decision recorded; either JWT works end-to-end or guarded stubs removed
- [ ] Boot fails if critical JWT env missing on vtrc-api / 2way-api
5.5 Days 61–90 — Frontend, mobile, process, reconcile
Goal
Reduce session theft; remove client private keys; install process controls; update debt registers.
WP-9 · Frontend session & XSS (Day 61–80)
| ID | Work |
|---|---|
SEC-VTRC-RC-02, SEC-BONEW-01 | Replace pathLogin / URL tokens with POST or httpOnly handoff |
SEC-VTRC-RC-04, SEC-VTRC-WEB-02 | DOMPurify on dangerouslySetInnerHTML |
SEC-VTRC-WEB-03/04, SEC-VTRC-RC-06 | CSP (+ SRI where applicable) |
SEC-VTRC-RC-01, SEC-RCWEB-01 | Purge frontend secrets; stop Stimulsoft conn / AES in CRA env |
| Plan | localStorage → httpOnly (SEC-VTRC-RC-05, SEC-VTRC-WEB-01) — may complete design in 90d, full migrate later |
WP-10 · Mobile (Day 61–90)
| ID | Work |
|---|---|
SEC-MOBILE-01, SEC-NEW-01 | Remove PEM private from apps; server-side crypto; rotate keypair |
SEC-MOBILE-02 | Keystore secrets → CI only; signing rotation plan |
SEC-NEW-02 | Prefer secure storage over plaintext AsyncStorage (High) |
WP-11 · Hygiene & PDPA leftovers (Day 70–90)
| ID | Work |
|---|---|
SEC-REPORTLOG-01/02 | Purge logs from git; enable *.log gitignore |
SEC-BENEFIT-06/07 | Remove SSRF ping + testProfile |
QUAL-BENEFIT-04 | User uploads in git |
| Encrypt false | SEC-2WAYAPI-06, SEC-2WAYVTRC-05, SEC-RECRUIT-07 — enable TLS to MSSQL |
WP-12 · Process (prevent recurrence)
| Control | Addresses pattern |
|---|---|
PR checklist: no @Public() on write/PII without threat model | Nest epidemic |
| CI grep fail on `i3admin | testnaja |
Forbid committing .env / *-sa-key.json | Secrets cluster |
| semgrep for SQL string interpolation | Injection cluster |
Release checklist: no mock/debug routes in prod | SEC-MTVTRC-02 |
| Secret manager pilot (Vault / cloud SM / k8s) | Vol 17.4 pattern 6 |
WP-13 · Documentation reconcile
| Doc | Action |
|---|---|
| Vol 17 master register | Merge deep IDs; retire shallow C1/C2 duplicates |
| Vol 17 index totals | Replace 275 when mobile/OCR agents done |
| Vol 12.3 | Keep as legacy table; Vol 12.5 + this volume are operational |
| This volume | Update gates with completion dates |
Day-90 gate
- [ ] Top-20 runbook checklist (Chapter 2) ≥90% complete; exceptions signed
- [ ] No tokens in URL paths on rc-backoffice / backoffice-new SSO flows
- [ ] DOMPurify (or equivalent) on known XSS hotspots in vtrc-web + rc-bo
- [ ] Mobile apps on store track no longer ship the old RSA private key
- [ ] CI secret/backdoor greps enforced on critical repos
- [ ] Vol 17 reconciliation PR opened (even if mobile agents late — document residual)
5.6 Week-by-week view (compact)
| Week | Focus | Primary IDs |
|---|---|---|
| 1 | Backdoors + inventory | SEC-2WAYAPI-01, SEC-2WAYVTRC-01, SEC-CU-04 |
| 2 | Public PII/salary/medical | SEC-BENEFIT-03..05, SEC-PMS-01, SEC-RECRUIT-03 |
| 3–4 | Secret rotate A/B/D/G | Chapter 3 clusters |
| 4 | Meeting auth | SEC-MTVTRC-05/06 |
| 5–7 | SQL + qpdf | SEC-BENEFIT-02, SEC-2WAYAPI-03, SEC-VTRC-API-01 |
| 6–8 | Nest/Express auth | SEC-2WAYAPI-04/05, SEC-WORKFLOW-01, CRIT-03 |
| 9–11 | Frontend XSS/URL | SEC-VTRC-RC-02/04, SEC-VTRC-WEB-02/03 |
| 10–13 | Mobile RSA | SEC-MOBILE-01, SEC-NEW-01 |
| 12–13 | Process + docs | CI greps, Vol 17 reconcile |
5.7 Risk register for the plan itself
| Risk | Mitigation |
|---|---|
Frontend breaks when @Public() removed | Pair with FE owners; temporary service account only if threat-modeled |
| Shared JWT rotate causes multi-service outage | Single cutover window; feature flag readiness checks |
| Meeting middleware fix incomplete | Block uncomment behind code review of SEC-MTVTRC-06 tests |
ignoreExpiration change breaks clients | Communicate; force re-login |
| Mobile RSA rotate breaks old app versions | Versioned server endpoints; force upgrade policy |
| New Criticals from mobile/OCR deep dive | Buffer in Days 61–90; do not claim “zero Critical” |
5.8 Success metrics (executive)
| Metric | Day 0 (Deep Redo) | Day 90 target |
|---|---|---|
| Known intentional backdoors in prod branches | 3+ (i3admin, testnaja, signinbypass) | 0 |
| Domain services sharing one committed JWT in git | 4 | 0 committed; secrets in manager |
Nest write/PII endpoints documented @Public() in Top Critical set | Many (benefit/pms/recruit/2way/centralize) | 0 without exception |
| Meeting global secure auth | Commented out | Enabled + verified |
| SQL injection Critical IDs open | benefit/2way/recruit/chat/common/vtrc | Closed or under regression tests |
| Token-in-URL SSO | rc-bo + bo-new | Removed |
English: Day 90 is not “perfect security.” It is “Critical clusters from Deep Redo Wave 1–2 no longer reachable by default.”
ไทย: เป้า 90 วันคือปิดคลัสเตอร์ Critical ที่ Deep Redo ยืนยันแล้ว — ไม่ใช่เคลมว่าแพลตฟอร์มปลอดช่องโหว่ทั้งหมด
5.9 Mapping ROI (Vol 12.5) → plan phases
| Vol 12.5 ROI step | Plan phase |
|---|---|
| 1. Rotate secrets + filter-repo | Days 0–30 WP-3 |
2. Remove backdoors + close @Public() write/PII | Days 0–30 WP-1/2; Days 31–60 WP-7 |
| 3. Parameterized SQL | Days 31–60 WP-5 |
4. Replace execSync qpdf | Days 31–40 WP-6 |
5. SSO redesign CORR-SSO-01 | Days 35–60 WP-7 |
| 6. HttpOnly / CSP / DOMPurify | Days 61–80 WP-9 |
5.10 Handoff checklist to Vol 19 / ops
เมื่อจบ 90 วัน ส่งต่อ:
- Completed Top-20 checklist with PR links
- Exception list (remaining Criticals + rationale)
- Secret manager pilot status
- Updated Vol 17 counts
- Suggested Wave plan items for residual Highs (
SEC-2WAYAPI-06encrypt, CORS typos, etc.)
5.11 Quick reference — Top-20 ↔ day bands
| Runbook | Day band |
|---|---|
| R01 secrets | 0–30 |
| R02–R04 backdoors | 0–7 |
| R05 meeting | 14–30 |
| R06 centralize | 15–60 |
| R07–R09 public PII | 3–14 |
| R10–R12 qpdf/download/defaults | 31–55 |
| R13–R15 SQL | 31–50 |
| R16–R18 auth redesign | 35–60 |
| R19 frontend | 61–80 |
| R20 mobile | 61–90 |
จบ Volume 18 · Security Review (Deep Redo)