fix(ai-review): 強化安全防護與建問題留言處理
This commit is contained in:
+22
-20
@@ -36,7 +36,7 @@ const AGENT_DIAGNOSTIC_OUTPUT_LIMIT = 500;
|
||||
function redactSecrets(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/[\r\n\t\v\f\x00-\x1f\x7f]+/g, ' ')
|
||||
.replace(/(authorization\s*[:=]\s*)\S+/gi, '$1***')
|
||||
.replace(/(authorization\s*[:=]\s*)(?:bearer\s+)?\S+/gi, '$1***')
|
||||
.replace(/((?:api[_-]?key|token|password|secret|bearer)\s*[:=]\s*)\S+/gi, '$1***')
|
||||
.replace(/(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi, '$1***:***@')
|
||||
.replace(/\bgh[pousr]_[A-Za-z0-9]{16,}\b/g, '***')
|
||||
@@ -48,10 +48,9 @@ function redactSecrets(text) {
|
||||
* 從 `runAgent` 的失敗結果組出可診斷的一行摘要:退出碼/訊號為主,原始輸出預設隱藏。
|
||||
*
|
||||
* 安全考量:AI CLI 失敗時可能在 stderr/stdout 回顯提示內容、環境資訊、token、PII 或
|
||||
* 原始碼祕密,這些會被長期保存並供多人讀取的 CI log 收錄。權衡「可除錯性」後:本函式
|
||||
* 於失敗時**預設**附上經 {@link redactSecrets} 遮罩且去除控制字元的 **stderr 與 stdout**
|
||||
* 片段(各先截去過長輸入再取前 500 字)——只印 exit code 幾乎無從判斷 CLI 為何失敗,
|
||||
* 且部分 CLI(如 claude-code 的 `-p` 模式)將錯誤寫到 stdout 而非 stderr。純函式、不拋例外。
|
||||
* 原始碼祕密,這些會被長期保存並供多人讀取的 CI log 收錄。因此本函式預設只輸出退出碼、
|
||||
* 訊號與逾時狀態;只有 `ACTIONS_STEP_DEBUG=true` 時才附上經 {@link redactSecrets}
|
||||
* 遮罩且去除控制字元的 stderr/stdout 片段(各先截去過長輸入再取前 500 字)。
|
||||
*
|
||||
* @param {{error: (Error & {code?: number|string, signal?: string, killed?: boolean})|null, stderr?: string, output?: string}} res
|
||||
* `runAgent` 的回傳物件。
|
||||
@@ -59,7 +58,7 @@ function redactSecrets(text) {
|
||||
* @remarks
|
||||
* 使用情境:{@link runAttackers}/{@link runDefenders}/{@link fillPurposes}/{@link selectLabels}
|
||||
* 判定 `!res.ok` 時,以本函式把失敗細節寫進 WRN log,讓 CI 記錄能看出 AI CLI 為何失敗;
|
||||
* 需要原始輸出診斷時,於 workflow 設定 secret `ACTIONS_STEP_DEBUG=true` 再重跑。
|
||||
* 需要輸出片段輔助診斷時,於 workflow 設定 `ACTIONS_STEP_DEBUG=true` 再重跑。
|
||||
* 本函式未匯出,僅供模組內部使用。
|
||||
*/
|
||||
function agentFailureDetail(res) {
|
||||
@@ -71,14 +70,13 @@ function agentFailureDetail(res) {
|
||||
else if (err.code) parts.push(`code ${err.code}`);
|
||||
else if (err.signal) parts.push(`signal ${err.signal}`);
|
||||
}
|
||||
// 先截去過長輸入(INPUT_LIMIT)再遮罩,最終仍截為 AGENT_DIAGNOSTIC_OUTPUT_LIMIT;長度政策常數見模組頂層。
|
||||
// 預設即附上「經 redactSecrets 遮罩+去控制字元+限長」的 stderr 與 stdout 片段——CLI 失敗時
|
||||
// 只印 exit code 幾乎無從除錯(見 test-claude 秒失敗案例);且部分 CLI(如 claude-code 的
|
||||
// -p 模式)會把錯誤寫到 stdout 而非 stderr,故兩者都輸出。redactSecrets 為盡力防線。
|
||||
const stderr = redactSecrets(String((res && res.stderr) || '').slice(0, INPUT_LIMIT));
|
||||
if (stderr) parts.push(`stderr:${stderr.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||
const stdout = redactSecrets(String((res && res.output) || '').slice(0, INPUT_LIMIT));
|
||||
if (stdout) parts.push(`stdout:${stdout.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||
// 失敗輸出可能含 token 或 PII,預設不寫入長期 CI log;debug 模式才輸出遮罩後片段。
|
||||
if (process.env.ACTIONS_STEP_DEBUG === 'true') {
|
||||
const stderr = redactSecrets(String((res && res.stderr) || '').slice(0, INPUT_LIMIT));
|
||||
if (stderr) parts.push(`stderr:${stderr.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||
const stdout = redactSecrets(String((res && res.output) || '').slice(0, INPUT_LIMIT));
|
||||
if (stdout) parts.push(`stdout:${stdout.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
parts.push((err && err.message && redactSecrets(err.message)) || 'AI CLI 執行失敗(無診斷輸出)');
|
||||
}
|
||||
@@ -739,9 +737,9 @@ ${JSON.stringify(brief)}
|
||||
* 不使用 {@link templates.othersComment} 的單一表格——表格僅用於一般模式(PR)。
|
||||
*/
|
||||
async function postSevereToIssue({ ctx, gitea, issueNumber, severe }) {
|
||||
for (const finding of severe) {
|
||||
await gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding));
|
||||
}
|
||||
await Promise.all(
|
||||
severe.map((finding) => gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding))),
|
||||
);
|
||||
log('步驟9', 'INF', `已將 ${severe.length} 條嚴重問題留言到 issue #${issueNumber}。`);
|
||||
}
|
||||
|
||||
@@ -765,9 +763,9 @@ async function postSevereToIssue({ ctx, gitea, issueNumber, severe }) {
|
||||
* 以本函式把警告+建議逐條留言到追蹤 issue,確保 issue 上每條問題都是可個別回覆的留言。
|
||||
*/
|
||||
async function postOthersToIssue({ ctx, gitea, issueNumber, others }) {
|
||||
for (const finding of others) {
|
||||
await gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding));
|
||||
}
|
||||
await Promise.all(
|
||||
others.map((finding) => gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding))),
|
||||
);
|
||||
log('步驟10', 'INF', `已將 ${others.length} 條警告+建議逐條留言到 issue #${issueNumber}。`);
|
||||
}
|
||||
|
||||
@@ -933,4 +931,8 @@ module.exports = {
|
||||
postOthersToIssue,
|
||||
resolveOldComments,
|
||||
postSevereComments,
|
||||
__test: {
|
||||
agentFailureDetail,
|
||||
redactSecrets,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user