Handoff — Master Plan fact-check รอบ 2
สถานะรอบ fact-check ที่ 2 ของ master plan
อัปเดต: 2026-07-02
Handoff — Security Master Plan: รอบ fact-check ที่ 2 ยังไม่ได้ apply
บันทึกไว้เมื่อ: 2 กรกฎาคม 2026 · เอกสารเป้าหมาย: SECURITY_MASTER_PLAN_DEFENSE_ROADMAP.md (แผนแม่บทความปลอดภัย SuperApp)
สถานะ: มี fact-check review ผ่านมาแล้ว 2 รอบ — รอบแรก apply เสร็จแล้ว (6 จุด) รอบสอง ยังไม่ได้ apply เลยสักจุด (13 confirmed findings + 5 nits) นี่คืองานที่ค้างอยู่
บริบท: ทำไมมี 2 รอบ
- Session ก่อนหน้า (ครบัง crash ด้วย API error ระหว่างเรียก
advisor()—tool_use_idเสียใน transcript) เขียนแผนแม่บทเสร็จ + ยิง review workflow รอบแรก (7 agents: 3 reviewer มุมมองต่างกัน + adversarial verify ต่อ finding) ผลออกมาแล้วตอน session crash แต่ยังไม่ได้อ่าน - Session นี้กู้ผล workflow รอบแรกจาก output file, apply ครบ 6 จุด (F1-F6), อัปเดต status บรรทัด 7 ของเอกสารเป็น “ผ่าน fact-check review แล้ว”
- User ขอให้
advisor()review อีกรอบ — advisor tool unavailable ตลอด session นี้ (ลองซ้ำ 2 ครั้ง ทั้งก่อนและหลังเปลี่ยน model เป็น opus/sonnet) จึงใช้ Workflow tool สร้าง multi-agent re-review แทน (4 reviewer: fix-verify, fresh-critic, code-facts, sec-advisor ใช้ agent typesecurity-advisor) พร้อม adversarial verify ต่อ finding เหมือนรอบแรก - Workflow รอบสอง (task id
w660vncc0) เสร็จแล้ว: 13 confirmed findings (7 must_fix, 6 should_fix) + 5 nits — ยังไม่ได้ apply แม้แต่จุดเดียวตอน session ถูกขัดจังหวะ (user เปลี่ยน model 2 ครั้งแล้วพิมพ์ “handoff”)
หลักฐานเต็ม (ทุก finding มี file
+ verifyReason ละเอียด):C:\Users\SUPAKO~1\AppData\Local\Temp\claude\c--Source-Private-Atlas-docs\f5f43cb5-b403-4d94-9455-e4ce71afcbdf\tasks\w660vncc0.output
(เป็น temp file — อาจถูกลบโดย OS ได้ สรุปย่อทุกจุดอยู่ด้านล่างนี้แล้ว ถ้าไฟล์หายให้ใช้สรุปย่อแทนได้เลย ไม่กระทบการทำงานต่อ)
งานที่ต้อง apply ต่อ (13 confirmed — เรียงตาม severity)
🔴 must_fix (7 จุด)
M1. Backend_WorkflowService หายจาก inventory bypass ทั้งฉบับ — bypass ร้ายแรงกว่าที่รายงาน
- ตำแหน่งที่ต้องแก้: §0 ตาราง #2 (บรรทัด 33), §3.1 #2 (บรรทัด 193-201), §5 Phase 1 (บรรทัด 336)
- ปัญหา:
WorkFlow01.API/Program.cs:73-81ลงทะเบียนBypassAuthHandlerเป็น auth scheme เดียวในทุก env ที่ไม่ใช่ Production (guard เดียว = throw ถ้าIsProduction()) — ไม่ผูกกับASPNETCORE_ENVIRONMENT=Developmentเหมือน 9 service อื่น ดังนั้น hedge ที่เอกสารเพิ่งใส่ไป (“Critical ก็ต่อเมื่อ env=Development”) ใช้ไม่ได้กับ WF — WF bypass ติดทั้ง dev/sit/uat เสมอBypassAuthHandlerreturn success เสมอพร้อม claimRole=Adminโดยไม่ดู token เลย (แรงกว่า if-isDev เดิมที่อย่างน้อยยังต้องส่ง JWT-shaped token) - แก้: เพิ่ม WorkflowService เข้า inventory ทั้ง 3 จุด, ระบุว่าเป็น bypass คนละ class (scheme-replacement ไม่ใช่
if(isDev)), ตัดประโยค “UserService เป็นตัวเดียวที่ปิด”, แก้ Phase 1 verify criterion (grep if(isDev) = 0ไม่จับ WF เพราะ WF ไม่มี pattern นี้อยู่แล้ว — ต้องมี verify แยกสำหรับ WF)
M2. §3.1 #3 (FallbackPolicy) เหมารวม endpoint ที่ตั้งใจเปิด anonymous เข้ากับ “ลืมติด [Authorize]”
- ตำแหน่ง: §0 บรรทัด 34, §3.1 #3 (บรรทัด 206-213), §4.2 Step 5 (บรรทัด 295), §5 Phase 1 (บรรทัด 334)
- ปัญหา: ThirdParty controllers (CustomerController, EGovTokenController, FileManagementController ฯลฯ) ติด
[AllowAnonymous]explicit ระดับ class พร้อม write endpoints จริง — FallbackPolicy ช่วยอะไรไม่ได้เพราะ[AllowAnonymous]override เสมอ (พฤติกรรมมาตรฐาน ASP.NET Core) แต่ roadmap ไม่มี task ไหนสั่งถอด/แก้ endpoint พวกนี้เลย — Phase 1 verify คือ “ทุก controller มี[Authorize]หรือ[AllowAnonymous]ชัดเจน” ซึ่ง ThirdParty ผ่านอยู่แล้วโดยไม่ต้องแก้อะไร ทั้งที่ยังเปิด anonymous 100% - แก้: แยกเป็น 2 เรื่อง — (a) ไม่มี FallbackPolicy (เคส “ลืมติด” เช่น ConsentController) (b)
[AllowAnonymous]explicit บน write endpoint ที่ต้อง audit รายตัวแล้วเปลี่ยนเป็น[Authorize]/[RequireApiKey]— เพิ่มงาน + verify per-endpoint (เรียกไม่มี credential ต้องได้ 401/403) เข้า Phase 1
M3. ConsentService rate-limit pattern ที่แผนสั่งให้ลอกเป็น non-partitioned — self-DoS risk
- ตำแหน่ง: §3.1 #5 / §5 Phase 1 (rate-limit helper)
- ปัญหา:
AddFixedWindowLimiter("GlobalPolicy", 100/min)ของ ConsentService เป็น counter เดียวแชร์ทุก client ทั้ง service ไม่ใช่ per-client — ถ้า helper กลางลอก pattern นี้ตรงๆ: client เดียวยิง 100 req/min ก็กินโควตาหมด → user จริงทุกคนโดน 429 (self-DoS ง่ายกว่าเดิม) และเมื่อผสมกับ penalty layer (นับ 429 ต่อ identity) ผู้ใช้บริสุทธิ์ที่โดน 429 จาก global window จะถูกสะสมจน ban เอง - แก้: helper กลางต้องใช้ partitioned limiter (partition key = user sub → API key → client IP จาก forwarded header) ไม่ใช่
AddFixedWindowLimiterตัวเดียวแชร์ทั้ง service — อย่าอ้าง Consent GlobalPolicy เป็น pattern ต้นแบบ, ระบุให้ exclude/healthจาก limiter ด้วย
M4. RateLimitPenaltyMiddleware ไม่มี enforcement จริง — PenaltyBox.IsBanned ไม่มี caller ที่ไหนเลย
- ตำแหน่ง: §3.1 #5 (คำอธิบายที่เพิ่งแก้ไปว่าเป็น “ban-escalation layer”)
- ปัญหา: grep ทั้ง platform พบ
PenaltyBox.IsBannedมีแค่ definition ไม่มีที่ไหนเรียกใช้ — wire middleware นี้แล้วได้ 0 การป้องกันจริง (ban ลง in-memory dict ที่ไม่มีใครอ่าน) ซ้ำIdentityResolverเชื่อ headerX-Demo-Userที่ client ปลอมได้ (spoof เพื่อ ban เหยื่อ) และ fallback เป็นConnection.RemoteIpAddressซึ่งหลัง APIM/ingress คือ IP เดียวกันหมด (ban ครั้งเดียว = anonymous ทั้ง platform โดน); state เป็น per-pod in-memory ไม่ consistent บน AKS หลาย replica - แก้: อย่าใส่ RateLimitPenaltyMiddleware ใน helper กลางเวอร์ชันแรก — primary
AddRateLimiter(partitioned ตาม M3) อย่างเดียวพอ ถ้าจะใช้ escalation จริงต้องเพิ่ม (1) middleware เช็คIsBannedก่อนnext()(2) ตัดX-Demo-Userออกจาก IdentityResolver (3) ใช้ IP จาก forwarded headers ที่ผ่านUseForwardedHeaders+KnownNetworksแล้วเท่านั้น (4) เก็บ state ใน Redis ไม่ใช่ in-memory
M5. DGA ConsumerSecret เป็น external API contract — ย้ายฝ่ายเดียวจะพัง token acquisition
- ตำแหน่ง: §3.1 #4 / Phase 0 (ย้าย ConsumerSecret ไป header/body)
- ปัญหา: URL ที่มี
?ConsumerSecret=เป็น request ขาออกไป DGA (api.egov.go.th/ws/auth/validate) — contract เป็นของ DGA ไม่ใช่ของเรา (ยืนยันจาก DGA API spec: query param เป็นข้อบังคับ ไม่มีทางเลือก POST body) ทำตามแผน “ย้ายไป header/body” ฝ่ายเดียว = token acquisition พังทันที ช่องรั่วจริงที่คุมได้คือฝั่ง log ของเราเอง — แต่ตรวจแล้วพบว่า .NET 9+ HttpClientFactory redact query string เป็น default อยู่แล้ว (ThirdParty target net10.0, ไม่มี opt-out) และโค้ดเรา log แค่ AuthUrl ไม่มี query อยู่แล้ว ดังนั้นความเสี่ยงที่เหลือคือ access log ฝั่ง DGA/TLS-inspecting proxy เท่านั้น ไม่ใช่ log ของเรา - บันทึกเพิ่มจาก verifier: ระหว่างตรวจพบ ConsumerKey/ConsumerSecret ค่า prod hardcode อยู่ใน
appsettings.Production.json:9-11ของ ThirdParty — ควรรวม scope นี้เข้ากับ Finding #1 (hardcoded credential) ด้วย - แก้: เปลี่ยน task เป็น (1) ยืนยัน DGA spec ว่า query param บังคับจริง (2) mitigation ที่คุมได้คือ Serilog override category
System.Net.Http.HttpClientให้ ≥Warning + ยืนยัน TLS-only/egress proxy ไม่เก็บ URL ไม่ใช่ “ย้ายไป header” (3) เพิ่ม ThirdParty prod hardcoded secret เข้า scope ของ Finding #1
M6. ApiKeyAuthFilter ไม่ได้ wire เข้า ConsentService — attribute จะเงียบสนิท
- ตำแหน่ง: §4.2 Step 2 (pattern
[AllowAnonymous]+[RequireApiKey]) - ปัญหา:
ApiKeyAuthFilterenforce ก็ต่อเมื่อ service registeroptions.Filters.Add<ApiKeyAuthFilter>()เข้า MVC — ยืนยันแล้วว่า ConsentService เรียกAddApiKeyAuthแต่ไม่เคย add filter เข้าAddControllers(grep ทั้ง repo ไม่พบ) ในขณะที่ FilterService/Codex/Centralized/FileManagement/Log/Notification/Orchestrator/Template/Task/UserService register ครบ — ติด[AllowAnonymous]+[RequireApiKey]บน Consent วันนี้ = attribute ไม่มีผลอะไร endpoint เปิด anonymous เต็มรูปแบบ (Consent คือ service เป้าหมายของแผนพอดี —POST /receipt) - แก้: เพิ่ม prerequisite ใน Step 2 — ก่อนใช้ pattern นี้บน service ใดต้อง verify (1)
Filters.Add<ApiKeyAuthFilter>()อยู่ในAddControllersจริง (2)ApiKeyAuth:ApiKeysมีค่าใน config/KV ของ env นั้น (dict ว่าง = ปฏิเสธทุก key = fail-closed พังของจริง) — แนะนำให้ helper กลาง register filter อัตโนมัติ + fail-fast ตอน startup ถ้า ApiKeys ว่าง
M7. Backend_ThirdPartyFXService ไม่มี authentication เลยทั้ง repo — ไม่อยู่ใน inventory ของแผน
- ตำแหน่ง: §4.2 Step 1-4 + §4.3 (FallbackPolicy rollout “ทุก service”)
- ปัญหา: grep
AddAuthentication|AddAuthorization|AddJwtBearer|FallbackPolicyทั้ง repo = 0 hit, ไม่มี[Authorize]แม้แต่จุดเดียว,MapHub<FxRateHub>("/hubs/fx-rate")เปิด anonymous — เปิด FallbackPolicy ที่นี่โดยไม่มี auth scheme ลงทะเบียนก่อนจะได้InvalidOperationException(“No authenticationScheme was specified…”) = 500 ทุก request ไม่ใช่ 401 (คนละ failure mode จาก service อื่น) - แก้: เพิ่ม Step 0 ใน §4.2 — ตรวจว่า service มี JWT authentication ลงทะเบียนจริงก่อนเข้า rollout (
AddAppAuthentication) — service ที่ยังไม่มี (ThirdPartyFX, และ Workflow ด้วยเหตุผลคนละแบบ = “bypass ไม่ใช่ validation จริง” ไม่ใช่ “จะ 500”) ต้อง port auth ก่อน — เพิ่ม ThirdPartyFXService เข้า inventory ของแผน
🟡 should_fix (6 จุด)
S1. §3.1 #3 bullet วิธีแก้ (บรรทัด 213) ยังเป็น wording เก่าที่ขัดกับ §4.2 Step 2 ที่แก้แล้ว (health probe ติด attribute ไม่ได้ + ไม่มีหมวด API-key ใน inventory summary) → ปรับให้สอดคล้อง §4.2 ใหม่
S2. Over-claim ทั้งฉบับ — §3 intro (บรรทัด 183) + footer (บรรทัด 394) เขียน “ทุกข้อยืนยันจากโค้ดจริง” แต่มีอย่างน้อย 5 ข้อในเนื้อหาที่เอกสารเองบอกว่า “ต้อง verify แยก/ควร confirm” (Notification auth pattern, ShowPII scope, Swagger SIT/UAT, Sentinel InternalOnly IaC, RoleController) → เปลี่ยน claim เป็น “ข้อที่มี file
= ยืนยันแล้ว; ข้อ ⚠️/ควร confirm = ยังไม่ verified”S3. HMAC/integrity gap วางผิดหมวดเทียบ guideline จริง — อยู่ใต้ §2 หมวด 4 (Secure Communication) แต่ตาม API-GL-001 ข้อ 3.2 Data Integrity (ใต้ข้อ 7(3)) ต่างหาก ที่พูดเรื่อง message-level integrity/JWS ตรงๆ ทำให้ §2 หมวด 4 ให้สถานะ ✅ กลบ severity 🟠 High ที่ §0 ให้ไว้ → ย้าย gap ไปหมวด 3, ปรับสถานะ/คะแนนให้สอดคล้อง
S4. Phase 1 แถว “เปลี่ยนทุก if(isDev) bypass” มี verify แค่ syntactic (grep = 0) ไม่มี functional check — ปิด bypass = behavior change จริง (เปิด JWT validation เต็มที่ dev cluster ไม่เคย exercise มาก่อน เช่น AzureAd config ที่อาจขาด/ผิด) เสี่ยงพัง dev/sit แต่ verify ผ่านได้ทั้งที่พัง → เพิ่ม functional verify เหมือนแถว FallbackPolicy (ทีละ service + E2E เขียว + valid token 200 / no token 401)
S5. ASPNETCORE_ENVIRONMENT ที่เอกสารทิ้งเป็นคำถามเปิด (“ต้องยืนยันจาก IaC”) จริงๆ ตอบได้จาก repo เองแล้ว — Dockerfile.DEV ทุก service bake ENV ASPNETCORE_ENVIRONMENT=Development + IaC dev deployment manifests ตั้งค่าเดียวกัน explicit ครบ (ยกเว้น sentinel-gateway ที่ไม่ตั้งแต่ image ENV ชนะอยู่ดี) → ยกระดับ #2 เป็น confirmed-Critical สำหรับ dev cluster ได้เลย (ไม่ใช่ conditional อีกต่อไป) พร้อม note SIT/UAT ใช้ชื่อ env Sit/Uat จริง (bypass แบบ if-isDev ไม่ active ที่นั่น ยกเว้น Workflow ที่ bypass ทุก non-Production)
S6. (ซ้ำเนื้อหากับ M1 จากมุม sec-advisor — ดูรายละเอียดเพิ่มเรื่อง comment ในโค้ด WF เองที่บอกว่ารอ “final phase” port JWT validation จริง)
🔵 nits (5 จุด — เอาไว้ทำทีหลังก็ได้)
- §3.1 #2 heading (บรรทัด 193) ยังพิมพ์ “เปิดจริง” ทั้งที่ Exec Summary แก้เป็น “อยู่ในโค้ดจริง” แล้ว
- §2 หมวด 1 บรรทัด 122 คำเดียวกัน (“เปิดจริง ~10 service”)
- §2 หมวด 2 “มาตรฐานสั่ง” ตกข้อกำหนด replay-attack protection (nonce/short-lived token) ของ guideline ข้อ 7(2)
- PDPA wording (§2 หมวด 3 + §5 Phase 3) ทิศทางคลุมเครือ — guideline จริงคือ “ฝ่ายที่ทำลายข้อมูลส่งหลักฐานมาให้ธนาคาร” ไม่ใช่เราส่งออก
- §4.2 Step 1 inventory ไม่ครอบ SignalR hub endpoints (
MapHub) ซึ่งโดน FallbackPolicy เหมือน health probe + ต้องมีOnMessageReceivedอ่านaccess_tokenจาก query (NotificationService ทำแบบนี้อยู่แล้ว เป็นตัวอย่างได้)
สิ่งที่ทำไปแล้ว (ไม่ต้องทำซ้ำ)
- รอบแรก (F1-F6) apply ครบแล้วจริง — ตรวจสอบได้จาก diff ของไฟล์ปัจจุบัน เทียบ workflow output
wsqpebkm5.output(temp path เดิม, session ก่อน) - บรรทัด 7 ของเอกสาร (status line) อัปเดตแล้วว่า “ผ่าน fact-check review แล้ว (รอบแรก)” — ต้องอัปเดตอีกครั้งหลัง apply รอบสองเสร็จ ให้สะท้อนว่าผ่าน 2 รอบ
สิ่งที่ยังไม่ได้ทำ / ต้องระวัง
- ทั้ง 13 confirmed + 5 nits ด้านบนยังไม่ได้แตะเอกสารเลย — เป็นงานหลักที่ค้าง
advisor()tool unavailable ตลอด session ที่ผ่านมา (ทั้งก่อนและหลังเปลี่ยน model) — ถ้า session ใหม่ยังเจอ error เดิม ให้ใช้ Workflow-based multi-agent re-review แทนเหมือนที่ทำมา 2 รอบแล้ว (ดู pattern ในw660vncc0— 4 reviewer + adversarial verify ต่อ finding)- COMPLIANCE_MATRIX.md ในโฟลเดอร์เดียวกัน (สร้างคนละ session, ไม่เกี่ยวกับงานนี้โดยตรง) link ไปหา
SECURITY_SCAN_FINDINGS.mdและSECURITY_HARDENING_PLAN.mdที่ไม่มีอยู่จริงในระบบไฟล์เลย — น่าจะเป็น doc set ของ Sentinel Gateway (SG-13..SG-26) จากอีก session ที่ยังสร้างไม่ครบ ไม่ใช่ scope ของ handoff นี้ แต่ถ้า user ถามถึงให้แจ้งว่าไฟล์ที่ referenced ยังไม่มี - ไฟล์ทั้งหมดใน
security/02072026/ยัง untracked ใน git (ไม่เคย commit) — ถ้า apply เสร็จแล้ว ให้ถาม user ก่อน commit ตามปกติ
Suggested skills สำหรับ session ถัดไป
- ไม่ต้องใช้ skill พิเศษ สำหรับงาน apply findings — เป็นการแก้ไฟล์ markdown ตรงไปตรงมาด้วย Edit tool ตาม list ด้านบน ทำตามลำดับ severity (must_fix ก่อน)
- หลัง apply เสร็จครบ ถ้าต้องการความมั่นใจเพิ่มก่อนส่งให้ทีม ให้ลองเรียก
advisor()อีกครั้งก่อน (อาจกลับมาใช้ได้แล้ว) — ถ้ายัง unavailable ให้ fallback เป็น Workflow re-review รอบ 3 แบบเดิม (เฉพาะจุดที่เพิ่งแก้ใหม่ ไม่ต้อง full re-review ทั้งฉบับอีก เพราะรอบ 1-2 cover ทั้งฉบับไปแล้ว) - ถ้า user ขอให้เขียน
SECURITY_SCAN_FINDINGS.md/SECURITY_HARDENING_PLAN.mdที่ COMPLIANCE_MATRIX.md อ้างถึง (Sentinel Gateway scope, คนละเรื่องกับ master plan นี้) ให้เริ่มจากอ่านsentinel-gateway-service/โฟลเดอร์อื่นๆ ในภาพก่อน (มี SECURITY_REVIEW.md, SECURITY_CHECKLIST.md จาก 04062026 ที่อาจมี SG-ID เดิมให้ต่อยอด) — งานนี้แยกจาก handoff นี้โดยสิ้นเชิง
Reference
- เอกสารเป้าหมาย: SECURITY_MASTER_PLAN_DEFENSE_ROADMAP.md
- Workflow รอบแรก (F1-F6, apply แล้ว): task id
wsqpebkm5 - Workflow รอบสอง (13+5, ยังไม่ apply): task id
w660vncc0, journal เต็ม:C:\Users\supakornp\.claude\projects\c--Source-Private-Atlas-docs\f5f43cb5-b403-4d94-9455-e4ce71afcbdf\subagents\workflows\wf_712622ca-ba5\journal.jsonl - ไม่มี hardcoded credential ค่าจริงใดๆ ถูกคัดลอกมาใน handoff นี้ — อ้างเฉพาะ file ตามที่เอกสารต้นทางทำไว้แล้ว