chore: update ai-review findings [ai-review-bot][failure]
This commit is contained in:
+124
-19
@@ -1,4 +1,42 @@
|
||||
[
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Assassin",
|
||||
"location": "app/releases.js:48",
|
||||
"problem": "攻擊者可以透過控制 GITEA_REPOSITORY 環境變數,在其名稱中包含路徑穿越序列(如 '../'),進而操控 API 的刪除目標,造成越權刪除其他儲存庫的成品。",
|
||||
"suggestion": "在將 id 拼接進 URL 前,務必使用 encodeURIComponent(id) 對 id 進行編碼,確保其僅被視為路徑的一部分,而非路徑控制字元。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Assassin",
|
||||
"location": "app/tags.js:70",
|
||||
"problem": "攻擊者可以透過控制 GITEA_REPOSITORY 環境變數,在其名稱中包含路徑穿越序列,進而操控 API 的刪除目標;同時,tag 名稱若包含惡意字元也可能造成路徑穿越。",
|
||||
"suggestion": "同樣地,在將 tag.name 拼接進 URL 前,必須使用 encodeURIComponent(tag.name) 對其進行編碼。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Maya",
|
||||
"problem": "main 函式執行失敗時會觸發 process.exit(1),確保 CI/CD 流程能正確偵測錯誤。目前的測試僅驗證了 Promise 被 reject,但並未驗證程式是否真的正確以非零狀態碼結束。",
|
||||
"suggestion": "請在 app/test/main.test.js 中,模擬 main 拋出錯誤的情境,並透過 mock process.exit 來驗證當 main 執行失敗時,程式碼確實執行了 process.exit(1)。",
|
||||
"location": "app/index.js:31",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Maya",
|
||||
"location": "app/test/config.test.js:63",
|
||||
"problem": "測試只驗證了正常與極端的 token 輸入,但完全沒有測試 token 為 null 或未定義(匿名模式)下的處理邏輯,也沒確認匿名請求時,設定物件是否正確地將 token 設為 null。",
|
||||
"suggestion": "請增加測試案例,明確斷言當 GITEA_TOKEN 不存在於環境變數時,loadConfig() 回傳的 token 屬性為 null。"
|
||||
},
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Maya",
|
||||
"location": "app/test/gitea-client.test.js:33",
|
||||
"problem": "測試中雖然有 fetchAllPages 的功能測試,但對於 MAX_PAGES 的邊界情況,測試只用了 globalThis.fetch 永遠回傳非空陣列,這是一個快樂路徑的極端變體。如果 API 剛好在第 MAX_PAGES 頁回傳空陣列,測試並未驗證客戶端是否能正確處理並停止。",
|
||||
"suggestion": "請增加一個測試案例,模擬當 API 恰好在第 MAX_PAGES 次請求時回傳空陣列的情境,確認客戶端能成功結束,而非拋出例外。"
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Mage",
|
||||
@@ -6,16 +44,8 @@
|
||||
"problem": "清理流程對網路請求依賴強,若 API 呼叫失敗,整個 main 流程中斷,無法確保後續清理的一致性與部分成功重試。",
|
||||
"suggestion": "引入更細緻的錯誤處理(如錯誤閾值機制)或部分清理成功後的重試策略。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "與 app/releases.js:46 同屬錯誤處理/重試策略的設計取捨。目前單筆刪除失敗會記錄並繼續、讀取失敗則中止屬合理保守行為;是否引入閾值/重試保留待人工評估。"
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Mage",
|
||||
"location": "app/releases.js:46",
|
||||
"problem": "刪除邏輯未針對特定 HTTP 狀態碼(502, 503, 504)實作重試機制,且遇到失敗時未停止後續請求。",
|
||||
"suggestion": "針對特定 HTTP 狀態碼實作指數退避重試並引入錯誤閾值;資料量龐大時考慮分批/串流讀取。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "重試/退避/錯誤閾值涉及次數、間隔與冪等性等設計決策;清理 Action 以排程執行,單次失敗可於下次補刪。分頁已有 MAX_PAGES 上限,實務上 release/tag 數量有限,串流化屬最佳化取捨,保留待人工評估。"
|
||||
"defer_reason": "與 app/releases.js:46 同屬錯誤處理/重試策略的設計取捨。目前單筆刪除失敗會記錄並繼續、讀取失敗則中止屬合理保守行為;是否引入閾值/重試保留待人工評估。",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
@@ -24,7 +54,8 @@
|
||||
"problem": "cleanupReleases 同時處理資料獲取、邏輯判斷與副作用執行,違反單一職責原則。",
|
||||
"suggestion": "拆分邏輯與執行層,將刪除副作用抽象化為獨立函式。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "純邏輯(selectReleasesToDelete)已抽離且函式短小、已具失敗路徑與編碼測試;進一步拆出刪除迴圈為設計偏好,效益有限,保留待人工評估。"
|
||||
"defer_reason": "純邏輯(selectReleasesToDelete)已抽離且函式短小、已具失敗路徑與編碼測試;進一步拆出刪除迴圈為設計偏好,效益有限,保留待人工評估。",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
@@ -33,7 +64,8 @@
|
||||
"problem": "清理成品與刪除 tag 使用序列化迴圈,API 請求逐一排隊,整體執行時間拉長。",
|
||||
"suggestion": "改用 Promise.all 搭配 map 將刪除請求並行化。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "刻意保留序列化:避免對 Gitea API 造成併發壓力與觸發速率限制,並維持可預期的記錄輸出順序;無上限並行化非等價變更,保留待人工評估(可日後改為有上限並行)。"
|
||||
"defer_reason": "刻意保留序列化:避免對 Gitea API 造成併發壓力與觸發速率限制,並維持可預期的記錄輸出順序;無上限並行化非等價變更,保留待人工評估(可日後改為有上限並行)。",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
@@ -42,7 +74,8 @@
|
||||
"problem": "錯誤路徑以 res.text() 讀取整個回應主體,未限制大小,惡意伺服器可回傳極大內容導致記憶體耗盡(DoS)。",
|
||||
"suggestion": "限制讀取的回應大小,例如檢查 Content-Length 或以串流方式設定讀取上限。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "風險低:目標為已通過 URL 驗證的受信任 Gitea 實例,需伺服器被入侵或中間人攻擊才成立,且每請求已有 30 秒逾時部分約束。正確修法需以串流逐段讀取並設位元組上限(Content-Length 在 chunked 回應下不可靠),屬較大改動,保留待人工評估。"
|
||||
"defer_reason": "風險低:目標為已通過 URL 驗證的受信任 Gitea 實例,需伺服器被入侵或中間人攻擊才成立,且每請求已有 30 秒逾時部分約束。正確修法需以串流逐段讀取並設位元組上限(Content-Length 在 chunked 回應下不可靠),屬較大改動,保留待人工評估。",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
@@ -51,15 +84,87 @@
|
||||
"problem": "成功路徑以 res.json() 解析整個回應,未限制大小,惡意伺服器可回傳極大 JSON 導致記憶體耗盡(DoS)。",
|
||||
"suggestion": "對 API 回應設定明確大小上限,超過時拒絕解析並拋出異常。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "與 app/gitea-client.js:66 同類:受信任目標、已有逾時與 MAX_PAGES 約束,風險低;正確修法需串流讀取並設上限,屬較大改動,保留待人工評估。"
|
||||
"defer_reason": "與 app/gitea-client.js:66 同類:受信任目標、已有逾時與 MAX_PAGES 約束,風險低;正確修法需串流讀取並設上限,屬較大改動,保留待人工評估。",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Maya",
|
||||
"problem": "logger.js 中的輸出函式(如 separator, section, info, success, warn, fail)負責 Action 的核心視覺輸出格式,但目前缺乏測試驗證其實際輸出內容是否正確對齊並符合格式要求。",
|
||||
"suggestion": "請在 app/test/logger.test.js 中補齊對這些函式的測試,驗證其是否正確寫入預期的格式內容(含分隔線與正確的前綴)到 stdout。",
|
||||
"location": "app/logger.js:9",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Maya",
|
||||
"problem": "sanitizeBody 函式負責 API 回應的字串清理與格式化,但目前缺乏直接的單元測試,無法確保正規表示式能正確處理所有控制字元以及長度限制。",
|
||||
"suggestion": "請在 app/test/gitea-client.test.js 中新增 sanitizeBody 的單元測試,務必包含正常字串、包含控制字元的字串、空字串/null 值、以及超過 200 字元的極端案例。",
|
||||
"location": "app/gitea-client.js:13",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Mage",
|
||||
"location": "app/releases.js:59",
|
||||
"problem": "使用字串模板直接拼接 API URL:`const url = `${config.releaseApiUrl}/${id}`;`。若 API 回傳的 id 包含 `/` 或特殊字元,可能導致拼接出錯誤的 API 路徑,甚至在某些處理器上導致路徑穿越,雖是 Gitea API 但應防禦性地進行 URL 編碼。",
|
||||
"suggestion": "建議使用 encodeURIComponent(id) 對 id 進行編碼後再拼接。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Mage",
|
||||
"location": "app/tags.js:65",
|
||||
"problem": "與 releases.js 相同問題,在拼接 tag URL 時:`const url = `${config.tagApiUrl}/${tag.name}`;` 未對 tag 名稱進行 URL 編碼。Tag 名稱若包含 `/` 等特殊字元,會破壞 URL 結構。",
|
||||
"suggestion": "建議使用 encodeURIComponent(tag.name) 對 tag 名稱進行編碼。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Maya",
|
||||
"location": "app/test/releases.test.js:77",
|
||||
"problem": "在 `cleanupReleases` 的整合測試中,雖然有測試 `deleteResource` 回傳非 204 時的行為,但測試只檢查了 `client.deleted` 的呼叫順序,並沒有驗證 `fail` 日誌(對 stderr 的寫入)是否正確被觸發。",
|
||||
"suggestion": "建議攔截 `process.stderr.write`,確認當 `deleteResource` 回傳 500 時,系統確實有記錄到錯誤訊息。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Maya",
|
||||
"location": "app/test/tags.test.js:73",
|
||||
"problem": "在 `cleanupOrphanTags` 的測試中,缺乏對於「當 `deleteResource` 失敗」時的行為驗證。目前只測試了成功的情境。",
|
||||
"suggestion": "增加模擬 `deleteResource` 回傳非 204 狀態碼的情境,確認該項刪除失敗不會中斷整個標籤清理流程,並檢查是否有對應的錯誤日誌。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "info",
|
||||
"role": "Bard",
|
||||
"location": "app/releases.js:28",
|
||||
"problem": "在 `cleanupReleases` 函式中,參數 `config` 是 `ReturnType<import('./config.js').loadConfig>`,這種型別定義方式雖然準確,但過於冗長且與實作細節耦合過深,影響程式碼的可讀性與簡潔度。",
|
||||
"suggestion": "建議在 `app/config.js` 定義並匯出型別註解(JSDoc @typedef),然後在此處直接使用該別名,提升整體程式碼的可讀性。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "info",
|
||||
"role": "Bard",
|
||||
"location": "app/gitea-client.js:25",
|
||||
"problem": "在 `GiteaClient` 建構子中,`this.headers` 初始化時僅簡單檢查 `token` 是否存在,且假設所有請求都適用這組標頭。雖然目前專案單純,但若未來擴充需針對不同 API 採取不同標頭時,此處結構會稍顯死板。",
|
||||
"suggestion": "考慮將產生 header 的邏輯抽離成一個內部 private 函式(如 `_getHeaders()`),即便現在很簡單,也能增加未來的彈性。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "info",
|
||||
"role": "Maya",
|
||||
"location": "app/test/validate.test.js:20",
|
||||
"problem": "雖然有 `requireInteger` 的測試,但沒有測試當 `KEEP_COUNT` 為 `0` 時的邊界情況。雖然 0 在邏輯上可能是允許的,但這對於清理邏輯來說是個關鍵的邊界。",
|
||||
"suggestion": "明確測試 `requireInteger('KEEP_COUNT', '0')` 並斷言其不應拋出錯誤,確保系統允許保留 0 個成品的配置。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "info",
|
||||
"role": "Rogue",
|
||||
"location": "app/gitea-client.js:33",
|
||||
"problem": "fetchAllPages 採線性逐頁請求,資料龐大時效能不佳。",
|
||||
"suggestion": "若 API 支援,先取得總頁數後並行請求。",
|
||||
"status": "deferred",
|
||||
"defer_reason": "Gitea 分頁未可靠提供總頁數,需解析 Link 標頭且各端點支援度不一;線性逐頁搭配逾時與 MAX_PAGES 上限已足夠穩健,並行化屬最佳化取捨,保留待人工評估。"
|
||||
"location": "app/releases.js:23",
|
||||
"problem": "為了排序而進行了多次 map 操作,先將陣列 map 為物件陣列,排序後又 map 回原始物件陣列。這在大數據集下會造成多次 O(n) 的陣列分配與垃圾回收壓力,浪費 CPU 與記憶體週期。",
|
||||
"suggestion": "排序時應嘗試減少陣列中間狀態的產生,例如考慮使用原地排序(若不介意原陣列變更)或簡化 mapping 的邏輯。",
|
||||
"is_new": true
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user