4 · Scorecard + debt register (V1 depth)
บทสรุปของ 01 Repository map, 02 Core pipeline, และ 03 Persistence & integrations สำหรับ 2way-api (branch: prod, 178 tracked files)
2way-api เป็น repo ที่มี security debt หนาแน่นที่สุดในกลุ่ม 2way Platform ตามที่ตรวจจากซอร์ส — แกนกลางคือ pattern เดียวกันซ้ำหลายที่: global JwtAuthGuard ถูกทำให้เป็น opt-in ด้วย @Public() บน mutation ส่วนใหญ่ พร้อม backdoor login, secret fallback ในซอร์ส, และ raw SQL string interpolation
อย่าคัดลอกค่า secret / password จริงลงเอกสารหรือ ticket — อ้างชื่อตัวแปรและ file:line
Severity legend
| Level | เกณฑ์ | Action |
|---|---|---|
| Critical | active security/data-loss/availability risk | แก้ก่อน feature ใหม่ / ปิด surface ทันที |
| High | correctness/auth hygiene hazard, real impact | วางแผนแก้ใน sprint ถัดไป |
| Medium | friction/hygiene | แก้ตามโอกาส |
| Low | cleanup / consistency | backlog |
Severity: Critical
SEC-2WAYAPI-01 · Backdoor login hardcode i3admin / password — ออก JWT admin 48h โดยไม่ผ่าน SSO
ที่: 2way-api/src/modules/auth/auth.service.ts (branch: prod):184-240
Route: POST .../auth/ เป็น @Public() — auth.controller.ts:13-28 (ดู 02 Trace C)
Evidence:
async signInNonEmp(body: AuthDto) {
const { username, password } = body;
if (username === 'i3admin' && password === 'aA123456!') {
const mockProfile = {
empCode: 'adm9999',
// ...
level: 1,
};
const mockToken = await jwt.sign(
{ id: 999999, profile: mockProfile, uid: '99999-ad-9999' },
process.env.SECRET_JWT_TWOWAY,
{ expiresIn: '48h' },
);
return { token: mockToken, success: true, statusCode: 2000, profile: mockProfile };
}
// ... SSO path ...
}ข้อเท็จจริงที่ตรวจแล้ว:
- Username/password ฝังในซอร์ส — ไม่มี feature flag /
NODE_ENVgate - ออก JWT จริงด้วย
SECRET_JWT_TWOWAY mockProfile.level = 1→ ใน announce list จะข้าม org/divi filter (ดู 02)- ไม่เรียก SSO และไม่เรียก
checkEmployeeCD
Exploit:
curl -X POST "https://<host>/2way-api/api/v1/auth/" \
-H "Content-Type: application/json" \
-d '{"username":"i3admin","password":"aA123456!"}'
# → ได้ Bearer 48h สิทธิ์เทียบ admin สำนักงาน i3gateway ใน mock profileImpact: ใครก็ตามที่อ่านซอร์ส (หรือเดารหัสจาก leak) ได้สิทธิ์ระดับสูงในระบบประกาศ/แบบสำรวจ/ร้องเรียน โดยไม่ต้องมีบัญชีพนักงาน — รวมถึงใช้ token เรียก endpoint ที่ยังบังคับ JWT อยู่
Remediation:
- ลบก้อน
if (username === 'i3admin' ...)ออกทั้งหมดจาก production branch - ถ้าต้องการบัญชีทดสอบ — ใส่เฉพาะ UAT ด้วย seed + secret ที่ rotate ได้ และ feature flag ปิดใน prod
- Audit log ทุก login สำเร็จ; alert เมื่อ
empCode === 'adm9999' - Rotate
SECRET_JWT_TWOWAYหลังปิด backdoor (token เก่าที่ออกจาก backdoor ยังใช้ได้จนหมดอายุ)
SEC-2WAYAPI-02 · SSO / crypto secrets มี default fallback ฝังในซอร์ส
ที่: 2way-api/src/modules/auth/auth.service.ts (branch: prod):20-26
Evidence:
private AES_SECRET_KEY = process.env.SSO_AES_SECRET_KEY || 'EoABvcD7RsFBeKRZgSOHS4LLD08To6Pc';
private AES_INIT_VECTOR = process.env.SSO_AES_INIT_VECTOR || 'AaNZRKzytx9yCAiA';
private SSO_END_POINT = process.env.SSO_END_POINT || 'https://uatssoapi.redcross.or.th/SSOService_dev/api';
private SSO_APP_ID = process.env.SSO_APP_ID || 'VTRCBO';
private SSO_REQUEST_ID = process.env.SSO_REQUEST_ID || 'SSO220607000000031';
private SSO_USERNAME = process.env.SSO_USERNAME || 'user';
private SSO_PASSWORD = process.env.SSO_PASSWORD || 'P@ssw0rd';เพิ่มเติม: 2way-api/src/utils/encrypt.util.ts:3 — process.env.NEXT_PUBLIC_ENCRYPT_SECRET || 'CHANGE_ME_PLS'
Exploit: Deploy ที่ลืมตั้ง env → ใช้ค่าที่ commit ใน git ทันที; attacker ที่อ่าน repo รู้ AES key/IV และ SSO basic-auth fallback
Impact: ถอดรหัส/ปลอม payload ไปทาง SSO; ยิง UAT SSO จาก production path; decrypt utility ใช้ secret อ่อน
Remediation: ลบ || 'literal' ทั้งหมด; boot fail-fast ถ้า env จำเป็นหาย; rotate ค่าที่เคยเป็น fallback; ห้าม default ชี้ *_dev SSO จากโค้ด prod
SEC-2WAYAPI-03 · SQL injection — string interpolation ใน raw SQL (announces / surveys / complaints)
2way-api/src/modules/announces/announces.service.ts (branch: prod):164-296,297-463— list/detail กรองด้วยempCode,orgCode,diviCode,typeGroup,identityCard,from,to,announceTypeต่อเข้า WHERE2way-api/src/modules/surveys/surveys.service.ts—getSummaryและจุดเช่น:430content_id = ${item.id}2way-api/src/modules/complaints/complaints.service.ts—getSummary/ count queries (~278+)2way-api/src/crontab/notification-pms/notification-pms.task.service.ts:77-84— UPDATE concat (cron ปิดอยู่ แต่โค้ดยังอยู่)
Evidence (รูปแบบ announce list — ดูรายละเอียด string ในบท 02):
// พารามิเตอร์จาก client ถูกต่อเข้า SQL string แล้วส่ง
await this.entityManager.query(strQuery);Exploit (แนวคิด):
curl -G "https://<host>/2way-api/api/v1/announces" \
--data-urlencode "empCode=1' OR 1=1--" \
--data-urlencode "isMobile=true"
# route เป็น @Public() → ไม่ต้องมี JWT ก็เริ่มฉีดได้ถ้าเงื่อนไข soft-check ผ่านImpact: อ่าน/บิดเบือนข้อมูลประกาศ แบบสำรวจ เรื่องร้องเรียน; ในกรณีรุนแรงขยายสิทธิ์ SQL ตาม login ของ DB user sa/app user
Remediation:
- ทุกจุดใช้
query(sql, [params])หรือ QueryBuilder.setParameter() - Cast/validate ประเภทก่อนเข้า SQL (ตัวเลข/วันที่)
- Sweep:
rg "entityManager\\.query\\(\" แล้วปิดทุกจุดที่ยังมี${ - แม้ PMS cron ปิด — แก้ UPDATE concat ก่อนเปิด
@Cronใหม่
SEC-2WAYAPI-04 · Auth bypass ทั่วระบบผ่าน @Public() บน mutation + LINE push
ที่ (ตัวอย่างที่ยืนยันแล้ว — ไม่ใช่รายการครบทุกบรรทัด):
- Announces create/update:
announces.controller.ts:45,56,79,85 - Complaints ทั้ง state machine:
complaints.controller.ts:13-114 - LINE push/multicast:
line.controller.ts:13-34 - Employees proxy ส่วนใหญ่:
employees.controller.ts(ยกเว้นบาง GET ที่ comment@Publicออก) - Auth ทั้งก้อน:
auth.controller.ts:13-28 - Know-content / assign paths ที่เปิด public (ดู 02 Trace B)
Evidence — guard:
// libs/jwt/jwt-auth.guard.ts:15-24
if (isPublic) {
return true;
}
return super.canActivate(context);เมื่อ @Public() ติดอยู่ การมี/ไม่มี Bearer ไม่สำคัญ ที่ชั้น guard — service บางตัวมี soft check เอง (เช่น announce list ดู isMobile) แต่ mutation จำนวนมากรับ empCode จาก body โดยตรงโดยไม่เทียบกับ token
Exploit:
# สร้าง/เปลี่ยนสถานะเรื่องร้องเรียนโดยไม่มี token
curl -X POST "https://<host>/2way-api/api/v1/complaints/adminAssign" \
-H "Content-Type: application/json" \
-d '{"empCode":"victim","contentId":123,...}'
# สั่ง LINE multicast
curl -X POST "https://<host>/2way-api/api/v1/Line/multiCast" \
-H "Content-Type: application/json" \
-d '{"to":["Uxxxx"],"messages":"..."}'Impact:
- ปลอมแปลง workflow ร้องเรียนทั้งสาย (assign / approve / reject)
- สร้าง/แก้ประกาศในนามคนอื่น
- Spam / phishing ผ่านช่องทาง LINE ขององค์กร
- อ่านข้อมูลพนักงานผ่าน centralize proxy ที่ public
Remediation:
- Inventory ทุก
@Public()เทียบ threat model — default-deny - Mutation ต้อง JWT + ownership/role check (teamAdmin / level)
- LINE controller ต้อง service-to-service secret หรือ internal network only
- เปิด EmpCodeMiddleware ที่ถูก comment ใน
employees.controller.ts:33-49กลับมาหลังทดสอบ
Severity: High
SEC-2WAYAPI-05 · verifyJwt() ตั้ง ignoreExpiration: true เสมอ
ที่: 2way-api/src/modules/auth/auth.service.ts (branch: prod):31-40
ใช้ซ้ำใน signInRedirectByPass (~290) และ verifyToken (~339-357)
Evidence:
const decoded = await verify(token, secret, {
algorithms: ['HS256'],
ignoreExpiration: true,
});ตรงข้ามกับ JwtStrategy ที่ ignoreExpiration: false (libs/jwt/jwt.strategy.ts:13-14)
Exploit: เก็บ JWT เก่า (หรือ VTRC SECRET_KEY token ที่หมดอายุ) → POST /auth/redirectbypass หรือ /auth/verifyToken ยังผ่าน → ได้ two-way token ใหม่ 48h
Impact: session ที่ควรหมดอายุไม่หมด; ขยายหน้าต่างหลัง credential rotate ถ้ายังไม่ invalidate ฝั่ง server
Remediation: ignoreExpiration: false; จัดการ 401 เมื่อ TokenExpiredError; อย่าแลก token หมดอายุเป็น token ใหม่โดยไม่มี refresh policy ที่ชัด
SEC-2WAYAPI-06 · MSSQL encrypt: false
ที่: 2way-api/src/app.module.ts (branch: prod):39-42
Evidence:
extra: {
trustServerCertificate: true,
encrypt: false,
}Impact: traffic ระหว่าง app กับ DB (รวมเนื้อหาร้องเรียน / ประกาศ / empCode) ไม่บังคับ TLS ที่ driver layer
Remediation: encrypt: true; จัดการ cert; ลดการใช้ trustServerCertificate: true ใน production
SEC-2WAYAPI-07 · LINE service URL hardcode เป็น IP สาธารณะ
ที่: 2way-api/src/modules/line/line.service.ts (branch: prod):76,86
Evidence:
await this.axios.post('http://20.212.116.206:8200/api/v1/line/nextPage', data);
await this.axios.delete('http://20.212.116.206:8200/api/v1/line/previousPage', data);Impact: ผูก infra กับโค้ด; ใช้ HTTP ไม่ใช่ HTTPS; ถ้า IP เปลี่ยนระบบพัง; traffic ไป line-service ไม่เข้ารหัส; attacker ที่รู้ IP อาจยิงตรงถ้า firewall อ่อน
Remediation: LINE_SERVICE_URL จาก env; บังคับ HTTPS ภายใน; network allowlist
SEC-2WAYAPI-08 · EmpCodeMiddleware และ permission view ถูกปิด — employees เป็น open proxy
ที่: employees.controller.ts:33-49 (middleware next() อย่างเดียว), employees.service.ts:112-125 (call site comment)
Evidence: validateEmpCodeToken ที่อ่าน VM_PERMISSION_FOR_SEARCH_EMPLOYEE มีอยู่แต่ไม่ถูกเรียก — ดู 02 Trace E
Impact: endpoint พนักงานส่วนใหญ่ @Public() + ไม่เช็คสิทธิ์ค้นหา → เป็น proxy อ่านข้อมูลจาก NestJS centralize-api โดยไม่ต้องมี JWT
Remediation: เปิด middleware; ลบ @Public() ที่ไม่จำเป็น; บังคับ JWT + permission view
CORR-2WAYAPI-01 · Cron PMS มี SQL concat และ cross-schema join — handler ปิดแต่โค้ดพร้อมรันผิดพลาด
ที่: notification-pms.task.service.ts:19-23 (@Cron comment), :27-85 (SQL ยังอยู่)
Evidence: UPDATE ใช้ '${notifyCode}' และ empCode IN (${empSet.map(...)}) — ค่ามาจาก SELECT ก่อนหน้า (ไม่ใช่ HTTP ตรง) แต่เป็น pattern อันตราย; join pms.dbo.tbnotify ↔ [2way].dbo.userline
Impact: ตอนเปิด cron ในอนาคตโดยไม่แก้ — risk injection จากข้อมูลในตาราง + coupling ข้าม DB ที่ deploy ต้องมี linked schema
Remediation: parameterized UPDATE ก่อน uncomment @Cron; document dependency schema ใน runbook
Severity: Medium
QUAL-2WAYAPI-01 · โมดูลซ้ำไม่ได้ต่อเข้า AppModule (*-new, old_*)
ที่: announces-new, employees-new, surveys/old_surveys, complaints/old_complaints, crontab/*/old_* — ไม่ import ใน app.module.ts:1-19
Impact: dead code ทำให้ review สับสน; ถ้ามีคนเผลอ wire เข้ามา จะเปิด @Public() ทั้งชุดของ *-new ทันที
Remediation: ลบโฟลเดอร์หรือย้าย branch archive; ห้าม merge โดยไม่ audit @Public()
QUAL-2WAYAPI-02 · AppModule import ตัวเอง
ที่: 2way-api/src/app.module.ts (branch: prod):46
Impact: สื่อเจตนาผิด; Nest จัดการ circular ได้แต่เป็นกลิ่นโค้ด
Remediation: ลบ AppModule ออกจาก imports ของตัวเอง
QUAL-2WAYAPI-03 · console.log ปน I3GatewayLogger รวม PII / query
ที่: ตัวอย่าง announces.service.ts:56,94,112,289; auth.service.ts:246 (console.log('sso', sso)); cron tasks
Impact: PII ใน container logs; ไม่มีระดับ log มาตรฐาน
Remediation: ห้าม console.log ด้วย lint; mask token/profile
QUAL-2WAYAPI-04 · Node engine ไม่ตรง Dockerfile
ที่: package.json:27 (engines >=18) vs Dockerfile.prod:1 (node:16-alpine)
Impact: พฤติกรรม/security patch ของ Node ต่างกันระหว่าง local กับ image
Remediation: รวมเวอร์ชันทุก stage + .nvmrc
QUAL-2WAYAPI-05 · Package manager ไม่สอดคล้อง (yarn.lock + package-lock-prod.json)
ที่: มีทั้งสองไฟล์ใน repo — workspace convention ระบุ npm สำหรับกลุ่มนี้ แต่ CI อาจใช้ yarn
Impact: dependency version เพี้ยนระหว่าง environment
Remediation: เลือกตัวเดียว; ลบ lockfile อีกฝั่ง; ล็อกใน pipeline
QUAL-2WAYAPI-06 · ScheduleModule ถูก forRoot() หลายครั้งในแต่ละ task module
ที่: announce.task.module.ts, survey.task.module.ts, complaint.task.module.ts, notification-pms.task.module.ts — แต่ละตัว imports: [ScheduleModule.forRoot(), ...]
Impact: pattern ที่ Nest เตือนได้ — ควร forRoot() ครั้งเดียวที่ AppModule
Remediation: ย้าย ScheduleModule.forRoot() ขึ้น root; task modules import ScheduleModule อย่างเดียวถ้าจำเป็น
OBS-2WAYAPI-01 · TypeORM logging: ['error','query'] ใน production path
ที่: app.module.ts:37
Impact: SQL + พารามิเตอร์เข้า log — เนื้อหาร้องเรียนอาจเป็นข้อมูลอ่อนไหว
Remediation: prod = ['error'] เท่านั้น
OBS-2WAYAPI-02 · CORS origin: '*' + Swagger เปิด + body 50mb
ที่: main.ts:30-42 (ดู 01 boot sequence)
Impact: ขยาย attack surface เมื่อรวมกับ @Public() mutations
Remediation: allowlist origin; ปิด docs ใน prod; rate limit ที่ gateway
QUAL-2WAYAPI-07 · Soft Bearer check บน announce/survey list ไม่ใช่ authorization จริง
ที่: เงื่อนไข isMobile / optional Authorization ใน service (02 Trace A)
Impact: ผู้พัฒนาเข้าใจผิดว่า "มีเช็ค token" ทั้งที่ route เป็น @Public() และ mobile path ข้ามการเช็ค
Remediation: แยก public catalog endpoint กับ admin list ที่บังคับ JWT ชัดเจน
Severity: Low
QUAL-2WAYAPI-08 · Package name vtrc-restfull-v2 ไม่ตรงชื่อ repo
ที่: 2way-api/package.json:2
Impact: สับสนใน monorepo / artifact
Remediation: rename หรือใส่ description ชัด
QUAL-2WAYAPI-09 · endpoint.txt มือเขียนอาจ stale
ที่: root endpoint.txt
Impact: onboarding อ่าน route ผิด
Remediation: generate จาก Swagger หรือลบแล้วชี้ไป Volume นี้
QUAL-2WAYAPI-10 · Dual centralize callers (GraphQL cu-central vs REST centralize-api)
ที่: Auth ใช้ GraphQL CENTRALIZE_API_URL; Employees ใช้ REST CENTRALIZE_SERVER — 03
Impact: debt การตั้งชื่อ "centralize" สับสน; debug ยากว่าเรียกระบบไหน
Remediation: ตั้งชื่อ env ให้สื่อระบบจริง (CU_CENTRAL_GRAPHQL_URL / CENTRALIZE_REST_URL)
Finding index
| ID | Sev | หัวข้อสั้น |
|---|---|---|
| SEC-2WAYAPI-01 | Critical | Backdoor i3admin |
| SEC-2WAYAPI-02 | Critical | Secret/AES/SSO fallback ในซอร์ส |
| SEC-2WAYAPI-03 | Critical | SQL injection raw string |
| SEC-2WAYAPI-04 | Critical | @Public() บน mutation / LINE |
| SEC-2WAYAPI-05 | High | ignoreExpiration: true |
| SEC-2WAYAPI-06 | High | DB encrypt: false |
| SEC-2WAYAPI-07 | High | LINE IP hardcode HTTP |
| SEC-2WAYAPI-08 | High | Employees permission ปิด |
| CORR-2WAYAPI-01 | High | PMS cron SQL concat (dormant) |
| QUAL-2WAYAPI-01 | Medium | Dead *-new / old_* modules |
| QUAL-2WAYAPI-02 | Medium | AppModule self-import |
| QUAL-2WAYAPI-03 | Medium | console.log / PII |
| QUAL-2WAYAPI-04 | Medium | Node 18 vs image 16 |
| QUAL-2WAYAPI-05 | Medium | yarn vs npm lockfiles |
| QUAL-2WAYAPI-06 | Medium | ScheduleModule.forRoot ซ้ำ |
| OBS-2WAYAPI-01 | Medium | query logging |
| OBS-2WAYAPI-02 | Medium | CORS/Swagger/50mb |
| QUAL-2WAYAPI-07 | Medium | soft auth หลอกตา |
| QUAL-2WAYAPI-08..10 | Low | naming / docs / dual centralize |
2way-api Health Scorecard
| Dimension | สถานะ | หมายเหตุ |
|---|---|---|
| Auth pipeline | แดง | Backdoor + @Public ทั่ว + ignoreExpiration บน verify path |
| Persistence & SQL safety | แดง | Raw SQL concat หลายโมดูล; encrypt:false |
| Secrets management | แดง | Fallback secrets ในซอร์ส; hardcode backdoor password |
| External integrations | เหลือง | SSO + dual centralize + LINE; IP hardcode |
| Public surface control | แดง | Mutation และ LINE เปิดโดยไม่ต้อง JWT |
| Background jobs | เหลือง | 3 cron 07:00 ทำงาน; PMS ปิดแต่โค้ดเสี่ยง |
| Logging & observability | เหลือง | console.log + query log |
| Layering / dead code | เหลือง | *-new / old_* ค้าง; AppModule self-import |
| Build & tooling | เหลือง | Node version + dual lockfile |
| Test coverage | เหลือง | มี test/ แต่ไม่ได้ใช้เป็นเกตปิด Critical ในรอบนี้ |
คะแนนรวมมิติ: 0 เขียว / 6 เหลือง / 4 แดง
Cron jobs — สรุปสั้นสำหรับ scorecard
| Job | Schedule | File | สถานะ |
|---|---|---|---|
| Announce offer-date LINE notify | EVERY_DAY_AT_7AM | announce.task.service.ts:19-22 | Active |
| Survey notify | EVERY_DAY_AT_7AM | survey.task.service.ts:21-25 | Active |
| Complaint notify | EVERY_DAY_AT_7AM | complaint.task.service.ts:21-22 | Active |
| PMS LINE notify | (comment) | notification-pms.task.service.ts:19-23 | Inactive — โค้ด SQL ยังอยู่ |
รายละเอียด pipeline: 02 Core pipeline — Cron
ความเสี่ยง cron ที่ active: พึ่ง raw SQL + LineService; ไม่ผ่าน JwtAuthGuard (ถูกต้องสำหรับ in-process) แต่ถ้า SQL ผิดพลาดจะส่งแจ้งเตือนผิดกลุ่มได้ — ต้อง monitor จาก log 07:00
Modernization — ลำดับ ROI
- ลบ backdoor
i3admin(SEC-2WAYAPI-01) + rotateSECRET_JWT_TWOWAY - ลบ secret fallback (SEC-2WAYAPI-02) + fail-fast boot
- ปิด
@Public()บน complaints / LINE / announce mutations (SEC-2WAYAPI-04) — คุย frontend/LINE client ก่อนตัด - Parameterized SQL ทั้ง announces/surveys/complaints (SEC-2WAYAPI-03)
ignoreExpiration: false(SEC-2WAYAPI-05)- เปิด employees permission ใหม่ (SEC-2WAYAPI-08)
- LINE URL จาก env + HTTPS (SEC-2WAYAPI-07)
- DB encrypt + ลด query logging (SEC-2WAYAPI-06 / OBS-01)
- ลบ dead modules + แก้ ScheduleModule.forRoot (QUAL-01/06)
- รวม Node + package manager (QUAL-04/05)
สิ่งที่ตรวจแล้ว vs ยังไม่ตรวจ
| ตรวจแล้วจากซอร์ส (branch prod) | UNVERIFIED / นอกขอบเขตรอบนี้ |
|---|---|
| Backdoor login ทั้งก้อน | ว่า production deploy ปัจจุบันยังเป็น commit ชุดนี้หรือไม่ |
@Public() บน complaints/line/announces | network ACL / API gateway บังคับ auth ด้านนอกหรือไม่ |
| SQL concat ใน announce list | ผล pentest จริงบน UAT |
ignoreExpiration: true บน AuthService | ว่า SECRET_KEY (VTRC) กับ SECRET_JWT_TWOWAY หมุนอย่างไรใน ops |
| 3 cron 07:00 active + PMS ปิด | timezone ของ container = Asia/Bangkok หรือไม่ |
| Dual centralize callers | ownership ทีมของแต่ละ centralize |
สมมติฐานที่ระบุไว้: ถ้ามี WAF/gateway บังคับ JWT ด้านหน้า ความรุนแรงของ SEC-2WAYAPI-04 อาจลดในทางปฏิบัติ — แต่โค้ดยังเป็น auth bypass และต้องถือว่า Critical จนกว่าจะมีหลักฐาน control ภายนอก
ตัวอย่าง remediation แบบ parameterized (อ้างอิง)
// แทนที่ string concat
const rows = await this.entityManager.query(
`SELECT id, topic_name FROM t_content
WHERE emp_code = @0 AND offer_date >= @1 AND offer_date <= @2`,
[empCode, fromDate, toDate],
);// Auth boot — ห้าม fallback
const secret = process.env.SSO_AES_SECRET_KEY;
if (!secret) {
throw new Error('SSO_AES_SECRET_KEY is required');
}// ปิด backdoor — ลบทั้ง if-block; อย่า comment ไว้ใน prodCross-links
- 01 Repository map — modules, boot, crontab folders
- 02 Core pipeline — traces A–G + cron
- 03 Persistence & integrations — MSSQL, LINE, dual centralize, PMS join
ไป กลับสู่ index หรือ ดูภาพรวม Volume 5