iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Software Development

收到自己的紅色炸彈!婚期就是 Deadline:前端一人用 AI 規格流水線,30 天出貨婚禮 SaaS系列 第 15

Day 15 全綠燈,然後正式站還是炸了!從 3 起線上事故看自動化測試的守備盲區與 CI 防線補強

  • 分享至 

  • xImage
  •  

❯❯ 綠燈的邊界:R2 CRC32、跨租戶越權、CI 沒跑 unit:三個真實事故
Day 15 全綠燈,然後正式站還是炸了!從 3 起線上事故看自動化測試的守備盲區與 CI 防線補強

📍 流水線位置|入口 → flow → 型別 → mock → 【測試】 → UI → vibe → 上線(綠燈的邊界)

流水線位置|入口 → flow → 型別 → mock → 【測試】 → UI → vibe → 上線(綠燈的邊界)

上一篇把自動化 E2E 測試定為不說謊的系統主規格。但測試能驗證的,終究只有被明確寫進斷言的條款;那些沒被涵蓋的邊界條件與環境變數,它一概無法保證。

最可怕的不是測試沒過,而是測試全部綠燈,生產環境卻依然炸了。

以下是開發過程中發生的三起真實事故:生產環境功能失效、內部稽核挖出的安全漏洞,以及 CI 管道組態缺失。這三起案例在問題爆發前,E2E 自動化測試全都高掛綠燈。


事故一:生產環境圖片上傳功能全面失效

在部署至生產環境後,圖片上傳介面(包含交通資訊圖、賓客祝福照片與喜餅圖檔)突然全面回傳 HTTP 400 失敗,使用者反覆重試也無法完成上傳。

明明測試全過,為什麼線上會爆掉?

仔細排查後發現,根因並非產品自身的業務邏輯出錯,而是第三方套件的預設行為變更:@aws-sdk/client-s3 套件自 3.729.0 版本起,預設對 PutObjectCommand 自動計算並附加 CRC32 校驗和(Checksum)。當後端在沒有 Request Body 的情況下生成 Presigned URL 時,SDK 便自動計算了「空內容」的校驗和,並將其一併簽入 URL 參數中。

結果,當前端拿著這個 Presigned URL 向 Cloudflare R2 發起實體檔案上傳時,Cloudflare R2 一比對 Header 與 Payload,發現實體檔案內容與簽名中的 CRC32 校驗和不符,當場拒絕請求並回傳 HTTP 400 錯誤。

那麼,E2E 測試為什麼沒抓到?因為在測試環境中, r2Enabled 組態設定為false,測試資料流走的是本地的 Base64 模擬路徑(Data URL),根本沒有對 R2 儲存服務發起真實的網路請求。

這種 Mock 設計本意是為了降低測試對外部雲端服務的相依性,但也帶來了意外的的代價:真實環境下的實體傳送路徑,在測試鏈結中完全處於未驗證狀態。(最終修復方案是調整 SDK 組態,將校驗和計算改為只有在必要時才觸發。)


事故二:跨租戶越權存取漏洞(Broken Object Level Authorization)

第二個事故是在全系統的安全稽核期間發現的。早期撰寫的部分 API Handler 只有依據 Request 傳入的單一 ID 進行 Drizzle ORM 資料庫查詢,卻完全沒有比對該筆資料是否隸屬於當前 Session 所在的婚禮租戶(weddingId)。

這意味著什麼?只要是已登入的使用者,只需枚舉或推測資料 ID(例如測試資料常採用的 guest-001 格式),就能直接跨租戶存取、甚至修改與刪除其他婚禮的賓客與系統資料。

在該 Issue 追蹤紀錄中包含以下明確結論:

lint / typecheck / 211 條 e2e 全綠也抓不到此類問題。

E2E 測試之所以對這個漏洞完全失敏,主因有二:

  1. 驗證機制未啟動:
    凍結的主規格測試套件預設運行於 authMode=open 模式下,並沒有模擬實體權限校驗。

  2. 多租戶狀態缺失:
    自動化測試環境中只有注入單一婚禮租戶的資料,缺乏「多租戶同時存在且持 A 身分存取 B 資料」的邊界條件。

這個安全隱患在單租戶測試環境下被完美掩蓋,直到系統啟用「管理員代建帳號」、正式進入多租戶並存階段後,才暴露出這個高風險的越權漏洞。


