refactor(release-cleanup): 將清理邏輯由 bash 改寫為 Node.js #5
@@ -4,6 +4,9 @@
|
|||||||
// 單一 HTTP 請求的逾時(毫秒)。避免 API 緩慢或掛起時容器永久卡死。
|
// 單一 HTTP 請求的逾時(毫秒)。避免 API 緩慢或掛起時容器永久卡死。
|
||||||
const REQUEST_TIMEOUT_MS = 30000
|
const REQUEST_TIMEOUT_MS = 30000
|
||||||
|
|
||||||
|
// 分頁讀取的最大頁數上限,作為安全斷點:即使 API 異常未以空陣列結尾,也不致無限迴圈耗盡資源。
|
||||||
|
const MAX_PAGES = 1000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Ghost marked this conversation as resolved
|
|||||||
* 將回應內容整理成可安全寫入錯誤訊息的片段:移除控制字元(避免換行等造成的 log 注入)並限制長度。
|
* 將回應內容整理成可安全寫入錯誤訊息的片段:移除控制字元(避免換行等造成的 log 注入)並限制長度。
|
||||||
* @param {string} text 原始回應文字
|
* @param {string} text 原始回應文字
|
||||||
@@ -49,6 +52,10 @@ export class GiteaClient {
|
|||||||
let page = 1
|
let page = 1
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (page > MAX_PAGES) {
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Mage
**問題**:await res.json() 若遇到 API 回傳格式不符的內容(即使 content-type 為 application/json),會直接拋出 SyntaxError,且因為此處未以 try-catch 包裹,會導致程式崩潰並遺失錯誤發生的 URL 上下文。
**建議**:將 await res.json() 包裹在 try-catch 區塊中,解析失敗時捕捉錯誤並拋出包含當前請求 URL 的明確錯誤訊息。
|
|||||||
|
throw new Error(`GET ${baseUrl} exceeded MAX_PAGES (${MAX_PAGES}); aborting to avoid an unbounded loop`)
|
||||||
|
}
|
||||||
|
|
||||||
const url = `${baseUrl}?page=${page}`
|
const url = `${baseUrl}?page=${page}`
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Bard
**問題**:fetchAllPages 採展開運算子處理分頁資料,面對龐大項目數量可能導致 Maximum call stack size exceeded 錯誤,並伴隨無窮迴圈風險 (缺少 MAX_PAGES 限制)。
**建議**:改用簡單的 for...of 迴圈逐一 push 項目以規避堆疊風險,並務必加入 MAX_PAGES 常數作為安全斷點,防止 API 異常導致無限迴圈與資源耗盡。
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Bard
**問題**:fetchAllPages 採展開運算子處理分頁資料,面對龐大項目數量可能導致 Maximum call stack size exceeded 錯誤,並伴隨無窮迴圈風險 (缺少 MAX_PAGES 限制)。
**建議**:改用簡單的 for...of 迴圈逐一 push 項目以規避堆疊風險,並務必加入 MAX_PAGES 常數作為安全斷點,防止 API 異常導致無限迴圈與資源耗盡。
|
|||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ export async function cleanupReleases(client, config) {
|
|||||||
continue
|
continue
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Assassin
**問題**:直接將 API 回傳的 id 與 tag 名稱拼接到 URL 中進行 DELETE 操作,未經驗證,存在路徑穿越或 SSRF 風險。
**建議**:在使用 id 或 tag 名稱構建 URL 前,必須嚴格驗證其字元組成(如僅允許特定格式或編碼處理)。
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `${config.releaseApiUrl}/${id}`
|
// 對 id 做編碼,避免非預期內容被拼接進 URL(防路徑穿越);正常數值 id 編碼後不變。
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:使用字串模板直接拼接 API URL:`const url = `${config.releaseApiUrl}/${id}`;`。若 API 回傳的 id 包含 `/` 或特殊字元,可能導致拼接出錯誤的 API 路徑,甚至在某些處理器上導致路徑穿越,雖是 Gitea API 但應防禦性地進行 URL 編碼。
**建議**:建議使用 encodeURIComponent(id) 對 id 進行編碼後再拼接。
|
|||||||
|
const url = `${config.releaseApiUrl}/${encodeURIComponent(id)}`
|
||||||
info(`DELETE ${tag} (${name})`)
|
info(`DELETE ${tag} (${name})`)
|
||||||
|
|
||||||
const code = await client.deleteResource(url)
|
const code = await client.deleteResource(url)
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ export async function cleanupOrphanTags(client, config) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Assassin
**問題**:直接將 tag 名稱拼接至 URL 是常見的 Injection 破口。儘管使用了 encodeURIComponent,但若 tag.name 包含某些在 Gitea API 邏輯中具特殊意義的字元,仍可能造成非預期的資源存取或路徑穿越。
**建議**:除了 encodeURIComponent 外,應在 config.js 或 tags.js 中對 tag.name 進行嚴格的白名單格式驗證(例如限制為英數字、點、破折號,並禁止 .. 或 /),這比單純編碼更安全。
|
|||||||
|
|
||||||
const url = `${config.tagApiUrl}/${tag.name}`
|
// 對 tag 名稱做編碼,避免名稱中的特殊字元被拼接進 URL(防路徑穿越);一般 tag 名編碼後不變。
|
||||||
|
const url = `${config.tagApiUrl}/${encodeURIComponent(tag.name)}`
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:與 releases.js 相同問題,在拼接 tag URL 時:`const url = `${config.tagApiUrl}/${tag.name}`;` 未對 tag 名稱進行 URL 編碼。Tag 名稱若包含 `/` 等特殊字元,會破壞 URL 結構。
**建議**:建議使用 encodeURIComponent(tag.name) 對 tag 名稱進行編碼。
|
|||||||
info(`DELETE tag ${tag.name}`)
|
info(`DELETE tag ${tag.name}`)
|
||||||
|
|
||||||
const code = await client.deleteResource(url)
|
const code = await client.deleteResource(url)
|
||||||
|
|||||||
嚴重等級:🔵 建議
審查員:Leo
問題:目前
MAX_PAGES是寫死在程式碼中的常數,未來如果 API 規格變更或是特殊儲存庫的 Release 數量激增,維護者需要進程式碼修改,且這在不同的環境下可能需要不同的上限。建議:建議將
MAX_PAGES改為透過環境變數傳入,並設定一個合理的預設值,增加部署時的彈性。