Skip to content

6 · Scorecard + debt register

บทสรุปจาก 0105 สำหรับ ai-recruitment-api (branch: prod) — severity catalog พร้อม evidence / impact / remediation + health scorecard


Severity legend

Levelเกณฑ์Action
🔴 Criticalactive security / financial / data-loss riskแก้ก่อนปล่อยรอบถัดไป
🟡 Highcorrectness / abuse / maintainability ที่กระทบจริงวางแผนใน sprint ใกล้ ๆ
🟢 Mediumfriction / hygieneแก้ตามโอกาส

Severity: Critical (🔴)

SEC-AIRECRUIT-01 · Production secrets + billable API keys อยู่ใน git

ที่:

  • ai-recruitment-api/.env (branch: prod):2,5,7,9-10,11
  • ถูก track (git ls-files .env) ทั้งที่ .gitignore:51 มี .env

Evidence: ไฟล์มี DB_PASSWORD, OPENAI_API_KEY, REMOVE_BG_API_KEY, HCTI_*, JWT_SECRET เป็น plaintext
OpenAI / remove.bg เป็นคีย์ที่เรียกแล้วเกิดค่าใช้จ่ายจริง

Impact:

  • ใคร clone repo (หรืออ่าน history) ได้คีย์ → ใช้โควต้าองค์กรได้จนกว่าจะ rotate
  • JWT secret ที่รั่ว = ออก token ปลอมเข้าทุก endpoint ของ service นี้ (และ service ที่ share secret — ดูด้านล่าง)
  • MSSQL credential ถ้า network ถึง host ได้ = อ่านข้อมูล recruitment AI schema

Remediation:

  1. Rotate OpenAI, remove.bg, HCTI, JWT, MSSQL ทันที
  2. ลบ .env ออกจาก git index + ล้าง history (git filter-repo / BFG)
  3. ใช้ secret manager / k8s Secret / CI env ตอน deploy
  4. ตรวจ Dockerfile COPY . . ไม่ให้ bake secret เข้า image

SEC-AIRECRUIT-02 · Auth ตรวจแค่ JWT signature — ไม่มี role / issuer / client check

ที่: middleware/verifyJWT.js:7-39 + mount server.js:37

Evidence:

javascript
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
next();

ไม่มีการอ่าน decoded.role, decoded.client, decoded.iss

Shared secret confirmed: JWT_SECRET ของ service นี้ hash ตรงกับ SECRET_KEY ของ benefit-api และ recruitment-api (ดู 04)

Impact:

  • Token ที่ออกให้ context อื่น (รวมถึง client ที่ไม่ใช่ back-office ถ้าใช้ secret เดียวกัน) ใช้เรียก generate ภาพ / สัมภาษณ์ / OpenAI ได้
  • ไม่มี quota ต่อ uid — abuse = บิล OpenAI พุ่ง

Remediation:

  1. ตรวจ claim ที่ issuer ของ back-office ใส่ไว้ (เช่น client, iss) ให้ allowlist
  2. แยก signing key ของ AI helper จาก candidate-facing tokens ถ้าเป็นไปได้
  3. ใส่ rate limit ต่อ req.user.uid / IP ที่ reverse proxy

Severity: High (🟡)

QUAL-AIRECRUIT-01 · Dead code / broken export ทำให้เข้าใจผิดว่า endpoint มีหรือไม่มี

ที่:

  • controllers/module2_1_get_background_v2.js — export getBackgroundV2 ไม่ถูก mount
  • controllers/module_2_3_combine_image.js — ไม่ถูก require
  • routes/get-post-upload_image.js — ไม่ถูก mount จาก server.js
  • controllers/get-post-upload_image.js:176 export removeBgUploadController แต่ routes/index.js:33 ต้องการ uploadImageControllerundefined (log ที่ :44)

Impact: ทีมอาจแก้ไฟล์ผิดตัว (เช่น แก้ module_2_3_* แล้ว production ไม่เปลี่ยน) หรือคิดว่า upload route พังทั้งที่ route จริงใช้ removeBgUploadController.js

Remediation: ลบหรือ mount ให้ชัด; แก้ export ให้ตรงชื่อถ้ายังต้องการไฟล์สำเนา


CORR-AIRECRUIT-01 · Sync jobReqPosition ระหว่าง recruitment กับ recruitment_ai ไม่ปรากฏใน repo นี้

