漏洞到CI/CD集成)
去年我在維護(hù)一個內(nèi)部服務(wù)時代碼評審階段發(fā)現(xiàn)支付回調(diào)接口埋著一個越權(quán)漏洞。更尷尬的是這段有問題的代碼是AI編程助手生成的它能秒級寫完業(yè)務(wù)邏輯卻完全沒意識到自己在處理敏感資源時需要校驗歸屬權(quán)。從那天起我開始琢磨一件事能不能把安全審計的能力做成一整套Agent隨時可調(diào)用的“技能包”讓AI不光會寫代碼還能在寫之前、寫之后主動幫我把安全隱患篩一遍。于是就有了security-audit-skill這個項目。這篇文章會把整個過程完整攤開來講Skill在Agent生態(tài)里到底是什么、為什么安全審計特別適合做成Skill、一個可用的security-audit-skill內(nèi)部結(jié)構(gòu)長什么樣、我是怎么在本地倉庫和CI流水線里實測的以及中途踩過的誤報、漏報、上下文丟失這些坑。如果你正在用Codex、Claude、OpenCode這類工具寫代碼又不想把安全問題全部押在事后人工評審上這篇內(nèi)容應(yīng)該能幫你少走不少彎路。1. security-audit-skill到底在解決什么問題1.1 代碼安全審計的痛點與現(xiàn)有工具的邊界代碼安全審計這件事傳統(tǒng)上基本靠三類手段靜態(tài)分析工具SAST、依賴漏洞掃描、人工代碼評審。SAST類工具像Semgrep、CodeQL、SonarQube優(yōu)點是規(guī)則嚴(yán)謹(jǐn)、覆蓋廣但問題是誤報率不低而且很多規(guī)則只能抓“特征”抓不住業(yè)務(wù)流程里的邏輯漏洞。比如前面說的越權(quán)問題工具能看到你調(diào)用了getOrderById但它不知道這個訂單是不是屬于當(dāng)前登錄用戶這種上下文只能靠人去判斷。人工評審倒是能理解業(yè)務(wù)邏輯但成本高、周期長而且在小團(tuán)隊里往往被壓縮成“上線前走個過場”。依賴漏洞掃描解決的是供應(yīng)鏈問題對自研代碼的邏輯缺陷基本無能為力。你會發(fā)現(xiàn)傳統(tǒng)工具鏈在“靜態(tài)特征檢測”和“業(yè)務(wù)邏輯理解”之間存在一條明顯的空檔而大模型恰好擅長理解意圖和上下文。這就是我決定把安全審計做成Agent Skill的出發(fā)點把SAST的規(guī)則嚴(yán)謹(jǐn)性和LLM的上下文理解能力結(jié)合起來。1.2 為什么是Skill而不是Plugin或者Agent熱詞區(qū)里有人在問“skill和agent的區(qū)別”這確實是很多人剛接觸這個概念時最容易混淆的地方。我的理解直接說Agent是一個能做決策、能循環(huán)執(zhí)行任務(wù)的“操作者”Skill是給這個操作者預(yù)裝的“能力包”。Agent負(fù)責(zé)拆解任務(wù)、調(diào)用工具、編排步驟Skill負(fù)責(zé)告訴Agent“面對某類問題時你有一整套成熟的應(yīng)對流程可以按這個來”。如果你把這個能力做成插件它會偏向于提供一個具體的函數(shù)或接口調(diào)用方得自己決定什么時候用、怎么用主動權(quán)在調(diào)用方手里。但做成Skill主動權(quán)在Agent手里當(dāng)它發(fā)現(xiàn)當(dāng)前任務(wù)涉及安全檢查時會自動加載對應(yīng)流程。Plugin是“你叫它它才動”Skill是“Agent根據(jù)任務(wù)上下文主動想到用它”。至于把它做成一整個獨立的Agent那就太重了——安全審計通常只是研發(fā)流程的一個環(huán)節(jié)做成Agent反而難以和現(xiàn)有的Codex、Claude Code、OpenCode工作流無縫銜接。所以security-audit-skill的定位很清晰它不是替代人類安全工程師的獨立系統(tǒng)而是給AI編程助手裝上的一套“安全審查工作記憶”。代碼寫完了或者準(zhǔn)備提交了Agent能自覺進(jìn)入審計模式把可疑點一處處列出來給你看。2. 拆解Skill的運行機制Agent怎么“學(xué)會”做安全審計2.1 Skill的核心組成SKILL.md與腳本資產(chǎn)的協(xié)作關(guān)系我在GitHub上參考過不少Skill項目也自己翻過一批skill技能庫發(fā)現(xiàn)一個靠譜的Skill基本都由兩大部分構(gòu)成面向Agent的指令文件和面向具體任務(wù)的腳本資產(chǎn)。指令文件通常叫SKILL.md它的作用不是給人讀的文檔而是給LLM的“工作手冊”。腳本資產(chǎn)則是實際干活的代碼比如掃描器、規(guī)則文件、報告模板。這兩者的關(guān)系我打個比方SKILL.md是帶班組長的工作指令告訴Agent“這次任務(wù)的流程分幾步、每步有什么標(biāo)準(zhǔn)、遇到什么情況要停下來”腳本資產(chǎn)是工具箱里的扳手和螺絲刀Agent按指令判斷該擰哪個螺絲時就抽出對應(yīng)工具去執(zhí)行。沒有SKILL.mdAgent不知道什么時候該用這批腳本沒有腳本資產(chǎn)Agent只能靠自己的知識硬寫規(guī)則質(zhì)量完全不可控。我見過一些人寫的SkillSKILL.md只有三五行“你是一個安全審計專家”沒有任何流程和約束實際用下來Agent基本不會觸發(fā)偶爾觸發(fā)了也只是泛泛而談給不出具體文件和行號。真正好用的SkillSKILL.md一定要寫得像SOP標(biāo)準(zhǔn)作業(yè)程序一樣明確。2.2 觸發(fā)與編排讓審計動作在合適的時機自動出現(xiàn)Skill能不能被Agent自動想起來關(guān)鍵在SKILL.md里怎么描述觸發(fā)條件。這部分的寫法直接決定你的Skill是“救命工具”還是“擺設(shè)”。我在多次實測后總結(jié)出一條經(jīng)驗觸發(fā)條件描述要覆蓋“任務(wù)類型”和“信號詞”兩層。任務(wù)類型層是告訴Agent“這類任務(wù)你應(yīng)該用我”比如“代碼審查”“安全審計”“上線前檢查”“提交前自查”。信號詞層是更細(xì)的提示比如“SQL查詢”“用戶輸入”“鑒權(quán)”“Token”“加密”“依賴”“日志脫敏”等等。當(dāng)用戶對Agent說“幫我review一下這個改動”Agent看到“review”這個詞再結(jié)合當(dāng)前改動的代碼涉及用戶輸入拼接查詢就會主動聯(lián)想到該加載security-audit-skill。編排層同樣重要。Skill一旦被激活A(yù)gent不能上來就亂掃一通得有步驟。我在SKILL.md里明確規(guī)定了執(zhí)行順序先收集項目結(jié)構(gòu)和變更文件列表再做靜態(tài)規(guī)則掃描然后讓模型對掃描結(jié)果做上下文研判最后輸出結(jié)構(gòu)化報告。這個順序一旦固定下來審計結(jié)果的可復(fù)現(xiàn)性會大幅提升。我剛開始沒寫執(zhí)行順序結(jié)果Agent每次跑的流程都不一樣有時候先分析業(yè)務(wù)邏輯有時候先掃依賴文件報告格式五花八門根本無法橫向?qū)Ρ取?. 手寫一個可復(fù)現(xiàn)的security-audit-skill結(jié)構(gòu)3.1 目錄骨架與核心清單如果只是想要一個能跑的Skill不需要搞得很復(fù)雜。我的第一個可用版本包含一個目錄、三個文件結(jié)構(gòu)如下security-audit-skill/ ├── SKILL.md └── scripts/ ├── audit_rules.json ├── scan.py └── report_template.mdSKILL.md是Agent的行為指導(dǎo)告訴它什么場景下激活、按什么流程審計、需要注意哪些約束比如“不要在沒有證據(jù)的情況下斷言漏洞必須給出文件路徑和行號”。audit_rules.json是靜態(tài)規(guī)則的集合把常見高危模式按類別組織好Agent可以加載它做初步篩查。scan.py是一個輕量腳本負(fù)責(zé)做兩類事情一是列出倉庫里的關(guān)鍵文件清單二是調(diào)用規(guī)則文件跑一輪正則和AST風(fēng)格的模式匹配把可疑代碼段定位到文件和行號。report_template.md是輸出的標(biāo)準(zhǔn)模板保證每次審計報告格式一致方便后續(xù)處理和歸檔。這個結(jié)構(gòu)理論上可以跑在任何支持Skill機制的Agent上。我實際測試過Codex和OpenCode也有人在Claude Code里裝過類似的兼容性整體沒問題。核心思路就是不要讓Agent自己從零編一套審計流程而是把所有流程和規(guī)則固化下來Agent只需要按圖索驥。3.2 檢測規(guī)則怎么寫才不被Agent忽略規(guī)則文件是security-audit-skill的靈魂。寫得不好Agent加載了也不當(dāng)回事。我第一版規(guī)則只有一張簡單的關(guān)鍵詞列表比如“password”“token”“select”結(jié)果Agent掃描的時候把所有帶這些詞的代碼全標(biāo)紅了報告完全沒法看。后來我改成了結(jié)構(gòu)化規(guī)則每一條包含規(guī)則ID、風(fēng)險類別、嚴(yán)重級別、匹配模式、上下文判斷要點、修復(fù)建議。這里貼一個我實際在用的例子{ rule_id: AUDIT-SQLI-001, category: injection, severity: critical, patterns: [ executeQuery, executeUpdate, rawQuery, $query, \\$qb-expr() ], context_hints: [ 判斷拼接進(jìn)SQL的字符串是否來自HTTP請求參數(shù), 檢查是否使用了預(yù)編譯語句如果沒有判定為可疑, 如果SQL片段里有直接拼接的變量名必須標(biāo)記出來 ], recommendation: 使用參數(shù)化查詢或預(yù)編譯語句禁止直接拼接SQL }關(guān)鍵點在于context_hints這一段。我的做法是腳本掃描出候選位置后把這一段的代碼片段連同context_hints一起交給LLM。Agent讀到“來自HTTP請求參數(shù)”“是否預(yù)編譯”這兩個提示就能基于上下文做出判斷而不是機械地見executeQuery就報警。這樣一套組合拳下來誤報率明顯下降報告里能說出“為什么可疑”而不只是“這里有這個函數(shù)”。還有一點值得強調(diào)規(guī)則描述里必須明確“什么時候不要報”。比如很多代碼里寫日志時會打印用戶郵箱這不算敏感信息泄露但如果打印的是身份證號、支付賬號就要標(biāo)記為中等風(fēng)險。LLM需要這些邊界才能做出準(zhǔn)確判斷別讓它猜。3.3 輸出報告格式設(shè)計審計報告格式是我調(diào)整次數(shù)最多的部分。一開始Agent自己生成markdown報告結(jié)構(gòu)還算清晰但不同輪次跑出來的字段名不統(tǒng)一有的寫“嚴(yán)重級別”有的寫“風(fēng)險等級”對接到后續(xù)工單系統(tǒng)時很麻煩。后來我干脆用模板把它定死報告必須包含以下區(qū)塊審計概覽項目名、審計時間、掃描文件總數(shù)、問題總數(shù)、按嚴(yán)重級別分布嚴(yán)重/高/中/低問題列表每條包含問題ID、風(fēng)險類型、嚴(yán)重級別、文件路徑、行號、問題描述、上下文證據(jù)、修復(fù)建議未發(fā)現(xiàn)問題的重點區(qū)域按SKILL.md里的關(guān)鍵詞清單列出哪些高風(fēng)險區(qū)域已經(jīng)排查過且未發(fā)現(xiàn)問題方便評審人知道Agent確實覆蓋了哪些范圍補充觀察Agent在上下文研判中發(fā)現(xiàn)但不完全確定的問題單獨放一個“低置信度提示”避免混入正式問題造成干擾報告格式定死之后還有一個額外的好處你可以寫一個后續(xù)任務(wù)讓Agent把報告內(nèi)容自動填到某個表格或者Issue模板里字段能一一對應(yīng)上。安全團(tuán)隊跟進(jìn)問題的時候不用再從一段散文里手動提煉信息。4. 實測鏈路從本地代碼倉庫到審計報告4.1 環(huán)境準(zhǔn)備與Skill裝載我先說明一下測試環(huán)境機器是MacBook Pro安裝了Codex的CLI版本同時也用OpenCode跑過一遍兩個環(huán)境下的表現(xiàn)基本一致。Skill的裝載方式不同工具略有差異但原理相同——把Skill目錄放在Agent能掃描到的配置路徑中或者用支持的命令手動裝載。以Codex為例你可以在配置里指定skill目錄也可以直接在會話中用命令加載某個skill。這里有一個新手特別容易踩的坑裝完Skill之后一定要開一個新會話驗證Agent能否正確識別。Agent的上下文是會話級的你在舊會話里裝好的Skill舊會話可能不會主動刷新加載容易誤以為“Skill壞了”。我一般會在裝完后問一句“你現(xiàn)在有哪些可用的skill分別適用什么場景”讓Agent復(fù)述一遍確認(rèn)它真的讀到了SKILL.md里的內(nèi)容而不是我還停留在理想狀態(tài)。4.2 完整執(zhí)行過程與結(jié)果解讀我用一個模擬了常見漏洞的測試倉庫來做演示。倉庫是Python寫的一個Flask應(yīng)用含一個登錄接口、一個訂單查詢接口、一個數(shù)據(jù)庫訪問模塊。我在里面人為埋了幾個典型問題登錄接口里SQL拼接、訂單查詢接口沒校驗用戶歸屬、日志里打印了明文密碼字段、requirements.txt里出現(xiàn)了一個存在已知漏洞的舊版本庫。實際執(zhí)行時我讓Agent“review這個項目的代碼重點關(guān)注安全問題”。Agent成功加載了security-audit-skill然后按SKILL.md流程先掃描文件清單隨后調(diào)用scan.py跑規(guī)則找到可疑位置再結(jié)合上下文逐條研判。最后產(chǎn)出的報告里四個問題全部命中并且SQL注入那條被標(biāo)成了嚴(yán)重級別越權(quán)那條標(biāo)為高日志脫敏問題標(biāo)為中依賴漏洞標(biāo)為高。文件路徑、行號、修復(fù)建議全部對得上。有個細(xì)節(jié)讓我印象很深A(yù)gent在研判越權(quán)問題時自己去看了一眼這個接口路由有沒有裝飾器做登錄校驗發(fā)現(xiàn)只有登錄校驗沒有歸屬校驗然后在報告里寫了一條“雖然接口要求登錄但并未校驗訂單歸屬存在IDOR風(fēng)險”。這說明只要上下文給得足LLM是真的能理解邏輯漏洞而這恰恰是傳統(tǒng)SAST工具最難啃的部分。4.3 誤報、漏報與上下文丟失的應(yīng)對實測中不可能全是順利的。我遇到的第一類問題是誤報典型場景是測試代碼里大量使用Mock數(shù)據(jù)字符串里包含“select * from users”這種SQL片段但其實是測試用例不是真實查詢。剛開始Agent會把這些標(biāo)成“疑似SQL注入”。后來我在SKILL.md里加了一條約束“如果可疑代碼位于test/、tests/、mock/、fixtures/目錄應(yīng)降級為觀察項不計入正式問題列表?!闭`報率一下降了七成。第二類問題是漏報多發(fā)生在代碼文件特別多的時候。Agent受上下文窗口限制可能只仔細(xì)看了前幾個文件后面的文件草草帶過。我后來在SKILL.md里明確要求“先掃描項目結(jié)構(gòu)如果文件數(shù)量超過20個必須分批審計每一批都要在報告中記錄覆蓋狀態(tài)?!蓖瑫r要求scan.py輸出一份完整文件清單Agent審計完一批勾選一批避免遺漏。第三類也是最有代表性的問題上下文丟失。有一次在一個大型Java項目里跑Agent分析到一半突然忘了自己加載過security-audit-skill開始用普通“代碼解釋”模式跟我聊天報告格式完全走樣。排查了半天發(fā)現(xiàn)是因為SKILL.md太長Agent讀到后面注意力被其他任務(wù)占用了。解決辦法是把SKILL.md精簡到核心SOP把冗長的規(guī)則細(xì)節(jié)全部挪到audit_rules.json和scripts里讓SKILL.md只承擔(dān)“流程指揮”的角色。這個調(diào)整之后上下文丟失的情況明顯減少。5. 從“能用”到“好用”的進(jìn)階經(jīng)驗5.1 讓審計規(guī)則可配置化跑到第二周我開始不滿足于固定的內(nèi)置規(guī)則。不同項目的風(fēng)險側(cè)重點差別很大給支付系統(tǒng)做審計重點看金額計算、冪等性、回調(diào)驗簽給內(nèi)容社區(qū)做審計重點看XSS、用戶生成內(nèi)容的展示轉(zhuǎn)義給內(nèi)部管理系統(tǒng)做審計重點看越權(quán)和敏感數(shù)據(jù)查詢。由于每個項目的代碼風(fēng)格和業(yè)務(wù)形態(tài)不一樣固定規(guī)則很難滿足所有場景。我的做法是引入一個audit_profile.json配置層允許按項目覆蓋規(guī)則。每個項目根目錄下放一個審計配置文件里面可以指定啟用哪些規(guī)則類別、額外添加哪些關(guān)鍵詞模式、忽略哪些目錄。scan.py在運行時先讀取項目配置再疊加內(nèi)置規(guī)則形成一份“項目專屬規(guī)則集”。SKILL.md里也寫了一段如果發(fā)現(xiàn)項目根目錄有audit_profile.json優(yōu)先使用它覆蓋默認(rèn)規(guī)則。這樣做的收益很直接不同團(tuán)隊可以直接共享同一個Skill但每個項目有自己的審計策略。規(guī)則集不再是僵死的而是隨項目演進(jìn)的資產(chǎn)。有同事問我這算不算把Skill變成配置驅(qū)動的框架我說對這個階段你已經(jīng)不是在“寫死規(guī)則”了而是在建設(shè)一套組織級的安全審計基線。5.2 和CI/CD流水線的結(jié)合本地跑通之后我很快意識到真正發(fā)揮價值的地方是CI流水線。人的自覺性不可靠但流水線不會偷懶。我的做法是寫了一個小的命令行入口能讓Agent在離線環(huán)境下對指定代碼變更集運行審計。注意CI環(huán)境一般沒有交互式會話所以命令行入口必須支持非交互模式接收一個diff或提交范圍輸出報告文件。實際流水線里我是這樣接的在Pull Request觸發(fā)后CI執(zhí)行一次“新增或變更文件的增量審計”把報告上傳到GitHub Action的artifact如果有嚴(yán)重級別的問題直接讓CI標(biāo)記為失敗阻止合并。最開始團(tuán)隊成員擔(dān)心誤報會拖慢合并速度于是我加了一個“細(xì)微閾值”配置只有嚴(yán)重和高等級問題才阻斷流水線中低等級問題只出現(xiàn)在報告里提醒開發(fā)者處理。這樣既守住了底線又不至于讓AI審計變成開發(fā)流程的瓶頸。這套流程跑了一個多月最直觀的效果是人工評審時不再需要花時間找低級的注入問題可以把注意力放到業(yè)務(wù)邏輯和架構(gòu)合理性上。AI審計把“下限”兜住了人工評審專注“上限”的優(yōu)化。5.3 我踩過的坑與最終建議最后集中說幾個踩過的坑。第一個坑是Skill里塞了太多領(lǐng)域知識導(dǎo)致SKILL.md巨長Agent加載后占用大量上下文窗口反而沒空間去看代碼了?,F(xiàn)在我的原則是SKILL.md控制在兩百行以內(nèi)領(lǐng)域知識放到腳本或參考資料里需要的時候再按需讀取。這就像你給新員工發(fā)手冊手冊目錄寫清楚就行別把整本手冊背下來。第二個坑是規(guī)則誤傷業(yè)務(wù)特例。比如有些項目會把日志加密密鑰放在配置文件里掃描器看到“key ...”就報警但人家這是有意的設(shè)計。應(yīng)對方法是在audit_rules.json里支持“白名單模式”允許項目聲明哪些路徑、哪些變量名是已知可接受的掃描時直接跳過。這能顯著減少團(tuán)隊對AI審計的“狼來了”疲勞。第三個坑也是最玄學(xué)的就是Agent模型的版本差異。同一個Skill在不同模型版本下表現(xiàn)波動很大有時候突然像換了一個人規(guī)則執(zhí)行得七零八落。我能給的建議是把SKILL.md里的關(guān)鍵指令寫得更“笨”一些比如“遇到關(guān)鍵字的文件必須逐個列出文件名和行號不得只寫文件名”這種傻瓜式指令在任何模型下都不容易被忽略。另外重要的審計結(jié)論一定要有腳本輸出的結(jié)構(gòu)化數(shù)據(jù)作為支撐不能只靠模型“覺得有問題”。最后一個建議別指望Skill是銀彈。它能把安全審計的覆蓋面擴大、讓普通開發(fā)者也能獲得接近專業(yè)審計師的初篩能力但替換不了專業(yè)安全人員對復(fù)雜業(yè)務(wù)風(fēng)險的設(shè)計判斷。我現(xiàn)在的用法是“AI做初篩人做復(fù)核”兩者組合安全效率提升明顯風(fēng)險也控得住。如果你也在搭自己的Agent工作流建議不要把Skill定義成“檢查者”而是定義成“輔助你思考的搭檔”那才是它真正發(fā)光的地方。