Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ba9a4e223 | |||
| 7339145641 |
@@ -1 +1,44 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
+14
-3
@@ -62,11 +62,22 @@ function withAskpass(workspace, fn) {
|
|||||||
const askpassScript = path.join(workspace, '.git-askpass.sh');
|
const askpassScript = path.join(workspace, '.git-askpass.sh');
|
||||||
fs.writeFileSync(askpassScript, '#!/bin/sh\necho "$GIT_TOKEN"\n', { mode: 0o700 });
|
fs.writeFileSync(askpassScript, '#!/bin/sh\necho "$GIT_TOKEN"\n', { mode: 0o700 });
|
||||||
const credEnv = { ...process.env, GIT_ASKPASS: askpassScript, GIT_USERNAME: 'x-token', GIT_TOKEN: GITEA_TOKEN };
|
const credEnv = { ...process.env, GIT_ASKPASS: askpassScript, GIT_USERNAME: 'x-token', GIT_TOKEN: GITEA_TOKEN };
|
||||||
|
const cleanup = () => { try { fs.unlinkSync(askpassScript); } catch {} };
|
||||||
|
let result;
|
||||||
try {
|
try {
|
||||||
return fn(credEnv);
|
result = fn(credEnv);
|
||||||
} finally {
|
} catch (e) {
|
||||||
try { fs.unlinkSync(askpassScript); } catch {}
|
cleanup();
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
// Defer cleanup until an async callback settles, otherwise the askpass script
|
||||||
|
// is deleted at the first `await` and later network ops (e.g. git push) fail
|
||||||
|
// with "cannot exec .git-askpass.sh". Sync callbacks clean up immediately.
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
return result.finally(cleanup);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readGitOutput(run, args, cwd, env) {
|
function readGitOutput(run, args, cwd, env) {
|
||||||
|
|||||||
@@ -93,6 +93,18 @@ describe('commitAndPush', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the askpass script present while the network push runs', async () => {
|
||||||
|
let askpassExistsAtPush = null;
|
||||||
|
const spawn = makeSpawn({
|
||||||
|
push: (_args, opts) => {
|
||||||
|
askpassExistsAtPush = !!(opts?.env?.GIT_ASKPASS && fs.existsSync(opts.env.GIT_ASKPASS));
|
||||||
|
return { status: 0, stdout: '', stderr: '', error: null };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await commitAndPush(workspace, path.join(workspace, 'repo'), spawn, sourceRoot);
|
||||||
|
assert.equal(askpassExistsAtPush, true, 'askpass script must still exist when git push runs');
|
||||||
|
});
|
||||||
|
|
||||||
it('cleans up askpass script after successful run', async () => {
|
it('cleans up askpass script after successful run', async () => {
|
||||||
await commitAndPush(workspace, path.join(workspace, 'repo'), makeSpawn(), sourceRoot);
|
await commitAndPush(workspace, path.join(workspace, 'repo'), makeSpawn(), sourceRoot);
|
||||||
const leftover = fs.readdirSync(workspace).filter(f => f.endsWith('.git-askpass.sh'));
|
const leftover = fs.readdirSync(workspace).filter(f => f.endsWith('.git-askpass.sh'));
|
||||||
|
|||||||
Reference in New Issue
Block a user