ที่: 03 Persistence — service อ่านอย่างเดียวจาก recruitment_ai
ไม่มี cron (05) ที่ copy ข้อมูล

Impact: back-office เห็นตำแหน่งในระบบหลักแต่ AI description ว่าง เพราะแถวใน AI DB ไม่ตรง / ไม่มี

Remediation: เอกสาร + ยืนยันกับทีม data ว่า ETL อยู่ที่ใด; หรือเพิ่ม sync ที่ ownership ชัดใน recruitment-api


CORR-AIRECRUIT-02 · Prompt "technical" ของ interview อาจคัดลอกผิดเป็นข้อความ General

ที่: generateInterviewQuestionsController.js:158-169
ตัวแปร technicalQuestion แต่ prompt ยังกล่าวว่า "writing 10 General interview question" และพูดถึง soft skills เหมือน general (:167)

Impact: ชุด Technical กับ General อาจซ้ำเชิงเนื้อหา — คุณภาพคำถามสัมภาษณ์เพี้ยนโดยไม่มี error ชัด

Remediation: แก้ข้อความ prompt technical ให้ตรงเจตนา; เพิ่ม regression test ตรวจ substring


PERF-AIRECRUIT-01 · Puppeteer launch ต่อ request + OpenAI fan-out หนา

ที่:

  • combineJobPostingController.js:377-386 launch ใหม่ทุกครั้ง
  • Job description 4× gpt-4o sequential (:230-334)
  • Character path 3× image + remove.bg (module2_2_controller.js:174-201)
  • Interview ≥6× gpt-3.5 (:156-291)

Impact: latency สูง, ต้นทุนต่อคลิกสูง, risk OOM บน replica เล็กเมื่อมีคนกดพร้อมกัน

Remediation: browser pool / reuse; cache ผลลัพธ์; ลดจำนวน image; queue สำหรับ combine


Severity: Medium (🟢)

QUAL-AIRECRUIT-02 · Hardcode สมมติฐานประชากรใน PROMPT_RULES

ที่: module2_2_controller.js:31-44
แทนอาชีพบางกลุ่มด้วย "asian age 40 ..." เสมอ

Impact: ภาพประกาศสาธารณะสะท้อน bias ที่แก้ได้แค่ผ่านโค้ด; ไม่มี config

Remediation: ทบทวนกับ HR/comms; ย้าย rules ไป config ที่อนุมัติได้


OBS-AIRECRUIT-01 · ไม่มี test suite

ที่: ไม่พบไฟล์ .test.js / .spec.js ทั้ง repo

Impact: เปลี่ยน prompt / SQL / route แล้วไม่มี safety net — regression สูงสุดในกลุ่มที่เทียบกับ recruitment-api (มี spec จำนวนมาก)


OBS-AIRECRUIT-02 · Logger สองระบบปนกัน

ที่: มี logger.js แต่ controllers/services ใช้ console.log/console.error จำนวนมาก (เช่น routes/index.js:26,41-48, verifyJWT.js:27, removeBgService.js:189)

Impact: รวม log ใน aggregator ยาก; format ไม่สม่ำเสมอ


QUAL-AIRECRUIT-03 · Dependencies ประกาศแต่ไม่ใช้ / ขาดจาก package.json

