From aa4f6364e2c569ad1d2a882788d48a2d82ff77d5 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 26 Jun 2026 11:21:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(release-cleanup):=20=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E5=88=86=E9=A0=81=E4=B8=8A=E9=99=90=E4=B8=A6=E7=B7=A8=E7=A2=BC?= =?UTF-8?q?=E5=88=AA=E9=99=A4=20URL=20=E8=B7=AF=E5=BE=91=E7=89=87=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetchAllPages 加入 MAX_PAGES(1000)安全斷點,避免 API 異常時無限迴圈 - 刪除 release/tag 時對 id 與 tag 名稱做 encodeURIComponent, 防止特殊字元造成路徑穿越(正常數值/版本字串編碼後不變) Co-Authored-By: Claude Opus 4.8 (1M context) --- app/gitea-client.js | 7 +++++++ app/releases.js | 3 ++- app/tags.js | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/gitea-client.js b/app/gitea-client.js index f54fdd8..7a769ef 100644 --- a/app/gitea-client.js +++ b/app/gitea-client.js @@ -4,6 +4,9 @@ // 單一 HTTP 請求的逾時(毫秒)。避免 API 緩慢或掛起時容器永久卡死。 const REQUEST_TIMEOUT_MS = 30000 +// 分頁讀取的最大頁數上限,作為安全斷點:即使 API 異常未以空陣列結尾,也不致無限迴圈耗盡資源。 +const MAX_PAGES = 1000 + /** * 將回應內容整理成可安全寫入錯誤訊息的片段:移除控制字元(避免換行等造成的 log 注入)並限制長度。 * @param {string} text 原始回應文字 @@ -49,6 +52,10 @@ export class GiteaClient { let page = 1 while (true) { + if (page > MAX_PAGES) { + throw new Error(`GET ${baseUrl} exceeded MAX_PAGES (${MAX_PAGES}); aborting to avoid an unbounded loop`) + } + const url = `${baseUrl}?page=${page}` const res = await fetch(url, { headers: this.headers, diff --git a/app/releases.js b/app/releases.js index 4bd40f3..731add8 100644 --- a/app/releases.js +++ b/app/releases.js @@ -56,7 +56,8 @@ export async function cleanupReleases(client, config) { continue } - const url = `${config.releaseApiUrl}/${id}` + // 對 id 做編碼,避免非預期內容被拼接進 URL(防路徑穿越);正常數值 id 編碼後不變。 + const url = `${config.releaseApiUrl}/${encodeURIComponent(id)}` info(`DELETE ${tag} (${name})`) const code = await client.deleteResource(url) diff --git a/app/tags.js b/app/tags.js index c991da3..22becd9 100644 --- a/app/tags.js +++ b/app/tags.js @@ -61,7 +61,8 @@ export async function cleanupOrphanTags(client, config) { continue } - const url = `${config.tagApiUrl}/${tag.name}` + // 對 tag 名稱做編碼,避免名稱中的特殊字元被拼接進 URL(防路徑穿越);一般 tag 名編碼後不變。 + const url = `${config.tagApiUrl}/${encodeURIComponent(tag.name)}` info(`DELETE tag ${tag.name}`) const code = await client.deleteResource(url)