feat/文件整理
develop
0eb30cf
🔍 服務:cliproxyapi 模型:auto
本次審查(cliproxyapi / auto,共 7 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(CLIProxyAPI 不提供帳號額度資訊)
@@ -11,1 +11,4 @@
model:
description: '使用的 AI 模型'
required: false
runs:
嚴重等級:🟡 警告 審查員:Assassin 問題:AI 模型參數(model)為使用者輸入但未驗證。攻擊者可透過 PR workflow 傳入任意字符串,縱然後續經 JSON 序列化理論上應轉義,仍增加了攻擊面且難以追蹤輸入來源。 建議:在 action.yml 中對 model 輸入進行描述性限制(說明只接受特定格式),並在 src/config.js 的 getLLMConfig() 加上白名單驗證或正則表達式檢查,拒絕包含特殊字符的模型名稱(如單引號、反斜線、括號等)。例:/^[a-zA-Z0-9._-]+$/。
model
getLLMConfig()
/^[a-zA-Z0-9._-]+$/
@@ -1,4 +1,11 @@
#!/bin/sh
# ============================================================================
嚴重等級:🟡 警告 審查員:Bard 問題:進入點腳本一開頭就塞入固定更新時間與裝飾性框線,資訊價值很低,卻會讓每次重生產都留下無意義的 diff 雜訊。 建議:移除這種會過期的時間戳註解,只保留真正需要提醒讀者的簡短說明即可。
@@ -0,0 +1,1703 @@
# AI Code Review
更新時間:2026/08/07 13:51:53
嚴重等級:🟡 警告 審查員:Bard 問題:這份 README 已經長成機械化的 API 編目,還把時間戳與大量硬編碼連結一起寫進來,讓主文件變得又厚又脆,讀者很難快速抓到重點。 建議:把 README 收斂成專案摘要、安裝方式與使用入口;細部 API 文件另放獨立文件或改成可生成的 docs,避免主文件膨脹成資料堆。
@@ -213,10 +235,28 @@ function toReviewComment(f) {
}
嚴重等級:🟡 警告 審查員:Bard 問題:postFindingsReview 這段 JSDoc 太像流程筆記,不像 API 說明。@param、@remarks、使用情境 與多層降級敘事一路堆疊,重點被枝節埋掉,閱讀節奏很不乾淨。 建議:把註解壓縮回最必要的契約說明:用途、參數、回傳與例外即可;降級順序和測試注入細節留給實作內的短註解。
postFindingsReview
@param
@remarks
使用情境
@@ -371,7 +413,14 @@ function extractFileDiff(diff, file) {
/**
嚴重等級:🟡 警告 審查員:Bard 問題:resolveMissingLineNumbers 的註解把行為、邊界條件、併發設定與人工備註全揉成一段,語氣也從說明一路滑到審查心得,讀起來有點散、有點吵。 建議:把說明拆短,保留輸入、輸出與副作用三件事即可;如果某些設計值得提醒,也應縮成一句附註,不要塞進主體敘述。
resolveMissingLineNumbers
@@ -111,0 +170,4 @@
{ role: 'user', content: prompt },
],
temperature: 0,
stream: false,
嚴重等級:🔵 建議 審查員:Assassin 問題:HTTP request body 中的 model 欄位現在允許為 null(由上游 getLLMConfig() 傳入),導致該欄位的存在性由輸入決定。若 API 伺服器對缺少 model 欄位與 model: null 的處理邏輯不同,可能產生非預期的行為切換(例如自動選擇與使用者預期模型不符的模型版本)。 建議:在 src/llm.js 的 runProxyAPI() 中明確文檔化 model: null 時的 API 行為,或在構造 body 前透過 getLLMConfig() 的驗證確保 model 值的一致性。若允許自動選擇,應於 log 與回應中清楚標示使用了自動選擇(目前已在 main.js 中以 modelLabel 處理,但建議同步至 API 層確認)。
model: null
runProxyAPI()
modelLabel
@@ -3,0 +5,4 @@
* @param {Date} [date] - 要格式化的時間點;省略時使用呼叫當下的系統時間。
* @returns {string} 例如 `2026/08/07 12:39:43`。
*/
function formatTimestamp(date = new Date()) {
嚴重等級:🔵 建議 審查員:Bard 問題:這裡用 en-CA 來拼台灣時區時間字串,技法不算錯,但對讀者很不直觀。看到 Asia/Taipei 卻搭配 en-CA,第一眼會先懷疑這是不是某種繞路寫法。 建議:改用更直白的格式化方式,例如手動補零組字串,或至少把這個 locale 選擇的用意明講,讓 helper 的意圖一眼可懂。
en-CA
Asia/Taipei
@@ -54,1 +56,4 @@
* 目前程式碼中有 3 個 exit 1 呼叫點(未設定 CLIProxyAPI、取 diff 失敗、所有角色分析皆失敗)
* 退出前未呼叫 `section('Pipeline 結束')`,與其餘 exit 點不一致,會少一行收尾分隔線,
* 是否為刻意設計尚需人工確認。
嚴重等級:🟡 警告 審查員:Bard 問題:這段註解直接寫出『目前程式碼中有 3 個 exit 1 呼叫點』,把瞬時的實作現況硬塞進長期註解,過幾次重構就會先壞掉,徒增維護負擔。 建議:刪掉這種會隨流程變動而失真的數量型描述;若真要提醒收尾差異,改成更穩定的概念性說明即可。
本次審查(cliproxyapi / auto,共 21 次呼叫)
description: '使用的 AI 模型,僅允許英數字、點、底線與連字號'
嚴重等級:🟡 警告 審查員:Assassin 問題:action.yml 中新增的 inputs.model 沒有在 GitHub Actions 層面進行輸入驗證。雖然描述寫著「僅允許英數字、點、底線與連字號」,但使用者可以提供任意字符(如 gpt-4; rm -rf /),這些惡意輸入會先被寫入環境變數 CLI_PROXY_API_MODEL,才在 Node.js 代碼中被驗證。違反了「最小信任原則」。 建議:在 action.yml 中的 inputs.model 新增驗證限制(GitHub Actions 層面無原生驗證機制,但可在文檔中強調風險,並確保 Node.js 驗證實作完備)。或改為使用 choices 列表限制可選值。目前的 Node.js 驗證雖然有效,但應在 GitHub Actions 文檔中明確說明:只有英數字、點、底線、連字號的 model 值才會被接受,其他值會被拒絕並導致工作流失敗。
gpt-4; rm -rf /
CLI_PROXY_API_MODEL
choices
@@ -11,2 +12,4 @@
using: 'docker'
嚴重等級:🟡 警告 審查員:Bard 問題:把 Docker Action 的 image 參照改成小寫 dockerfile,讓原本業界慣用的 Dockerfile 檔名失去辨識度;這種大小寫改動會讓人讀配置時多停一下,也讓專案風格顯得不一致。 建議:把檔名與 action.yml 的 runs.image 都改回慣用的 Dockerfile,維持 Docker 生態的標準寫法。
dockerfile
Dockerfile
action.yml
runs.image
嚴重等級:🟡 警告 審查員:Bard 問題:新文件採用小寫 readme.md,和倉庫中常見的 README.md 命名慣例不合。這種只差大小寫的命名,最容易在查找與瀏覽時破壞一致感。 建議:改名為 README.md,讓入口文件維持一眼可辨的標準名稱。
readme.md
README.md
@@ -33,2 +34,4 @@
* {@link postNewCriticalComments} 組裝 comment 內文使用。
* 使用情境:任何要把一批 findings 呈現成單一 Markdown 表格的地方,先篩好要顯示的子集合再呼叫本函式。
function buildTable(findings) {
嚴重等級:🟡 警告 審查員:Mage 問題:buildTable 函式在呼叫 findings.map() 前無防呆檢查,若 findings 為 null/undefined 會拋出 TypeError。文件已提及此問題但函式本體未修正 建議:在 .map() 呼叫前加入 if (!Array.isArray(findings)) findings = []; 或改用可選鏈語法,確保即使傳入無效值也能優雅降級
if (!Array.isArray(findings)) findings = [];
@@ -77,0 +87,4 @@
* 產生單一 finding 的行內(inline)review comment 內文:等級/審查員/建議三行。
*
* @param {{ level?: string, role?: string, suggestion?: string }} f 單筆審查問題物件。
嚴重等級:🟡 警告 審查員:Mage 問題:inlineCommentBody 函式若 f.role 或 f.suggestion 為 undefined,會直接內嵌 undefined 字樣到輸出字串,產生 '等級:xxx\n審查員:undefined\n建議:undefined' 的破損註解 建議:在組字前加檢查:const role = f.role || 'AI Review'; const suggestion = f.suggestion || ''; 確保回傳值不含 undefined 字面值
const role = f.role || 'AI Review'; const suggestion = f.suggestion || '';
@@ -273,1 +312,3 @@
* 發布所有舊問題 comment(一次發布,依等級排序)
* 發布所有舊問題的彙總 comment(一次性發布一則一般 comment,不含行內標註)。
* @param {Array<{ is_new?: boolean, level?: string }>} findings 審查問題陣列;
嚴重等級:🟡 警告 審查員:Bard 問題:這段 JSDoc 連到不存在的 newFindingsOnly,斷鏈的 {@link} 會讓文件閱讀時突然失聲;同時還把判定差異寫得過於旁白化,讓主註解變得冗長。 建議:把 cross-reference 換成實際存在的符號,或直接刪掉;差異說明則濃縮成一句話,保留重點即可。
newFindingsOnly
{@link}
@@ -43,3 +43,4 @@
export const FINDINGS_PATH = '.gitea/ai-review/findings.json';
export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';
const MODEL_NAME_RE = /^[A-Za-z0-9._-]+$/;
嚴重等級:🔵 建議 審查員:Assassin 問題:正則表達式 MODEL_NAME_RE = /^[A-Za-z0-9._-]+$/ 不允許 / 字符。某些合法的模型名稱格式(如 openrouter/openai/gpt-4o 或 providers/openai/models/gpt-4o)會被拒絕,導致功能受限。雖然不是直接的安全漏洞,但可能造成合法請求被誤判為異常。 建議:評估是否需要在正則表達式中允許 / 字符。若允許,應同時確保不會引入新的安全風險(例如路徑穿越攻擊)。改為 /^[A-Za-z0-9._/-]+$/ 並增加單元測試確認邊界情況。
MODEL_NAME_RE = /^[A-Za-z0-9._-]+$/
/
openrouter/openai/gpt-4o
providers/openai/models/gpt-4o
/^[A-Za-z0-9._/-]+$/
@@ -330,14 +348,25 @@ export function mergeFindings(oldFindings, newFindings) {
嚴重等級:🔵 建議 審查員:Mage 問題:mergeFindings 用 suggestion 前 50 字作為 key 的一部分進行去重。若兩個 findings 的 role 與 location 相同但 suggestion 在第 50 字之後才出現差異,會被誤判為重複而遭移除 建議:考慮是否改用完整 suggestion 或增加其他識別字段(如 problem)來組成 key,確保去重不會誤刪本質不同的問題
@@ -338,3 +361,3 @@
* AI 呼叫失敗時的統一降級處理
* AI 呼叫失敗時的統一降級處理:記錄警告訊息後原樣回傳 findings(不做任何篩選),
嚴重等級:🔵 建議 審查員:Mage 問題:sortByLevel 使用 LEVELS.indexOf() 排序,級別不在 ['critical','warning','info'] 中的項目因 indexOf 回傳 -1 而被排到 critical 之前(最前面),此邊界行為是否為預期設計不明確 建議:在文件或代碼中明確說明未知級別項目的預期排序位置,或改用顯式的條件判斷以提升代碼可讀性
@@ -571,8 +656,13 @@ async function judgeFindingIsFalsePositive(finding, defender, exclusionHint, cha
嚴重等級:🟡 警告 審查員:Mage 問題:applyExclusions 的比對邏輯在 (locationMatches && roleMatches && (textMatches || ...)) 中,若排除規則只指定 filePath 不指定 role,會產生「該檔案內所有角色的問題都被排除」的非預期行為;若只指定 role 不指定 filePath,則「該角色所有檔案的問題都被排除」。此為對稱性缺陷 建議:重新檢視比對邏輯意圖:若欲實現「指定 filePath 時自動不檢查 role」的設計,需在文件中明確說明此為刻意設計;若非刻意,應改為 (locationMatches || !exclusion.filePath) && (roleMatches || !exclusion.role) && (textMatches || ...),確保每個維度皆能獨立篩選
@@ -26,6 +30,26 @@ export async function mapWithConcurrency(items, limit, fn) {
const n = Number(limit);
const workers = (!Number.isFinite(n) || n <= 0) ? list.length : Math.min(n, list.length);
嚴重等級:🔵 建議 審查員:Bard 問題:mapWithConcurrency 內部工作者 run() 的註解太像設計文件,對 cursor、Promise.all 行為、背景工作都展開長篇解釋,視覺重量遠超過程式本身。 建議:把這段縮成一兩句重點註解,保留「限制併發、保序寫入」即可,其餘執行細節交回外層函式說明。
mapWithConcurrency
run()
cursor
Promise.all
@@ -105,0 +151,4 @@
* 僅使用 `apiKeys[0]`;`model` 可省略,省略時交由 CLIProxyAPI 自動選擇。
* @param {string} prompt - 送給 API 的完整 prompt 內容,會作為 user 訊息內容;
* HTTP 層的 system 訊息為固定的通用指示,與 prompt 內可能內嵌的 `<system>` 內容無關。
* @returns {Promise<any>} API 回應的原始資料物件(`resp.data`),並非純文字;
嚴重等級:🔵 建議 審查員:Rogue 問題:runProxyAPI() 中 body 物件先建立後再條件性添加 model 屬性;若此函式在併發量大的場景反覆呼叫,每次都會新建完整物件結構 建議:改用 Object.assign() 或 const body = { messages: [...], temperature: 0, stream: false, ...(model && { model }) },減少不必要的中間物件建立步驟
@@ -105,1 +158,4 @@
* @remarks 逾時與輸出上限由環境變數 `AI_ASSISTANT_TIMEOUT_MS`/`AI_ASSISTANT_MAX_BUFFER`
* 控制,預設值為 15 分鐘/20 MB。
* @remarks 使用 `getInsecureHttpsAgent()`(停用 TLS 憑證驗證),適用內部自簽憑證環境。
嚴重等級:🟡 警告 審查員:Mage 問題:summarizeApiError 函式內存取 e.stderr 與 e.stdout 時未使用可選鏈,若 e 為 null 或 undefined,會拋出 TypeError 而非優雅容錯 建議:改用可選鏈:const stderr = e?.stderr || '' 與 const stdout = e?.stdout || '',或在函式開頭加入 if (!e) return String(e); 早期退出
const stderr = e?.stderr || ''
const stdout = e?.stdout || ''
if (!e) return String(e);
@@ -3,0 +4,4 @@
嚴重等級:🟡 警告 審查員:Rogue 問題:formatTimestamp() 每次調用都新建 Intl.DateTimeFormat 實例,加上 formatToParts() 與 Object.fromEntries() 轉換,高頻日誌場景下重複成本大;而日誌函式會在 section/step/line/input/output/result/ok/warn/error 等多處調用,累積開銷明顯 建議:將 Intl.DateTimeFormat 快取為模組層級單例(const formatter = new Intl.DateTimeFormat(...)),或改用更輕量的時間格式化方式(例如直接用 Date 方法),避免每條日誌都重複實例化
@@ -3,0 +15,4 @@
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
}).formatToParts(date);
嚴重等級:🔵 建議 審查員:Rogue 問題:formatToParts() 後用 Object.fromEntries(parts.map(...)) 進行雙次陣列與物件轉換,再拼字串;格式化操作偏複雜,對日誌輸出這種高頻操作成本偏高 建議:改用 reduce() 直接在一次遍歷內組出 map 物件,或改寫為單一模板字符串拼接,避免中間陣列轉換
@@ -3,0 +16,4 @@
const map = Object.fromEntries(parts.map((p) => [p.type, p.value]));
嚴重等級:🔵 建議 審查員:Assassin 問題:formatTimestamp 函數依賴 Intl.DateTimeFormat.formatToParts 的實現細節。若回應結構不符預期,map.year、map.month 等會是 undefined,導致日誌中顯示 undefined 字樣。雖然不影響安全性,但可能造成日誌混亂及除錯困難。 建議:加強容錯處理。在存取 map.year 等屬性前先驗證其存在性;或改用更穩定的日期格式化方式(如 new Date().toISOString())。同時增加單元測試,確保在異常情況下(例如不同的語言環境或舊版本瀏覽器)仍能產生正確的日誌格式。
map.year
map.month
undefined
new Date().toISOString()
@@ -94,5 +94,5 @@
return [...groups.values()].map(g => ({ ...g, thread: g.bodies.join('\n---\n') }));
/** codeWindow 預設的上下文行數(目標行上下各取幾行)。 */
嚴重等級:🔵 建議 審查員:Mage 問題:groupConversations 在設置 botFinding 時用 botFindings[0],若該對話的 botFindings 陣列為空,botFinding 會為 undefined。此設計雖有文件說明是為相容舊邏輯,但下游代碼仍需確保可安全處理 undefined 值 建議:在文件中明確註記 botFinding 可為 undefined,並在此函式或其呼叫端加入明確的 null 檢查,或改用 botFinding: botFindings.length > 0 ? botFindings[0] : null 以更清晰地表達意圖
botFindings[0]
botFinding: botFindings.length > 0 ? botFindings[0] : null
本次審查(cliproxyapi / auto,共 9 次呼叫)
@@ -0,0 +6,4 @@
| 專案名稱 | 專案描述 |
| --- | --- |
| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src) | Gitea Docker 容器 action:對 PR 的 diff 派多個角色進行 AI 程式碼審查,產生 findings 並依對話收斂、排除規則與 AI 誤報裁決收斂結果;負責 Gitea PR API(diff/comment/review/resolve)串接、CLIProxyAPI 對話與 usage/額度統計、git clone/commit/push 持久化 findings,以及執行前的 token/LLM/git 遠端前置驗證。 |
嚴重等級:🔵 建議 審查員:Assassin 問題:這份新增文件把內部 Gitea 網域與完整倉庫路徑直接寫進專案內容。只要文件被外部看見,攻擊者就能先掌握內部服務命名、URL 模式與專案結構,降低枚舉、釣魚與後續橫向移動的成本。 建議:如果這份文件有外部可見的可能,請把內網主機名與完整路徑改成相對路徑或 placeholder,並把只限內部使用的操作細節移到不對外公開的位置。
@@ -42,23 +42,28 @@ export const LLM_PROVIDER = 'cliproxyapi';
嚴重等級:🟡 警告 審查員:Assassin 問題:這裡只限制字元種類,卻還放行 . 與 /,因此像 ../foo、foo/../../bar 這類路徑式字串仍可通過。攻擊者只要能控制 inputs.model 或 CLI_PROXY_API_MODEL,就能把惡意 model 值送進 CLIProxyAPI;若後端拿 model 名稱去拼路徑、呼叫指令或做檔名查找,這個輸入就可能被拿來做路徑穿越或指令注入。 建議:不要只做字元白名單,應改成明確白名單比對可用模型 slug,並額外拒絕 ..、前導/結尾 /、連續 /、反斜線與控制字元;如果可行,直接用 /v1/models 回傳清單做嚴格選擇,而不是接受任意形狀的字串。
.
../foo
foo/../../bar
inputs.model
..
/v1/models
No dependencies set.
The note is not visible to the blocked user.
What changed
Validation
develop之間存在提交差異。Commit
0eb30cf🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(cliproxyapi / auto,共 7 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(CLIProxyAPI 不提供帳號額度資訊)
@@ -11,1 +11,4 @@model:description: '使用的 AI 模型'required: falseruns:嚴重等級:🟡 警告
審查員:Assassin
問題:AI 模型參數(
model)為使用者輸入但未驗證。攻擊者可透過 PR workflow 傳入任意字符串,縱然後續經 JSON 序列化理論上應轉義,仍增加了攻擊面且難以追蹤輸入來源。建議:在 action.yml 中對
model輸入進行描述性限制(說明只接受特定格式),並在 src/config.js 的getLLMConfig()加上白名單驗證或正則表達式檢查,拒絕包含特殊字符的模型名稱(如單引號、反斜線、括號等)。例:/^[a-zA-Z0-9._-]+$/。@@ -1,4 +1,11 @@#!/bin/sh# ============================================================================嚴重等級:🟡 警告
審查員:Bard
問題:進入點腳本一開頭就塞入固定更新時間與裝飾性框線,資訊價值很低,卻會讓每次重生產都留下無意義的 diff 雜訊。
建議:移除這種會過期的時間戳註解,只保留真正需要提醒讀者的簡短說明即可。
@@ -0,0 +1,1703 @@# AI Code Review更新時間:2026/08/07 13:51:53嚴重等級:🟡 警告
審查員:Bard
問題:這份 README 已經長成機械化的 API 編目,還把時間戳與大量硬編碼連結一起寫進來,讓主文件變得又厚又脆,讀者很難快速抓到重點。
建議:把 README 收斂成專案摘要、安裝方式與使用入口;細部 API 文件另放獨立文件或改成可生成的 docs,避免主文件膨脹成資料堆。
@@ -213,10 +235,28 @@ function toReviewComment(f) {}嚴重等級:🟡 警告
審查員:Bard
問題:
postFindingsReview這段 JSDoc 太像流程筆記,不像 API 說明。@param、@remarks、使用情境與多層降級敘事一路堆疊,重點被枝節埋掉,閱讀節奏很不乾淨。建議:把註解壓縮回最必要的契約說明:用途、參數、回傳與例外即可;降級順序和測試注入細節留給實作內的短註解。
@@ -371,7 +413,14 @@ function extractFileDiff(diff, file) {/**嚴重等級:🟡 警告
審查員:Bard
問題:
resolveMissingLineNumbers的註解把行為、邊界條件、併發設定與人工備註全揉成一段,語氣也從說明一路滑到審查心得,讀起來有點散、有點吵。建議:把說明拆短,保留輸入、輸出與副作用三件事即可;如果某些設計值得提醒,也應縮成一句附註,不要塞進主體敘述。
@@ -111,0 +170,4 @@{ role: 'user', content: prompt },],temperature: 0,stream: false,嚴重等級:🔵 建議
審查員:Assassin
問題:HTTP request body 中的
model欄位現在允許為 null(由上游getLLMConfig()傳入),導致該欄位的存在性由輸入決定。若 API 伺服器對缺少model欄位與model: null的處理邏輯不同,可能產生非預期的行為切換(例如自動選擇與使用者預期模型不符的模型版本)。建議:在 src/llm.js 的
runProxyAPI()中明確文檔化model: null時的 API 行為,或在構造 body 前透過getLLMConfig()的驗證確保 model 值的一致性。若允許自動選擇,應於 log 與回應中清楚標示使用了自動選擇(目前已在 main.js 中以modelLabel處理,但建議同步至 API 層確認)。@@ -3,0 +5,4 @@* @param {Date} [date] - 要格式化的時間點;省略時使用呼叫當下的系統時間。* @returns {string} 例如 `2026/08/07 12:39:43`。*/function formatTimestamp(date = new Date()) {嚴重等級:🔵 建議
審查員:Bard
問題:這裡用
en-CA來拼台灣時區時間字串,技法不算錯,但對讀者很不直觀。看到Asia/Taipei卻搭配en-CA,第一眼會先懷疑這是不是某種繞路寫法。建議:改用更直白的格式化方式,例如手動補零組字串,或至少把這個 locale 選擇的用意明講,讓 helper 的意圖一眼可懂。
@@ -54,1 +56,4 @@* 目前程式碼中有 3 個 exit 1 呼叫點(未設定 CLIProxyAPI、取 diff 失敗、所有角色分析皆失敗)* 退出前未呼叫 `section('Pipeline 結束')`,與其餘 exit 點不一致,會少一行收尾分隔線,* 是否為刻意設計尚需人工確認。*/嚴重等級:🟡 警告
審查員:Bard
問題:這段註解直接寫出『目前程式碼中有 3 個 exit 1 呼叫點』,把瞬時的實作現況硬塞進長期註解,過幾次重構就會先壞掉,徒增維護負擔。
建議:刪掉這種會隨流程變動而失真的數量型描述;若真要提醒收尾差異,改成更穩定的概念性說明即可。
🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(cliproxyapi / auto,共 21 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(CLIProxyAPI 不提供帳號額度資訊)
@@ -11,1 +11,4 @@model:description: '使用的 AI 模型,僅允許英數字、點、底線與連字號'required: falseruns:嚴重等級:🟡 警告
審查員:Assassin
問題:action.yml 中新增的 inputs.model 沒有在 GitHub Actions 層面進行輸入驗證。雖然描述寫著「僅允許英數字、點、底線與連字號」,但使用者可以提供任意字符(如
gpt-4; rm -rf /),這些惡意輸入會先被寫入環境變數CLI_PROXY_API_MODEL,才在 Node.js 代碼中被驗證。違反了「最小信任原則」。建議:在 action.yml 中的 inputs.model 新增驗證限制(GitHub Actions 層面無原生驗證機制,但可在文檔中強調風險,並確保 Node.js 驗證實作完備)。或改為使用
choices列表限制可選值。目前的 Node.js 驗證雖然有效,但應在 GitHub Actions 文檔中明確說明:只有英數字、點、底線、連字號的 model 值才會被接受,其他值會被拒絕並導致工作流失敗。@@ -11,2 +12,4 @@description: '使用的 AI 模型,僅允許英數字、點、底線與連字號'required: falseruns:using: 'docker'嚴重等級:🟡 警告
審查員:Bard
問題:把 Docker Action 的 image 參照改成小寫
dockerfile,讓原本業界慣用的Dockerfile檔名失去辨識度;這種大小寫改動會讓人讀配置時多停一下,也讓專案風格顯得不一致。建議:把檔名與
action.yml的runs.image都改回慣用的Dockerfile,維持 Docker 生態的標準寫法。@@ -0,0 +1,1703 @@# AI Code Review嚴重等級:🟡 警告
審查員:Bard
問題:新文件採用小寫
readme.md,和倉庫中常見的README.md命名慣例不合。這種只差大小寫的命名,最容易在查找與瀏覽時破壞一致感。建議:改名為
README.md,讓入口文件維持一眼可辨的標準名稱。@@ -33,2 +34,4 @@* {@link postNewCriticalComments} 組裝 comment 內文使用。* 使用情境:任何要把一批 findings 呈現成單一 Markdown 表格的地方,先篩好要顯示的子集合再呼叫本函式。*/function buildTable(findings) {嚴重等級:🟡 警告
審查員:Mage
問題:buildTable 函式在呼叫 findings.map() 前無防呆檢查,若 findings 為 null/undefined 會拋出 TypeError。文件已提及此問題但函式本體未修正
建議:在 .map() 呼叫前加入
if (!Array.isArray(findings)) findings = [];或改用可選鏈語法,確保即使傳入無效值也能優雅降級@@ -77,0 +87,4 @@/*** 產生單一 finding 的行內(inline)review comment 內文:等級/審查員/建議三行。** @param {{ level?: string, role?: string, suggestion?: string }} f 單筆審查問題物件。嚴重等級:🟡 警告
審查員:Mage
問題:inlineCommentBody 函式若 f.role 或 f.suggestion 為 undefined,會直接內嵌 undefined 字樣到輸出字串,產生 '等級:xxx\n審查員:undefined\n建議:undefined' 的破損註解
建議:在組字前加檢查:
const role = f.role || 'AI Review'; const suggestion = f.suggestion || '';確保回傳值不含 undefined 字面值@@ -273,1 +312,3 @@* 發布所有舊問題 comment(一次發布,依等級排序)* 發布所有舊問題的彙總 comment(一次性發布一則一般 comment,不含行內標註)。** @param {Array<{ is_new?: boolean, level?: string }>} findings 審查問題陣列;嚴重等級:🟡 警告
審查員:Bard
問題:這段 JSDoc 連到不存在的
newFindingsOnly,斷鏈的{@link}會讓文件閱讀時突然失聲;同時還把判定差異寫得過於旁白化,讓主註解變得冗長。建議:把 cross-reference 換成實際存在的符號,或直接刪掉;差異說明則濃縮成一句話,保留重點即可。
@@ -43,3 +43,4 @@export const FINDINGS_PATH = '.gitea/ai-review/findings.json';export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';const MODEL_NAME_RE = /^[A-Za-z0-9._-]+$/;嚴重等級:🔵 建議
審查員:Assassin
問題:正則表達式
MODEL_NAME_RE = /^[A-Za-z0-9._-]+$/不允許/字符。某些合法的模型名稱格式(如openrouter/openai/gpt-4o或providers/openai/models/gpt-4o)會被拒絕,導致功能受限。雖然不是直接的安全漏洞,但可能造成合法請求被誤判為異常。建議:評估是否需要在正則表達式中允許
/字符。若允許,應同時確保不會引入新的安全風險(例如路徑穿越攻擊)。改為/^[A-Za-z0-9._/-]+$/並增加單元測試確認邊界情況。@@ -330,14 +348,25 @@ export function mergeFindings(oldFindings, newFindings) {}嚴重等級:🔵 建議
審查員:Mage
問題:mergeFindings 用 suggestion 前 50 字作為 key 的一部分進行去重。若兩個 findings 的 role 與 location 相同但 suggestion 在第 50 字之後才出現差異,會被誤判為重複而遭移除
建議:考慮是否改用完整 suggestion 或增加其他識別字段(如 problem)來組成 key,確保去重不會誤刪本質不同的問題
@@ -338,3 +361,3 @@/*** AI 呼叫失敗時的統一降級處理* AI 呼叫失敗時的統一降級處理:記錄警告訊息後原樣回傳 findings(不做任何篩選),嚴重等級:🔵 建議
審查員:Mage
問題:sortByLevel 使用 LEVELS.indexOf() 排序,級別不在 ['critical','warning','info'] 中的項目因 indexOf 回傳 -1 而被排到 critical 之前(最前面),此邊界行為是否為預期設計不明確
建議:在文件或代碼中明確說明未知級別項目的預期排序位置,或改用顯式的條件判斷以提升代碼可讀性
@@ -571,8 +656,13 @@ async function judgeFindingIsFalsePositive(finding, defender, exclusionHint, cha}/**嚴重等級:🟡 警告
審查員:Mage
問題:applyExclusions 的比對邏輯在 (locationMatches && roleMatches && (textMatches || ...)) 中,若排除規則只指定 filePath 不指定 role,會產生「該檔案內所有角色的問題都被排除」的非預期行為;若只指定 role 不指定 filePath,則「該角色所有檔案的問題都被排除」。此為對稱性缺陷
建議:重新檢視比對邏輯意圖:若欲實現「指定 filePath 時自動不檢查 role」的設計,需在文件中明確說明此為刻意設計;若非刻意,應改為 (locationMatches || !exclusion.filePath) && (roleMatches || !exclusion.role) && (textMatches || ...),確保每個維度皆能獨立篩選
@@ -26,6 +30,26 @@ export async function mapWithConcurrency(items, limit, fn) {const n = Number(limit);const workers = (!Number.isFinite(n) || n <= 0) ? list.length : Math.min(n, list.length);嚴重等級:🔵 建議
審查員:Bard
問題:
mapWithConcurrency內部工作者run()的註解太像設計文件,對cursor、Promise.all行為、背景工作都展開長篇解釋,視覺重量遠超過程式本身。建議:把這段縮成一兩句重點註解,保留「限制併發、保序寫入」即可,其餘執行細節交回外層函式說明。
@@ -105,0 +151,4 @@* 僅使用 `apiKeys[0]`;`model` 可省略,省略時交由 CLIProxyAPI 自動選擇。* @param {string} prompt - 送給 API 的完整 prompt 內容,會作為 user 訊息內容;* HTTP 層的 system 訊息為固定的通用指示,與 prompt 內可能內嵌的 `<system>` 內容無關。* @returns {Promise<any>} API 回應的原始資料物件(`resp.data`),並非純文字;嚴重等級:🔵 建議
審查員:Rogue
問題:runProxyAPI() 中 body 物件先建立後再條件性添加 model 屬性;若此函式在併發量大的場景反覆呼叫,每次都會新建完整物件結構
建議:改用 Object.assign() 或 const body = { messages: [...], temperature: 0, stream: false, ...(model && { model }) },減少不必要的中間物件建立步驟
@@ -105,1 +158,4 @@* @remarks 逾時與輸出上限由環境變數 `AI_ASSISTANT_TIMEOUT_MS`/`AI_ASSISTANT_MAX_BUFFER`* 控制,預設值為 15 分鐘/20 MB。* @remarks 使用 `getInsecureHttpsAgent()`(停用 TLS 憑證驗證),適用內部自簽憑證環境。*/嚴重等級:🟡 警告
審查員:Mage
問題:summarizeApiError 函式內存取 e.stderr 與 e.stdout 時未使用可選鏈,若 e 為 null 或 undefined,會拋出 TypeError 而非優雅容錯
建議:改用可選鏈:
const stderr = e?.stderr || ''與const stdout = e?.stdout || '',或在函式開頭加入if (!e) return String(e);早期退出@@ -3,0 +4,4 @@** @param {Date} [date] - 要格式化的時間點;省略時使用呼叫當下的系統時間。* @returns {string} 例如 `2026/08/07 12:39:43`。*/嚴重等級:🟡 警告
審查員:Rogue
問題:formatTimestamp() 每次調用都新建 Intl.DateTimeFormat 實例,加上 formatToParts() 與 Object.fromEntries() 轉換,高頻日誌場景下重複成本大;而日誌函式會在 section/step/line/input/output/result/ok/warn/error 等多處調用,累積開銷明顯
建議:將 Intl.DateTimeFormat 快取為模組層級單例(const formatter = new Intl.DateTimeFormat(...)),或改用更輕量的時間格式化方式(例如直接用 Date 方法),避免每條日誌都重複實例化
@@ -3,0 +15,4 @@minute: '2-digit',second: '2-digit',hourCycle: 'h23',}).formatToParts(date);嚴重等級:🔵 建議
審查員:Rogue
問題:formatToParts() 後用 Object.fromEntries(parts.map(...)) 進行雙次陣列與物件轉換,再拼字串;格式化操作偏複雜,對日誌輸出這種高頻操作成本偏高
建議:改用 reduce() 直接在一次遍歷內組出 map 物件,或改寫為單一模板字符串拼接,避免中間陣列轉換
@@ -3,0 +16,4 @@second: '2-digit',hourCycle: 'h23',}).formatToParts(date);const map = Object.fromEntries(parts.map((p) => [p.type, p.value]));嚴重等級:🔵 建議
審查員:Assassin
問題:formatTimestamp 函數依賴 Intl.DateTimeFormat.formatToParts 的實現細節。若回應結構不符預期,
map.year、map.month等會是undefined,導致日誌中顯示undefined字樣。雖然不影響安全性,但可能造成日誌混亂及除錯困難。建議:加強容錯處理。在存取
map.year等屬性前先驗證其存在性;或改用更穩定的日期格式化方式(如new Date().toISOString())。同時增加單元測試,確保在異常情況下(例如不同的語言環境或舊版本瀏覽器)仍能產生正確的日誌格式。@@ -94,5 +94,5 @@}return [...groups.values()].map(g => ({ ...g, thread: g.bodies.join('\n---\n') }));}/** codeWindow 預設的上下文行數(目標行上下各取幾行)。 */嚴重等級:🔵 建議
審查員:Mage
問題:groupConversations 在設置 botFinding 時用
botFindings[0],若該對話的 botFindings 陣列為空,botFinding 會為 undefined。此設計雖有文件說明是為相容舊邏輯,但下游代碼仍需確保可安全處理 undefined 值建議:在文件中明確註記 botFinding 可為 undefined,並在此函式或其呼叫端加入明確的 null 檢查,或改用
botFinding: botFindings.length > 0 ? botFindings[0] : null以更清晰地表達意圖🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(cliproxyapi / auto,共 9 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(CLIProxyAPI 不提供帳號額度資訊)
@@ -0,0 +6,4 @@| 專案名稱 | 專案描述 || --- | --- || [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src) | Gitea Docker 容器 action:對 PR 的 diff 派多個角色進行 AI 程式碼審查,產生 findings 並依對話收斂、排除規則與 AI 誤報裁決收斂結果;負責 Gitea PR API(diff/comment/review/resolve)串接、CLIProxyAPI 對話與 usage/額度統計、git clone/commit/push 持久化 findings,以及執行前的 token/LLM/git 遠端前置驗證。 |嚴重等級:🔵 建議
審查員:Assassin
問題:這份新增文件把內部 Gitea 網域與完整倉庫路徑直接寫進專案內容。只要文件被外部看見,攻擊者就能先掌握內部服務命名、URL 模式與專案結構,降低枚舉、釣魚與後續橫向移動的成本。
建議:如果這份文件有外部可見的可能,請把內網主機名與完整路徑改成相對路徑或 placeholder,並把只限內部使用的操作細節移到不對外公開的位置。
@@ -42,23 +42,28 @@ export const LLM_PROVIDER = 'cliproxyapi';export const FINDINGS_PATH = '.gitea/ai-review/findings.json';export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';嚴重等級:🟡 警告
審查員:Assassin
問題:這裡只限制字元種類,卻還放行
.與/,因此像../foo、foo/../../bar這類路徑式字串仍可通過。攻擊者只要能控制inputs.model或CLI_PROXY_API_MODEL,就能把惡意 model 值送進 CLIProxyAPI;若後端拿 model 名稱去拼路徑、呼叫指令或做檔名查找,這個輸入就可能被拿來做路徑穿越或指令注入。建議:不要只做字元白名單,應改成明確白名單比對可用模型 slug,並額外拒絕
..、前導/結尾/、連續/、反斜線與控制字元;如果可行,直接用/v1/models回傳清單做嚴格選擇,而不是接受任意形狀的字串。