รายการหลักฐาน
canvas, sharp ใน dependenciespackage.json:12,22 — ไม่พบ `require('canvas'
uuid ถูก requiremodule2_1_get_background.js:4ไม่อยู่ใน dependencies ของ package.json
HCTI_*, DALLE_ENDPOINT, GEN_BASE_URLใน .env แต่ไม่มี caller ใน .js

Impact: Docker ยัง build native libs ของ canvas โดยไม่จำเป็น; uuid อาจพังบน install ใหม่ถ้าไม่มี transitive


QUAL-AIRECRUIT-04 · saveFileToAPI hardcode ชื่อไฟล์

ที่: uploadController.js:6-7fileName = 'doctor', folder = 'Module2_2' เสมอ

Impact: ไฟล์ที่เซฟชนกันเชิงความหมาย; ไม่สะท้อนชนิดงานจริง


LEG-AIRECRUIT-01 · Node 18 base image

ที่: Dockerfile:1FROM node:18-alpine
sibling services ใช้ Node 20

Impact: ติดตาม EOL / CVE แยกเวอร์ชัน; ไม่สม่ำเสมอใน platform group


QUAL-AIRECRUIT-05 · Binary assets จำนวนมากใน git

ที่: storage-uploads/ ≈ 361 ไฟล์ (Background 29 + คลังตัวละคร)

Impact: clone ช้า, diff รก; ไม่ใช่ pattern object storage


SEC-AIRECRUIT-03 · Upload CORS ตั้ง origin เป็น * ได้

ที่: routes/index.js:91-106Access-Control-Allow-Origin: req.headers.origin || '*'
ขณะที่ global CORS จำกัด 3 origins (server.js:24-28)

Impact: นโยบาย CORS ไม่สม่ำเสมอ; browser จาก origin อื่นอาจ preflight ผ่านบน path นี้ (ยังต้องมี JWT)


ai-recruitment-api Health Scorecard

  • 🔴 แดง — มีปัญหาจริง ควรแก้
  • 🟡 เหลือง — ใช้ได้ แต่มีจุดอ่อน
  • 🟢 เขียว — ผ่านเกณฑ์สำหรับมิตินี้
Dimensionสถานะหมายเหตุ
Auth pipeline🔴verify signature อย่างเดียว; shared secret ข้าม 3 services; ไม่มี role
Persistence & SQL safety🟢parameterized queries; read-only; scope ตารางชัด
Secrets management🔴.env tracked รวม billable API keys
External integration resilience🟡fail-soft บางจุดดี; ไม่มี timeout/retry ชัดบน OpenAI; Puppeteer ต่อ request
Code duplication / dead code🔴orphan controllers/routes + export ชื่อไม่ตรง
Background jobs🟢ไม่มี cron — ไม่มีความเสี่ยงกลุ่มนี้
Logging & observability🟡มี logger.js แต่ปน console.*
Layering🟡แยก routes/controllers/services พอใช้; validation กระจาย; ไม่มี DTO layer
Test coverage🔴0 test files
Prompt / content governance🟡PROMPT_RULES hardcode; technical prompt น่าสงสัย

คะแนนรวม: 2 🟢 / 4 🟡 / 4 🔴


Modernization — ลำดับ ROI

  1. Rotate + ถอด secrets ออกจาก git (SEC-AIRECRUIT-01) — รวม OpenAI key
  2. เพิ่ม authorization claims + rate limit (SEC-AIRECRUIT-02)
  3. ลบ/mount dead code ให้ตรงความจริง (QUAL-AIRECRUIT-01)
  4. ยืนยัน sync jobReqPosition กับทีม data (CORR-AIRECRUIT-01)
  5. Browser pool + ลด fan-out AI (PERF-AIRECRUIT-01)
  6. Smoke tests สำหรับ route สำคัญ + mock OpenAI (OBS-AIRECRUIT-01)
  7. ย้าย storage-uploads ไป object storage (QUAL-AIRECRUIT-05)
  8. อัป Node 20 ให้สอดคล้อง sibling services (LEG-AIRECRUIT-01)

ตาราง debt ID ด่วน

IDSeverityหัวข้อสั้น
SEC-AIRECRUIT-01🔴secrets in git
SEC-AIRECRUIT-02🔴no-role JWT / shared secret
SEC-AIRECRUIT-03🟢upload CORS *
QUAL-AIRECRUIT-01🟡dead code / bad export
QUAL-AIRECRUIT-02🟢PROMPT_RULES ethnicity/age
QUAL-AIRECRUIT-03🟢unused / missing deps
QUAL-AIRECRUIT-04🟢hardcode save filename
QUAL-AIRECRUIT-05🟢binaries in git
CORR-AIRECRUIT-01🟡AI DB sync unknown
CORR-AIRECRUIT-02🟡technical prompt copy-paste
PERF-AIRECRUIT-01🟡puppeteer + AI fan-out
OBS-AIRECRUIT-01🟢no tests
OBS-AIRECRUIT-02🟢console vs logger
LEG-AIRECRUIT-01🟢Node 18

สิ่งที่ยืนยันแล้ว vs สมมติ

ยืนยัน

  • Express + global JWT no role
  • OpenAI / remove.bg / QR / Puppeteer call sites ทั้งหมดในตารางบท 02
  • Shared JWT hash กับ benefit + recruitment
  • Shared DB name/host hash กับ recruitment DB_AI_*
  • ไม่มี cron

ไม่ยืนยัน

  • ลำดับคลิกจริงใน UI back-office
  • ว่า production rotate คีย์ไปแล้วหรือยังหลัง commit .env
  • เจ้าของ ETL ของ recruitment_ai

กลับ index · Volume 6 overview: ../index