Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33f1291a0f | |||
| cedcb04424 | |||
| 9d780788e9 |
@@ -351,5 +351,35 @@
|
|||||||
"location": "Dockerfile, app/git.js, app/gitea.js",
|
"location": "Dockerfile, app/git.js, app/gitea.js",
|
||||||
"suggestion": "此變更引入了新的代理(agent)相關路徑(例如 `.agents/` 和 `AGENTS.md`),並在 `Dockerfile` 的 `COPY` 指令、`app/git.js` 中的 `SYNC_PATHS`、`FORCE_SYNC_FILE_PATHS`、`SYNC_TREE_PATHS` 陣列,以及 `app/gitea.js` 的 `filterDiff` 陣列中重複添加了這些路徑。這種模式導致了程式碼重複,每次新增一個代理都需要手動修改多個檔案和多個列表,增加了維護成本和出錯的可能性。建議考慮引入一個集中的設定檔或機制,例如透過掃描特定目錄來動態生成這些路徑列表,以提高模組化和可擴展性。",
|
"suggestion": "此變更引入了新的代理(agent)相關路徑(例如 `.agents/` 和 `AGENTS.md`),並在 `Dockerfile` 的 `COPY` 指令、`app/git.js` 中的 `SYNC_PATHS`、`FORCE_SYNC_FILE_PATHS`、`SYNC_TREE_PATHS` 陣列,以及 `app/gitea.js` 的 `filterDiff` 陣列中重複添加了這些路徑。這種模式導致了程式碼重複,每次新增一個代理都需要手動修改多個檔案和多個列表,增加了維護成本和出錯的可能性。建議考慮引入一個集中的設定檔或機制,例如透過掃描特定目錄來動態生成這些路徑列表,以提高模組化和可擴展性。",
|
||||||
"is_new": true
|
"is_new": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Rex",
|
||||||
|
"location": "app/preflight.js:12",
|
||||||
|
"suggestion": "程式碼中根據 `GITEA_SKIP_TLS_VERIFY` 環境變數來禁用 TLS 憑證驗證 (`rejectUnauthorized: false`),這會使應用程式容易受到中間人 (Man-in-the-Middle, MITM) 攻擊。攻擊者可能在不被察覺的情況下攔截和修改與 Gitea 伺服器的通訊。建議移除此功能,或確保在任何生產環境中永不啟用。如果 Gitea 伺服器使用自簽憑證,應將其憑證加入信任儲存區,而非禁用驗證。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Leo",
|
||||||
|
"location": "app/preflight.js:56",
|
||||||
|
"suggestion": "函式 `verifyLLM` 處理了多種 LLM 供應商的驗證邏輯(Ollama、Claude、OpenAI 相容等),導致其長度較長且複雜度較高。建議將不同供應商的驗證邏輯拆分成獨立的輔助函式(例如 `_verifyOllama`、`_verifyOpenAICompatible`),以提高模組化程度和可讀性。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Zara",
|
||||||
|
"location": "app/preflight.js:70-82",
|
||||||
|
"suggestion": "在 `verifyLLM` 函式中,當配置了多個 LLM API Key 時,系統會依序嘗試驗證每個 Key,每個嘗試都有 30 秒的逾時時間。如果前幾個 Key 驗證失敗,這可能導致顯著的累積延遲。雖然這是為了找到一個可用的 Key,但若 Key 數量多且網路不穩定,可能會造成啟動時間過長。可以考慮縮短單次 Key 驗證的逾時時間,或在特定情況下提供更快的失敗機制。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Rex",
|
||||||
|
"location": "app/preflight.js:100",
|
||||||
|
"suggestion": "在記錄 LLM API 驗證失敗時,直接輸出了錯誤訊息 `e.message`。雖然通常情況下 `e.message` 不會包含敏感資訊,但為了最佳安全實踐,建議審查 LLM 服務提供商的錯誤訊息格式,確保其中不會意外洩漏 API 金鑰或其他敏感請求內容。若有疑慮,應對錯誤訊息進行消毒或僅記錄高層次的錯誤類型。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "app/preflight.js:30",
|
||||||
|
"suggestion": "在 `checkRequiredEnv`、`verifyGiteaToken` 和 `verifyCommentToken` 等函式中,預設參數直接引用了從 `config.js` 匯入的常數。雖然這在功能上可行,但為了提高程式碼的清晰度和一致性,建議考慮以下兩種方式之一:1. 將所有配置值作為明確的參數從呼叫端傳入。2. 讓函式直接從 `config.js` 模組中讀取這些值,而不是透過預設參數。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "app/preflight.js:107",
|
||||||
|
"suggestion": "在 `verifyLLM` 函數中,呼叫 `axios.post` 時缺少 `httpsAgent` 選項。這會導致即使設定了 `GITEA_SKIP_TLS_VERIFY`,LLM 的 API 請求仍可能因 TLS 憑證問題而失敗。請將 `httpsAgent` 傳遞給 `axios.post` 的選項物件,例如:`await axios.post(`${base}/chat/completions`, payload, { headers, timeout: 30000, httpsAgent });`"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,44 +1 @@
|
|||||||
[
|
[]
|
||||||
{
|
|
||||||
"level": "critical",
|
|
||||||
"role": "Rex",
|
|
||||||
"location": "app/preflight.js:12",
|
|
||||||
"suggestion": "程式碼中根據 `GITEA_SKIP_TLS_VERIFY` 環境變數來禁用 TLS 憑證驗證 (`rejectUnauthorized: false`),這會使應用程式容易受到中間人 (Man-in-the-Middle, MITM) 攻擊。攻擊者可能在不被察覺的情況下攔截和修改與 Gitea 伺服器的通訊。建議移除此功能,或確保在任何生產環境中永不啟用。如果 Gitea 伺服器使用自簽憑證,應將其憑證加入信任儲存區,而非禁用驗證。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Leo",
|
|
||||||
"location": "app/preflight.js:56",
|
|
||||||
"suggestion": "函式 `verifyLLM` 處理了多種 LLM 供應商的驗證邏輯(Ollama、Claude、OpenAI 相容等),導致其長度較長且複雜度較高。建議將不同供應商的驗證邏輯拆分成獨立的輔助函式(例如 `_verifyOllama`、`_verifyOpenAICompatible`),以提高模組化程度和可讀性。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Zara",
|
|
||||||
"location": "app/preflight.js:70-82",
|
|
||||||
"suggestion": "在 `verifyLLM` 函式中,當配置了多個 LLM API Key 時,系統會依序嘗試驗證每個 Key,每個嘗試都有 30 秒的逾時時間。如果前幾個 Key 驗證失敗,這可能導致顯著的累積延遲。雖然這是為了找到一個可用的 Key,但若 Key 數量多且網路不穩定,可能會造成啟動時間過長。可以考慮縮短單次 Key 驗證的逾時時間,或在特定情況下提供更快的失敗機制。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Maya",
|
|
||||||
"location": "app/preflight.test.js",
|
|
||||||
"suggestion": "`runPreflight` 函數是一個重要的協調器,它依賴於多個內部驗證函數。目前的測試 `runPreflight` 僅涵蓋了環境變數缺失導致的早期終止情況。請為 `runPreflight` 函數添加以下測試案例,以確保其行為的完整性:\n1. **完整成功路徑:** 模擬所有內部驗證(`checkRequiredEnv`, `verifyGiteaToken`, `verifyCommentToken`, `verifyRemoteAccess`, `verifyLLM`)都成功的情況,驗證 `runPreflight` 返回 `true`。\n2. **各個失敗點:** 針對每個內部驗證函數(除了第一個 `checkRequiredEnv`),添加一個測試案例,模擬該函數失敗而其之前的函數都成功的情況,驗證 `runPreflight` 返回 `false` 並在正確的步驟停止。\n3. **依賴模擬:** 為了使 `runPreflight` 的測試更具單元性,請考慮在 `preflight.test.js` 中模擬 `verifyRemoteAccess` 函數(從 `app/git.js` 導入),而不是讓它執行實際的 `git` 命令。這將使 `runPreflight` 的測試更穩定且獨立於 `git.js` 的實現細節。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "info",
|
|
||||||
"role": "Rex",
|
|
||||||
"location": "app/preflight.js:100",
|
|
||||||
"suggestion": "在記錄 LLM API 驗證失敗時,直接輸出了錯誤訊息 `e.message`。雖然通常情況下 `e.message` 不會包含敏感資訊,但為了最佳安全實踐,建議審查 LLM 服務提供商的錯誤訊息格式,確保其中不會意外洩漏 API 金鑰或其他敏感請求內容。若有疑慮,應對錯誤訊息進行消毒或僅記錄高層次的錯誤類型。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "info",
|
|
||||||
"role": "Aria",
|
|
||||||
"location": "app/preflight.js:30",
|
|
||||||
"suggestion": "在 `checkRequiredEnv`、`verifyGiteaToken` 和 `verifyCommentToken` 等函式中,預設參數直接引用了從 `config.js` 匯入的常數。雖然這在功能上可行,但為了提高程式碼的清晰度和一致性,建議考慮以下兩種方式之一:1. 將所有配置值作為明確的參數從呼叫端傳入。2. 讓函式直接從 `config.js` 模組中讀取這些值,而不是透過預設參數。",
|
|
||||||
"is_new": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
+13
-6
@@ -93,24 +93,31 @@ export async function verifyLLM() {
|
|||||||
* 集中執行所有驗證相關設定的前置檢查;全部通過回傳 true,任一失敗回傳 false。
|
* 集中執行所有驗證相關設定的前置檢查;全部通過回傳 true,任一失敗回傳 false。
|
||||||
* 僅做唯讀的認證/連線確認,不發布任何 comment。
|
* 僅做唯讀的認證/連線確認,不發布任何 comment。
|
||||||
*/
|
*/
|
||||||
export async function runPreflight(workspace = process.env.GITHUB_WORKSPACE || '/workspace') {
|
export async function runPreflight(workspace = process.env.GITHUB_WORKSPACE || '/workspace', deps = {}) {
|
||||||
|
const {
|
||||||
|
checkEnv = checkRequiredEnv,
|
||||||
|
verifyToken = verifyGiteaToken,
|
||||||
|
verifyComment = verifyCommentToken,
|
||||||
|
verifyRemote = verifyRemoteAccess,
|
||||||
|
verifyLLMFn = verifyLLM,
|
||||||
|
} = deps;
|
||||||
step('Step1.5', '前置驗證(驗證相關設定)');
|
step('Step1.5', '前置驗證(驗證相關設定)');
|
||||||
|
|
||||||
const env = checkRequiredEnv();
|
const env = checkEnv();
|
||||||
if (!env.ok) {
|
if (!env.ok) {
|
||||||
error(`缺少必要環境變數: ${env.missing.join(', ')}`);
|
error(`缺少必要環境變數: ${env.missing.join(', ')}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ok('必要環境變數齊全 (GITEA_TOKEN, GITEA_REPOSITORY, PR_NUMBER)');
|
ok('必要環境變數齊全 (GITEA_TOKEN, GITEA_REPOSITORY, PR_NUMBER)');
|
||||||
|
|
||||||
const gitea = await verifyGiteaToken();
|
const gitea = await verifyToken();
|
||||||
if (!gitea.ok) {
|
if (!gitea.ok) {
|
||||||
error(`GITEA_TOKEN 驗證失敗(無法讀取 repo ${GITEA_REPOSITORY}): ${gitea.error}`);
|
error(`GITEA_TOKEN 驗證失敗(無法讀取 repo ${GITEA_REPOSITORY}): ${gitea.error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ok(`GITEA_TOKEN 可讀取 repo ${GITEA_REPOSITORY}`);
|
ok(`GITEA_TOKEN 可讀取 repo ${GITEA_REPOSITORY}`);
|
||||||
|
|
||||||
const comment = await verifyCommentToken();
|
const comment = await verifyComment();
|
||||||
if (!comment.ok) {
|
if (!comment.ok) {
|
||||||
error(`GITEA_COMMENT_TOKEN 驗證失敗: ${comment.error}`);
|
error(`GITEA_COMMENT_TOKEN 驗證失敗: ${comment.error}`);
|
||||||
return false;
|
return false;
|
||||||
@@ -118,14 +125,14 @@ export async function runPreflight(workspace = process.env.GITHUB_WORKSPACE || '
|
|||||||
if (comment.skipped) line('未提供 GITEA_COMMENT_TOKEN,comment 將沿用 GITEA_TOKEN');
|
if (comment.skipped) line('未提供 GITEA_COMMENT_TOKEN,comment 將沿用 GITEA_TOKEN');
|
||||||
else ok('GITEA_COMMENT_TOKEN 可用');
|
else ok('GITEA_COMMENT_TOKEN 可用');
|
||||||
|
|
||||||
const remote = verifyRemoteAccess(workspace);
|
const remote = verifyRemote(workspace);
|
||||||
if (!remote.ok) {
|
if (!remote.ok) {
|
||||||
error(`git push 認證/連線驗證失敗(ls-remote): ${remote.error}`);
|
error(`git push 認證/連線驗證失敗(ls-remote): ${remote.error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ok('git remote 認證可用(ls-remote 成功)');
|
ok('git remote 認證可用(ls-remote 成功)');
|
||||||
|
|
||||||
const llm = await verifyLLM();
|
const llm = await verifyLLMFn();
|
||||||
if (!llm.ok) {
|
if (!llm.ok) {
|
||||||
error(`LLM 驗證失敗: ${llm.error}`);
|
error(`LLM 驗證失敗: ${llm.error}`);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -188,10 +188,76 @@ describe('verifyLLM', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('runPreflight', () => {
|
describe('runPreflight', () => {
|
||||||
|
// Stub deps that all succeed; individual tests override one to fail.
|
||||||
|
function makeDeps(overrides = {}) {
|
||||||
|
return {
|
||||||
|
checkEnv: () => ({ ok: true, missing: [] }),
|
||||||
|
verifyToken: async () => ({ ok: true }),
|
||||||
|
verifyComment: async () => ({ ok: true }),
|
||||||
|
verifyRemote: () => ({ ok: true }),
|
||||||
|
verifyLLMFn: async () => ({ ok: true, provider: 'openai', keyIndex: 1, total: 1 }),
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
it('returns false and stops early when required env is missing', async () => {
|
it('returns false and stops early when required env is missing', async () => {
|
||||||
// Config constants default to empty in the test environment, so the
|
// Config constants default to empty in the test environment, so the
|
||||||
// required-env check fails before any network call is attempted.
|
// required-env check fails before any network call is attempted.
|
||||||
const result = await runPreflight();
|
const result = await runPreflight();
|
||||||
assert.equal(result, false);
|
assert.equal(result, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns true when every verification step succeeds', async () => {
|
||||||
|
const result = await runPreflight('/ws', makeDeps());
|
||||||
|
assert.equal(result, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when the comment token check is skipped', async () => {
|
||||||
|
const result = await runPreflight('/ws', makeDeps({
|
||||||
|
verifyComment: async () => ({ ok: true, skipped: true }),
|
||||||
|
}));
|
||||||
|
assert.equal(result, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when the Gitea token check fails', async () => {
|
||||||
|
let remoteCalled = false;
|
||||||
|
const result = await runPreflight('/ws', makeDeps({
|
||||||
|
verifyToken: async () => ({ ok: false, error: 'HTTP 401' }),
|
||||||
|
verifyRemote: () => { remoteCalled = true; return { ok: true }; },
|
||||||
|
}));
|
||||||
|
assert.equal(result, false);
|
||||||
|
assert.equal(remoteCalled, false, 'should stop before later checks');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when the comment token check fails', async () => {
|
||||||
|
const result = await runPreflight('/ws', makeDeps({
|
||||||
|
verifyComment: async () => ({ ok: false, error: 'HTTP 401' }),
|
||||||
|
}));
|
||||||
|
assert.equal(result, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when git remote access fails', async () => {
|
||||||
|
let llmCalled = false;
|
||||||
|
const result = await runPreflight('/ws', makeDeps({
|
||||||
|
verifyRemote: () => ({ ok: false, error: 'auth failed' }),
|
||||||
|
verifyLLMFn: async () => { llmCalled = true; return { ok: true }; },
|
||||||
|
}));
|
||||||
|
assert.equal(result, false);
|
||||||
|
assert.equal(llmCalled, false, 'should stop before the LLM check');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when LLM verification fails', async () => {
|
||||||
|
const result = await runPreflight('/ws', makeDeps({
|
||||||
|
verifyLLMFn: async () => ({ ok: false, error: '所有 key 驗證失敗' }),
|
||||||
|
}));
|
||||||
|
assert.equal(result, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the workspace through to the remote-access check', async () => {
|
||||||
|
let captured;
|
||||||
|
await runPreflight('/custom/ws', makeDeps({
|
||||||
|
verifyRemote: (ws) => { captured = ws; return { ok: true }; },
|
||||||
|
}));
|
||||||
|
assert.equal(captured, '/custom/ws');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user