refactor(release-cleanup): 將清理邏輯由 bash 改寫為 Node.js #5

Closed
jiantw83 wants to merge 43 commits from refactor/nodejs-rewrite-20260626-103443 into develop
2 changed files with 8 additions and 4 deletions
Showing only changes of commit 353a6af7c3 - Show all commits
+4 -1
View File
2
@@ -26,7 +26,10 @@ import { section, info, warn } from './logger.js'
export function loadConfig(env = process.env) { export function loadConfig(env = process.env) {
section('參數檢查') section('參數檢查')
const serverUrl = env.GITEA_SERVER_URL // 去除結尾多餘的 /,避免後續拼接出 `https://host//api/v1/...` 這類錯誤路徑。
const rawServerUrl = env.GITEA_SERVER_URL
const serverUrl =
Ghost marked this conversation as resolved Outdated
Outdated
Review

嚴重等級🔵 建議
審查員:Bard
問題:日志信息的键值对缺少空格,排版不够整齐统一。
建議:建议加上空格以提升易读性:info('GITEA_TOKEN = [redacted]')

**嚴重等級**:🔵 建議 **審查員**:Bard **問題**:日志信息的键值对缺少空格,排版不够整齐统一。 **建議**:建议加上空格以提升易读性:`info('GITEA_TOKEN = [redacted]')`
typeof rawServerUrl === 'string' ? rawServerUrl.replace(/\/+$/, '') : rawServerUrl
const repository = env.GITEA_REPOSITORY const repository = env.GITEA_REPOSITORY
Ghost marked this conversation as resolved
Review

嚴重等級🔵 建議
審查員:Leo
問題:直接將 env 預設為 process.env,全域相依性可能導致難以追蹤的副作用。
建議:建議在複雜系統中,將環境變數讀取抽離為獨立的 Provider 或 ConfigFactory。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:直接將 env 預設為 process.env,全域相依性可能導致難以追蹤的副作用。 **建議**:建議在複雜系統中,將環境變數讀取抽離為獨立的 Provider 或 ConfigFactory。
Review

嚴重等級🔴 嚴重
審查員:Mage
問題:在 loadConfig 函式中,對 GITEA_SERVER_URL 的 replace(//+$/, '') 操作假設了輸入一定是 string。雖然隨後有 requireValue 和 requireUrl 的驗證,但如果 env.GITEA_SERVER_URL 是非字串(例如數字、陣列、物件),這裡的 replace 可能會拋出 TypeError。
建議:應在 replace 之前確保其型別為 string,或使用 optional chaining 以及更嚴格的類型防護。例如: typeof rawServerUrl === 'string' ? rawServerUrl.replace(//+$/, '') : rawServerUrl 已經做了檢查,但後續的 requireValue 檢查順序應確保傳入的是處理後的字串。

**嚴重等級**:🔴 嚴重 **審查員**:Mage **問題**:在 loadConfig 函式中,對 GITEA_SERVER_URL 的 replace(//+$/, '') 操作假設了輸入一定是 string。雖然隨後有 requireValue 和 requireUrl 的驗證,但如果 env.GITEA_SERVER_URL 是非字串(例如數字、陣列、物件),這裡的 replace 可能會拋出 TypeError。 **建議**:應在 replace 之前確保其型別為 string,或使用 optional chaining 以及更嚴格的類型防護。例如: `typeof rawServerUrl === 'string' ? rawServerUrl.replace(//+$/, '') : rawServerUrl` 已經做了檢查,但後續的 `requireValue` 檢查順序應確保傳入的是處理後的字串。
const keepCountRaw = env.KEEP_COUNT const keepCountRaw = env.KEEP_COUNT
Ghost marked this conversation as resolved
Review

嚴重等級🟡 警告
審查員:Assassin
問題:將環境變數值直接輸出至日誌,若 GITEA_SERVER_URL 等變數內容被注入惡意字元或過長,可能造成 Log Injection 或日誌系統資源耗盡。
建議:在輸出前進行 sanitization,移除控制字元並限制長度,類似於 gitea-client.js 中的 sanitizeBody

**嚴重等級**:🟡 警告 **審查員**:Assassin **問題**:將環境變數值直接輸出至日誌,若 `GITEA_SERVER_URL` 等變數內容被注入惡意字元或過長,可能造成 Log Injection 或日誌系統資源耗盡。 **建議**:在輸出前進行 sanitization,移除控制字元並限制長度,類似於 `gitea-client.js` 中的 `sanitizeBody`。
Review

嚴重等級🔵 建議
審查員:Maya
問題:雖然有 requireUrl 驗證,但在 loadConfig 中,對於 GITEA_SERVER_URL 的解析假設其為完整路徑。若環境變數提供的網址不含 api/v1 或路徑有變化,整合後的 releaseApiUrl 可能無效。
建議:建議在 loadConfig 中增加對 serverUrl 的處理,確保結尾沒有多餘的 /,避免拼接 API 路徑時產生類似 //api 的錯誤路徑。

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:雖然有 requireUrl 驗證,但在 loadConfig 中,對於 GITEA_SERVER_URL 的解析假設其為完整路徑。若環境變數提供的網址不含 api/v1 或路徑有變化,整合後的 releaseApiUrl 可能無效。 **建議**:建議在 loadConfig 中增加對 serverUrl 的處理,確保結尾沒有多餘的 /,避免拼接 API 路徑時產生類似 //api 的錯誤路徑。
const token = env.GITEA_TOKEN const token = env.GITEA_TOKEN
Ghost marked this conversation as resolved
Review

嚴重等級🔴 嚴重
審查員:Assassin
問題:僅驗證是否為 URL 格式,未檢查傳入的 GITEA_SERVER_URL 是否指向內部網路敏感資源(如 localhost, 169.254.169.254 等),這在容器化環境中可能導致 SSRF(伺服器端請求偽造)。
建議:在 validate.jsrequireUrl 中加入黑名單機制,禁止解析為內部 IP 位址或 loopback 位址。

**嚴重等級**:🔴 嚴重 **審查員**:Assassin **問題**:僅驗證是否為 URL 格式,未檢查傳入的 `GITEA_SERVER_URL` 是否指向內部網路敏感資源(如 localhost, 169.254.169.254 等),這在容器化環境中可能導致 SSRF(伺服器端請求偽造)。 **建議**:在 `validate.js` 的 `requireUrl` 中加入黑名單機制,禁止解析為內部 IP 位址或 loopback 位址。
4
+4 -3
View File
26
@@ -60,12 +60,13 @@ export async function cleanupReleases(client, config) {
for (const release of toDelete) { for (const release of toDelete) {
const { id, tag_name: tag, name } = release const { id, tag_name: tag, name } = release
if (isEmptyOrNull(id)) { // 要求 id 為正整數(Gitea release id 本即正整數);非整數一律略過,不僅依賴 URL 編碼防護。
warn(`略過沒有 id 的成品: ${tag} (${name})`) if (isEmptyOrNull(id) || !Number.isInteger(Number(id)) || Number(id) <= 0) {
Ghost marked this conversation as resolved
Review

嚴重等級🟡 警告
審查員:Assassin
問題:雖然有 encodeURIComponent,但直接將 id 拼接至 URL 是危險操作。若 id 未經妥善驗證,攻擊者可能試圖透過特殊字元擾亂 API 路徑。
建議:建議確保 id 在進入此函數前,已驗證為預期的整數類型或符合嚴格格式的字串,不要完全依賴 encodeURIComponent 來防範所有注入可能。

**嚴重等級**:🟡 警告 **審查員**:Assassin **問題**:雖然有 encodeURIComponent,但直接將 id 拼接至 URL 是危險操作。若 id 未經妥善驗證,攻擊者可能試圖透過特殊字元擾亂 API 路徑。 **建議**:建議確保 id 在進入此函數前,已驗證為預期的整數類型或符合嚴格格式的字串,不要完全依賴 encodeURIComponent 來防範所有注入可能。
warn(`略過沒有有效 id 的成品: ${tag} (${name})`)
continue continue
} }
// id 做編碼,避免非預期內容被拼接進 URL(防路徑穿越);正常數值 id 編碼後不變 // id 已驗證為正整數;仍對其編碼作為縱深防禦
const url = `${config.releaseApiUrl}/${encodeURIComponent(id)}` const url = `${config.releaseApiUrl}/${encodeURIComponent(id)}`
info(`DELETE ${tag} (${name})`) info(`DELETE ${tag} (${name})`)