fix(release-cleanup): 強化輸入驗證、請求逾時與錯誤處理
解決 AI review 的安全性與健壯性問題: - 驗證 GITEA_SERVER_URL 為合法 http/https URL(降低 SSRF 風險) - 驗證 GITEA_REPOSITORY 為 owner/repo 形式並拒絕路徑穿越 - 為所有 fetch 請求加上 30 秒逾時(AbortSignal.timeout),避免卡死 - fetchAllPages 解析前檢查 content-type,非 JSON 時拋出明確錯誤 - selectReleasesToDelete 對無效 created_at 防呆,避免 NaN 排序 - index.js 匯出 main 並加上直接執行守衛,錯誤輸出含類型與堆疊 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1f27a46f7b
commit
154ab032bf
+25
-3
@@ -1,6 +1,9 @@
|
||||
// 與 Gitea API 溝通的 HTTP 客戶端,封裝認證標頭、分頁讀取與刪除請求。
|
||||
// 對應原本 entrypoint.sh 的 fetch_all_pages 與 curl DELETE 呼叫。
|
||||
|
||||
// 單一 HTTP 請求的逾時(毫秒)。避免 API 緩慢或掛起時容器永久卡死。
|
||||
const REQUEST_TIMEOUT_MS = 30000
|
||||
|
||||
/**
|
||||
* 與 Gitea REST API 溝通的輕量 HTTP 客戶端,負責帶上認證標頭、分頁讀取清單與發出刪除請求。
|
||||
* 取代原 bash 版本以 `curl`/`jq` 進行的 API 操作。
|
||||
@@ -34,10 +37,25 @@ export class GiteaClient {
|
||||
|
||||
while (true) {
|
||||
const url = `${baseUrl}?page=${page}`
|
||||
const res = await fetch(url, { headers: this.headers })
|
||||
const res = await fetch(url, {
|
||||
headers: this.headers,
|
||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`GET ${url} failed: HTTP ${res.status}`)
|
||||
const body = await res.text().catch(() => '')
|
||||
throw new Error(
|
||||
`GET ${url} failed: HTTP ${res.status}${body ? ` - ${body.slice(0, 200)}` : ''}`,
|
||||
)
|
||||
}
|
||||
|
||||
// 確認回應確實是 JSON,避免 API 回傳 HTML 錯誤頁時 res.json() 拋出難以理解的 SyntaxError。
|
||||
const contentType = res.headers.get('content-type') || ''
|
||||
if (!contentType.includes('application/json')) {
|
||||
const body = await res.text().catch(() => '')
|
||||
throw new Error(
|
||||
`GET ${url} returned non-JSON content-type "${contentType}": ${body.slice(0, 200)}`,
|
||||
)
|
||||
}
|
||||
|
||||
const items = await res.json()
|
||||
@@ -58,7 +76,11 @@ export class GiteaClient {
|
||||
* @returns {Promise<number>} 回應的 HTTP 狀態碼(成功刪除通常為 204)
|
||||
*/
|
||||
async deleteResource(url) {
|
||||
const res = await fetch(url, { method: 'DELETE', headers: this.headers })
|
||||
const res = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: this.headers,
|
||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
||||
})
|
||||
return res.status
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user