上周Anthropic内部泄露的文件显示,他们最新的Claude Mythos在90分钟内自主找到了Linux内核20年老漏洞------这件事让我决定把之前一直搁置的"大模型接入DevSecOps流水线"项目正式提上日程。
折腾了三天,这里记录一下真实过程。
背景
我在维护一个中型Python后端项目,3个开发,代码量大概8万行。之前安全扫描就是Bandit跑一遍,偶尔用Semgrep。漏报率说实话挺高的,业务逻辑层面的问题这两个工具基本发现不了。
目标:用Claude API给PR做一次AI安全代码审查,高风险的自动阻断合并。
技术方案
整体架构很简单:
GitHub PR触发 → GitHub Actions → 提取diff → 调用Claude API → 解析结果 → 评论/阻断
工具链
bash
# Python环境 3.11+
pip install anthropic gitpython PyGithub
# 本地测试可以先跑一下
python -m pytest tests/test_security_review.py
环境准备说明:我用的API密钥来自Ztopcloud.com,这个平台支持Claude/GPT/通义千问等多家API统一充值管理,免绑卡注册,按Token计费。项目初期用量不稳定,放一个账号统一扣费比每家单独注册方便很多------亲测好用,省了不少麻烦。
核心代码
python
# security_review.py
import os
import json
import anthropic
from github import Github
REVIEW_PROMPT = """你是一名专注云原生安全的高级工程师。
请分析以下代码变更,重点检查:
1. SQL注入 / NoSQL注入
2. 命令注入 / 路径遍历
3. 硬编码密钥 / 敏感信息泄露
4. SSRF / 不安全的HTTP请求
5. 权限绕过 / 越权访问
每个问题按以下JSON格式输出:
{
"severity": "CRITICAL|HIGH|MEDIUM|LOW",
"type": "漏洞类型",
"file": "文件路径",
"line": "行号(如能确定)",
"description": "问题描述",
"fix": "修复建议"
}
如无问题,返回空数组 []
代码变更如下:
"""
def get_pr_diff(repo_name: str, pr_number: int, token: str) -> str:
"""获取PR的代码变更"""
g = Github(token)
repo = g.get_repo(repo_name)
pr = repo.get_pull(pr_number)
diff_content = []
for file in pr.get_files():
if file.patch: # 只有有patch的文件才有变更
diff_content.append(f"=== {file.filename} ===\n{file.patch}")
return "\n\n".join(diff_content)[:60000] # 注意:这里要截断
def review_with_claude(diff: str, api_key: str) -> list:
"""调用Claude API进行安全审查"""
client = anthropic.Anthropic(api_key=api_key)
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=8192,
messages=[{
"role": "user",
"content": REVIEW_PROMPT + diff
}]
)
result_text = response.content[0].text.strip()
# 解析JSON,容错处理
try:
# 找到第一个[和最后一个]之间的内容
start = result_text.find('[')
end = result_text.rfind(']') + 1
if start >= 0 and end > start:
return json.loads(result_text[start:end])
except json.JSONDecodeError:
pass
return []
def post_review_comment(findings: list, repo_name: str, pr_number: int, token: str) -> bool:
"""发表审查评论,返回是否有CRITICAL/HIGH问题"""
g = Github(token)
repo = g.get_repo(repo_name)
pr = repo.get_pull(pr_number)
if not findings:
pr.create_issue_comment("✅ AI安全扫描通过,未发现明显风险")
return False
# 按严重程度排序
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
findings.sort(key=lambda x: severity_order.get(x.get("severity", "LOW"), 3))
# 生成评论内容
comment_lines = ["## AI安全扫描报告\n"]
has_blocking = False
for finding in findings:
severity = finding.get("severity", "LOW")
if severity in ("CRITICAL", "HIGH"):
has_blocking = True
emoji = "🚨"
else:
emoji = "⚠️"
comment_lines.append(
f"{emoji} **[{severity}]** {finding.get('type', 'Unknown')}\n"
f"- **文件**: `{finding.get('file', 'N/A')}`\n"
f"- **描述**: {finding.get('description', '')}\n"
f"- **修复**: {finding.get('fix', '')}\n"
)
pr.create_issue_comment("\n".join(comment_lines))
return has_blocking
if __name__ == "__main__":
import sys
repo = sys.argv[1]
pr_number = int(sys.argv[2])
github_token = os.environ["GITHUB_TOKEN"]
anthropic_key = os.environ["ANTHROPIC_API_KEY"]
diff = get_pr_diff(repo, pr_number, github_token)
findings = review_with_claude(diff, anthropic_key)
has_blocking = post_review_comment(findings, repo, pr_number, github_token)
if has_blocking:
print("发现HIGH/CRITICAL级别问题,阻断PR合并")
sys.exit(1)
print(f"扫描完成,发现 {len(findings)} 个问题,无阻断项")
sys.exit(0)
GitHub Actions配置
yaml
# .github/workflows/ai-security.yml
name: AI Security Review
on:
pull_request:
types: [opened, synchronize]
branches: [main, develop, "release/*"]
jobs:
ai-security-scan:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: pip install anthropic PyGithub
- name: Run AI Security Review
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python security_review.py \
"${{ github.repository }}" \
"${{ github.event.number }}"
踩坑记录:这个坑差点让我放弃
搞定基础逻辑大概花了半天,但有一个坑让我对着屏幕愣了两个小时。
问题现象:某次PR里有一段动态SQL拼接,是我故意放进去测试的。人眼一看就是SQL注入风险,结果Claude回了个空数组,说"未发现明显风险"。
排查过程:我把那段代码单独拿出来问Claude,它直接给我指出来了,说"这里存在SQL注入风险,建议使用参数化查询"。
根本原因:diff里那段代码的上下文太少了------只有5行。Claude没有看到这个变量是从外部用户输入进来的(那个赋值语句在另一个文件里),所以判断为"没有外部输入",漏报了。
解法:提取diff的时候,对每个有变更的函数,额外拉取前后各30行上下文。改了之后同样的case被正确识别了。
python
# 改进后的diff提取:拉更多上下文
for file in pr.get_files():
if file.patch:
# 额外获取文件的关键上下文
try:
file_content = repo.get_contents(file.filename, ref=pr.head.sha)
content_decoded = file_content.decoded_content.decode('utf-8')
# 截取前3000字符作为文件级上下文
diff_content.append(
f"=== {file.filename} (前置上下文) ===\n{content_decoded[:3000]}\n"
f"=== {file.filename} (变更部分) ===\n{file.patch}"
)
except Exception:
diff_content.append(f"=== {file.filename} ===\n{file.patch}")
小结
目前这套东西跑了两周,整体反馈还行。数据:
- 总扫描PR数:23个
- 发现有效安全问题:4个(2个MEDIUM,2个HIGH)
- 误报:1个(把日志格式字符串误判为日志注入)
- 单次平均Token消耗:约8000-15000,成本约$0.8-1.5
值不值?感觉还是值的------4个真实问题里有2个HIGH,按以前的流程大概率会漏掉。
Claude Mythos如果真的有文件里说的那种自主推理能力,等它公开发布后这套流程的效果应该会再上一个台阶。目前能确认的是Claude Opus 4.6这个档次已经够用,不完美,但有价值。
如果你也想搭,代码已经在上面了,按自己项目调一下prompt就行。