Skip to content

recruitment-web — Scorecard

Legend:

  • Critical = active security/data-loss/availability risk
  • High = correctness/maintainability hazard, real impact
  • Medium = friction/hygiene, ยังไม่เป็นอันตราย

repo นี้ (branch: prod) รับข้อมูลส่วนบุคคลของผู้สมัครงานจริงจำนวนมาก (เลขบัตรประชาชน, ที่อยู่, เบอร์โทร, เงินเดือน ฯลฯ) และมีปัญหาการจัดการ secret/PII หลายจุดที่กระทบกันเป็นลูกโซ่


Scorecard table

IDSeverityคำอธิบายหลักฐาน (file:line)การแก้ไข
SEC-RCWEB-01Critical.env, .env.dev, .env.uat ถูก track ใน git และมีค่า REACT_APP_SECRET_KEY / REACT_APP_PRIVATE_KEY จริง — คีย์ใช้ AES ฝั่ง client (utils/cryptoJs.js) CRA bake REACT_APP_* เข้า JS bundle ดังนั้นคีย์เปิดเผยทั้งใน git history และใน bundle ที่ดาวน์โหลดจากเบราว์เซอร์ได้ การ "เข้ารหัส" จึงไม่ปกป้องจากผู้ที่อ่าน bundle/repo ได้ Dockerfile stage-2 ยังฝัง REACT_APP_SECRET_KEY อีกชุด (Dockerfile:15) ซึ่งไม่มีผลต่อ bundle แต่เพิ่มพื้นผิวการรั่ว.env (branch: prod), .env.uat, src/utils/cryptoJs.js:1-32, Dockerfile:15ย้าย encrypt/decrypt PII ไปฝั่ง recruitment-api เท่านั้น; หมุนคีย์ที่รั่วทั้งหมด; ลบ .env* ที่มี secret ออกจาก git history; อย่าใส่ secret ใน Dockerfile
SEC-RCWEB-02HighPII ที่ decrypt แล้ว (idCard, ที่อยู่, โทร, วันเกิด, เงินเดือน) เก็บใน localStorage key user-login โดยไม่มีวันหมดอายุ — expiry ใน setStorageWithExpity / getStorageWithExpiry ถูก comment ปิดทั้งสองด้านsrc/utils/localStorage.js:1-28 (บรรทัด 6-7, 19-26)เปิด expiry จริง หรือย้าย PII ที่ decrypt แล้วไป in-memory (Recoil) ลดพื้นผิว XSS/shared device
SEC-RCWEB-03HighhandleLogin ข้าม backend OTP และใช้ OTP คงที่ 111111 เมื่อ REACT_APP_NODE_ENV === 'dev' — เป็น runtime check ใน production bundle ได้ถ้า build ผิด envsrc/components/header/index.js:230-231ลบ bypass จาก client; mock OTP ที่ backend/mock server เท่านั้น
SEC-RCWEB-04Mediumอีเมลและ OTP ส่งผ่าน query string ของ GET (validate_otp) และอีเมลผ่าน logout query — ถูกบันทึกใน access log / browser historysrc/pages/login/form-otp.js:51-52, src/components/header/index.js:74,232,266เปลี่ยนเป็น POST + body สำหรับข้อมูลระบุตัวตน/OTP
CORR-RCWEB-01MediumDockerfile ตั้ง ENV REACT_APP_* ที่ stage-2 (nginx) แต่ CRA inline ค่าตอน stage-1 build เท่านั้น — ค่า stage-2 ไม่มีผล ทำให้เข้าใจผิดว่า config มาจากไหนDockerfile:1-21ลบ ENV ที่ stage-2 หรือใช้ ARG ที่ stage-1 ให้ตรงพฤติกรรม CRA
QUAL-RCWEB-01Mediumsrc/api/login.js + src/api/index.js เป็น dead code — ไม่มี import จาก UI; ใช้ axios client คนละตัวและ endpoint คนละชุดsrc/api/login.js:1-53, src/api/index.js:1-6ลบ src/api/ และ src/utils/axios.js หลังยืนยันไม่มีแผนใช้
QUAL-RCWEB-02Mediumutils/axios.js ตั้งชื่อพารามิเตอร์ interceptor ผิด (response ทั้งที่เป็น request config), ตั้ง Authorization: null และ Access-Control-Allow-Origin: '*' เป็น request header ฝั่ง client (ไม่มีผลกับ CORS) — path นี้เป็น dead codesrc/utils/axios.js:8-15ลบพร้อม QUAL-RCWEB-01
QUAL-RCWEB-03Mediumtypo env REACT_APP_RECAPTCHA_SITE_KET (ควรเป็น KEY) สอดคล้องกันทั้ง .env*, และจุดอ่านใน content.js — แก้จุดเดียวจะพัง.env (key name), content.js:1355แก้ typo พร้อมกันทุกจุดแล้ว deploy พร้อมกัน
QUAL-RCWEB-04Mediumlogic decrypt PII ~25+ ฟิลด์ซ้ำใน header/index.js และ content.js — เพิ่ม field ต้องแก้ 2 จุดheader/index.js:104-148, content.js:492-537ดึงเป็น decryptApplicantProfile(data) shared utility
LEG-RCWEB-01Mediumไฟล์แนบแปลงเป็น base64 ฝัง JSON แทน multipart; ตรวจ type/ขนาด 2MB เฉพาะ client ในสโคป repo นี้uploadForm.js:22-30, content.js:654-668,677-696ตรวจซ้ำที่ recruitment-api (task แยก — UNVERIFIED จากมุม frontend)

สรุปลำดับแก้

  1. SEC-RCWEB-01 — secret ใน git + client AES (กระทบความน่าเชื่อถือของการเข้ารหัสทั้งระบบ)
  2. SEC-RCWEB-02 — PII ค้างใน localStorage
  3. SEC-RCWEB-03 — OTP bypass ใน bundle
  4. SEC-RCWEB-04 — OTP ใน GET query
  5. hygiene: CORR / QUAL / LEG ตามตาราง

สิ่งที่ตรวจแล้วว่าไม่ใช่ปัญหาคนละชนิด

หัวข้อสรุปCitation
Refresh tokenมี interceptor จริงบน 401/403axios-rc.js:52-72
Route gateมี soft gate สำหรับ MyJob / ApplicantProfileApp.js:49-62
GAinitialize ด้วย env GA_ID; mute console นอก UAT/localApp.js:29-34
nginx SPAtry_files ถูกต้องสำหรับ CRAdefault.conf:7-11

Cross-ref