ChefsMate · 夜審系統
啟動 2026-05-27 · Phase 1 · POS 10 模組輪流深審 · 每次睡覺 trigger,Agent 自動跑 audit + sysmap 比對 · Cycle 1 全 10 模組完成(2026-05-28 凌晨)
重審帳號/身分相關函數與權限,抓到 2 個 Critical(帳號接管) + 3 個 High。 其中 4 項今晚已修並驗證,1 項(合併會員卡深層授權)已先擋匿名、深層待老闆 dogfood。
dining_sessions_all / tsc_select|insert|update / checkout_sessions/payments_read|write_auth / menu_items|menu_categories|floor_items|floor_plans authenticated_read_active_*(全平台可讀風險);重建 inventory deduct trigger 用 NEW.status(原本拼錯 status_raw)。
send-wallet-link/index.ts 改成從 restaurants 表查 slug(原本查 restaurant_settings.slug 不存在 → email 兩個 wallet 按鈕從第一天就沒出現)。
POS_流程邏輯圖.md + POS_功能地圖.md 頂部加 cycle 1 doc_mismatch warning block。
web/booking/src/lib/shared/verify_caller.ts:從 JWT → user_profiles → restaurant_members.role 驗證 caller,不再接 body caller_role。
boss_audit_findings WHERE user_profile_id='9fc277c3-...'。下個 cycle 會 render 完整 cards。
AuthManager.swift:1034-1046 · 01. POS 認證👔 老闆版 — 問題:你「刪自己帳號」按鈕應該要擋你 — 跳出「你是這家店唯一的老闆,刪了餐廳就變孤兒」的警告。但這個防護程式打錯欄位,等於完全沒擋。
📍 舉例:你週末心情不好按了「刪除帳號」,App 沒任何警告就刪了。下週一發現店在 App 裡消失,員工被踢出,已付訂金的客人變孤兒訂單,POS 完全用不了。
💡 建議:把客戶端擋人的邏輯接到對的欄位(改一個變數名)。同時要 Supabase 後端那邊也加一道擋人保險,不能只靠客戶端。
AuthManager.swift:1108-1127 · 01. POS 認證👔 老闆版 — 問題:刪帳號本來設計兩條路:一條 App 直接打資料庫(現在 client 走的),一條走 Edge Function(會做 6 層保護,例如要打「DELETE_MY_ACCOUNT」確認字串才能刪)。實際 App 從來沒走第二條路。
📍 舉例:就像你家裝兩道門鎖,結果出門只鎖第一道(容易被撬的),第二道防盜鎖完全沒用過。Apple 審核以為我們有兩層,實際只有一層。而且現在用的那條路(資料庫 RPC)source code 沒入版控,連我們都看不到它在做什麼。
💡 建議:三選一 — (a) App 改走 Edge Function(最安全) (b) Edge Function 標廢棄拿掉 (c) 至少把資料庫那條路的 source 補進版控讓我們能稽核。
supabase/functions/send-staff-invitation-email/index.ts:43-72👔 老闆版 — 問題:「寄員工邀請信」這個介面應該只有「邀請人本人」可以用。現在完全沒檢查身份,任何拿到 App 公開金鑰的人都可以亂試 invitation_id 觸發官方寄信。
📍 舉例:壞人寫程式暴力試 invitation_id。試中就讓 chefsmate.app 官方信箱寄釣魚信給目標員工。員工看到「ChefsMate 官方信」加上「加入餐廳」連結就會點 — 但連結可能被竄改,變成釣魚陷阱。
💡 建議:寄信前驗證:看一下打這支 API 的人是不是邀請人本人。再加同 invitation 5 分鐘只能寄一次(防 spam)。
supabase/functions/email-link-merge/index.ts:91-100👔 老闆版 — 問題:合併帳號功能會用「email + 密碼」跟 Supabase 試登入。Supabase 本來有「同 IP 連續試 N 次密碼就鎖」保護,但我們 Edge Function 統一用 Supabase 自家 IP 出去,Supabase 看到都是「自己人」,保護失效。
📍 舉例:壞人註冊一個 ChefsMate 帳號(很容易),然後拿合併功能當「免費密碼破解工具」,對全世界任何 email 不停試密碼,試 1000 次也不會被擋。
💡 建議:加「每個 user 每 5 分鐘最多試 3 次失敗就鎖」+ 不論對錯都回一樣錯誤訊息(現在「email 不存在」跟「密碼錯」會分開講)。
oauth-link-merge/index.ts:188-199 · line-link-merge:86, 269👔 老闆版 — 問題:連 Google/LINE/Apple 帳號時,我們收一個「連完跳哪」的參數。現在這個參數沒檢查,可以寫任意網址。
📍 舉例:壞人寄釣魚連結讓你點 — 你 OAuth 認證完成後,系統會帶著「你連結哪個帳號 + 合併了幾張會員卡」資訊跳到 attacker.com。壞人就知道你的 ChefsMate 帳號狀態 + 餐廳會員量(間接洩漏營業情報)。
💡 建議:跳轉只允許 ChefsMate 自家網址(com.chefmate.app:// 或 chefsmate.app),其他一律拒絕。
StaffQRCodeView.swift:207-240 · 員工 QR Code 等老闆掃👔 老闆版 — 問題:員工加入餐廳要顯示 QR Code 等老闆掃。員工這邊用「即時連線」聽老闆掃完通知。但連線斷一次就死了,要殺 App 重開才會復活。
📍 舉例:新員工在你店裡 onboarding,WiFi 抖一下(店裡常見) — 老闆掃完員工 App 還停在 QR 畫面,等不到「歡迎加入」。員工會以為 App 壞了,殺 App 重來,變成「老闆掃了 → 員工 App 沒反應 → 殺 App → 重開 → 才看到加入成功」的爛體驗。
💡 建議:連線斷了要自動重連(其他地方已寫過這邏輯,這裡漏)。再加每 30 秒主動問一次資料庫狀態當保險。
RoleSelectionView.swift:208-224 · ✓ sysmap 已修👔 老闆版 — 問題:系統文件寫「選了角色直接跳對應畫面」,實際 code 中間還有一頁(OnboardingView)。文件描述跟實際流程不一致。
📍 舉例:工程師(或 AI 助手)讀文件寫新功能,以為「員工選了角色 → 直接到加入餐廳頁」,實際還有中間頁。後續加「角色選完寄推播給老闆」這種功能會加錯位置,觸發時機不對。
💡 建議:已經修了 — 把中間那層 OnboardingView 補進系統文件。
AuthManager.swift:1311-1314 · ✓ sysmap 已修StaffJoinRestaurantView.swift:279-295AuthManager.swift:1973-1995POS_Supabase_Edge_Functions地圖.md:438-460 · ✓ sysmap 已修AuthManager.swift:1557-1680 · ✓ sysmap 已加 1.9bSignInView.swift:520-612StaffPendingApprovalView.swift:217-235StaffPendingApprovalView.swift:261-267AppleSignInCoordinator.swift:65EmailVerificationView.swift:249-267SignInView.swift:256-271send-staff-invitation-email/index.ts:30-31AuthManager.swift:1034-1046StaffQRCodeView.swift:207-240StaffJoinRestaurantView.swift:279-295AuthManager.swift:1973-1995SignInView.swift:520-612StaffPendingApprovalView.swift:217-235AppleSignInCoordinator.swift:65EmailVerificationView.swift:249-267AuthManager.swift:1108-1127RoleSelectionView.swift:208-224AuthManager.swift:1311-1314POS_Supabase_Edge_Functions地圖.md:438-460AuthManager.swift:1557-1680send-staff-invitation-email/index.ts:43-72email-link-merge/index.ts:91-100oauth-link-merge/index.ts:188-199SignInView.swift:256-271send-staff-invitation-email/index.ts:30-31StaffPendingApprovalView.swift:261-267AuthManager.swift:1034-1046AuthManager.swift:1108-1127send-staff-invitation-email/index.ts:43-72email-link-merge/index.ts:91-100oauth-link-merge/index.ts:188-199StaffQRCodeView.swift:207-240RoleSelectionView.swift:208-224AuthManager.swift:1311-1314StaffJoinRestaurantView.swift:279-295AuthManager.swift:1973-1995POS_Supabase_Edge_Functions地圖.md:438-460AuthManager.swift:1557-1680SignInView.swift:520-612StaffPendingApprovalView.swift:217-235StaffPendingApprovalView.swift:261-267AppleSignInCoordinator.swift:65EmailVerificationView.swift:249-267SignInView.swift:256-271send-staff-invitation-email/index.ts:30-31boss_nightly_audits_next_module() RPC 挑「最久沒審」的模組agent_status='running' row 進 boss_nightly_auditsboss_nightly_audits row + INSERT findings 到 boss_audit_findings(這頁的資料來源)每筆 finding 有以下 8 種狀態:
怎麼改狀態?跟 AI 說「把『XXX 標題』標 fixed / wont_fix / deferred」, AI 會 UPDATE Supabase。下次 trigger 夜審或手動 refresh 這頁就會看到狀態變化。
底層:Supabase table boss_audit_findings + RPC boss_audit_findings_plan(uuid)。
資料 INSERT 一次永不刪除(只改 status),歷史完整保留。