事故三:單元測試爆了 5 顆,CI 流水線卻渾然不知

main 主分支上,其實有 5 顆單元測試(Unit Test)長期默默亮著紅燈,但我第一時間完全沒有察覺。

排查後才發現,問題出在 CI/CD 管道(GitHub Actions)的設定檔裡,我只建置了 buildeslint 與自訂規格審查流程,卻忘了將 test:unit 指令納入 Pull Request 的必經檢查點。

測試程式碼明明早就寫好,也抓得到錯誤,卻因為沒有被整合進構建管道而失去防護效用。

後續追查發現,這 5 顆單元測試之所以失敗,是因為環境設定發生了「漂移(Drift)」:測試腳本假設的 apiBase 路徑前綴與專案現行預設值脫節,導致請求全數打在不存在的路由而回傳 HTTP 404。修復方式只需在測試環境中補上固定 apiBase 即可,產品本體程式碼並沒有缺陷。然而,就因為 CI 管道出現了這個組態缺口,這 5 顆隱形紅燈就在主分支上被忽視了許久。


綠燈的守備範圍與邊界歸納

綜合上述三起案例,歸納自動化測試失效的根本原因:

事故案例 E2E 綠燈為什麼失效
R2 圖片上傳失效 測試環境將實體雲端服務路徑抽象化(r2Enabled=false),導致真實上傳路徑未被覆蓋。
跨租戶越權漏洞 測試環境缺乏多租戶並存的資料條件,且執行於開放權限模式(authMode=open)。
Unit 測試失敗未察 單元測試腳本未被配置於 CI/CD 管道的強制檢核節點(Gatekeeper)中。

綠燈的幻覺與三個測試盲點事故圖

上述問題本質上都是測試守備範圍(Test Scope)與環境條件的侷限,而不是測試工具本身有錯。自動化測試的綠燈,永遠只代表「在當前的測試環境與斷言條件下,系統表現符合預期」,絕不等於生產環境的絕對可靠。

針對這些測試盲點,我隨後補強了相對應的防守機制:

  • 針對外部服務相依性
    建立專屬單元測試(test/unit/r2-presign.spec.ts),直接斷言 Presigned URL 產出結果不得包含校驗和參數,避免 SDK 版本升級再次引入預設行為更迭。

  • 針對授權與權限邊界
    徹底清查 Handler 層的租戶檢查邏輯,於 33 支 API 端點補強 weddingId 約束條件,並將跨租戶存取攔截測試(security-tenant-scope-1.spec.ts)納入 Vibe 測試區進行持續監控。

  • 針對 CI 流水線缺口
    修訂 GitHub Actions 工作流程,將單元測試納入 PR 合併前的強制阻擋條件(Blocker),並將執行順序調整至 build 步驟之前。

CI/CD 強制門禁與修復防守機制圖

經歷這一輪邊界問題的修復與驗證,我對測試燈號的本質建立了更精確的工程認知:

紅燈非常直接,精準指著一個確定存在的壞點;綠燈卻更像是沉默,它對守備範圍之外的世界一字不提。將系統的沉默誤認品品質的承諾,是我過去的盲點。

下一篇將回到建構面:既然綠燈的可靠性完全取決於「我們究竟要求它驗證什麼」,那麼在自動化流水線中,測試合約(Test Contract)究竟該如何設計,才能精確且極大化綠燈的架構價值?

🎒 最小一步|打開你專案的 CI 設定檔(如 .github/workflows/),清查實際執行的測試指令,看有沒有已寫好的測試套件(Unit、Integration)根本沒被納入 PR 阻擋。
📎 本篇證據|Issue #50(R2 CRC32 checksum 異動分析)、Issue #48(跨婚禮越權存取漏洞檢討報告)、Issue #76(CI 流水線漏未執行 test:unit 檢討紀錄)


上一篇
Day 14 測試不是保險,是不會說謊的合約!150 條凍結 E2E 測試,打造「紅燈只許修 Code」的 SSOT 鐵律
下一篇
Day 16 測試怎麼寫,才對得起「合約」兩個字
系列文
收到自己的紅色炸彈!婚期就是 Deadline:前端一人用 AI 規格流水線,30 天出貨婚禮 SaaS17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言