简介:这份PDF是一份可直接参考的《系统源代码安全审计报告》模板类文档,面向安全工程师、开发测试人员及项目管理者,用于规范源代码审计工作的组织流程与报告编写。报告完整覆盖审计对象与目的、流程组织、审计范围及审计详情等章节,尤其在审计详情部分提供了风险等级定义、安全缺陷统计表及具体缺陷示例(含隐私泄露、XSS、SQL注入等),可帮助读者快速搭建企业级代码审计文档框架,并掌握从缺陷发现到修复建议的完整描述方法。资源为单个PDF文件,大小约257KB,内容精炼、目录清晰,已吸引超过1150人学习,适合作为安全审计报告写作的参考模板或培训材料。
1. 源代码安全审计报告:它到底审什么,什么人需要它
一个系统上线前,安全团队甩给我一份几十页的《系统源代码安全审计报告.pdf》,里面被翻来覆去地说有 hardcode 密钥、SQL 注入、反序列化风险。这份报告到底怎么来的?它不是自动扫描器吐出来的堆砌截图,而是把代码当作待检测的工件,逐条列出缺陷、危害、触发路径和修复建议的正式交付物。源代码安全审计就是把系统的每一行代码当成攻击面,用自动化工具加人工复核的方式找出可被利用的漏洞,最终落到一份能指导研发修复的文档里。
适合谁看?如果你是安全工程师,要自己产出这份报告;如果你是研发负责人,要判断供应商给我的报告靠不靠谱;如果你是项目经理,想知道审计要多久、要配合什么。它解决的是"代码已经写完了,怎么知道它能不能被黑"的问题,也解决"审计之后怎么不白审"的问题。后面所有内容都围绕"从接手代码到输出一份能落地的审计报告"这条主线展开。
2. 审计前先定边界:范围清单、评级标准与工具链选型
源代码安全审计不能拿到仓库就扫。我见过不少项目,扫描器跑完几千条告警,结果一半是测试代码和第三方样例,另一半是业务自己封装的工具类。真正的问题被淹没了。所以第一步不是扫,而是划边界。
2.1 审计范围清单:从代码仓库到依赖组件,哪些必须进报告
先列出这次审计要覆盖的代码对象。常见做法是建一份范围清单,逐项确认:
| 范围项 | 确认内容 | 是否纳入 |
|---|---|---|
| 主业务代码 | 前端、后端、微服务各自的仓库地址与分支 | 必审 |
| 自研公共库 | 团队内发布的 SDK、工具类、基础组件 | 必审 |
| 第三方依赖 | 直接与间接依赖的组件及其版本 | 必审(SCA) |
| 构建脚本与 CI/CD 配置 | Dockerfile、部署脚本、流水线里是否泄露密钥 | 建议审 |
| 配置文件 | 数据库连接串、配置中心、证书文件 | 必审 |
| 测试代码 | 单测、集成测试、压测脚本 | 可不纳入,但保留记录 |
| 生成代码 | protobuf、OpenAPI 生成代码 | 不纳入,避免噪音 |
确认清单后,要把每个仓库的 commit 范围也定下来。审计的是某个即将上线的 release 分支,还是整个历史分支?我一般会以 release 标签对应的 commit 为基线,因为线上跑的就是这份代码。如果审的是 master 最新 commit,可能审了一堆还没上线的功能,研发会拿"这个还没发布"来做挡箭牌。审计范围要在报告里写明,否则后面被质疑"你审的不是我们线上版本"时,连反驳的依据都没有。
依赖组件这块最容易被漏。很多团队只审自研代码,忽略第三方组件,但 Log4j2 那波漏洞已经说明问题有多严重。所以范围清单里必须包含一份由包管理工具导出的依赖清单,比如 Java 项目的pom.xml、gradle.lockfile,前端项目的package-lock.json。这条清单会直接喂给 SCA 工具。
2.2 风险评级标准:CVSS、CWE 与业务危害如何映射到报告结论
范围定完后,要统一评级语言。否则输出报告时,研发问"这个严重漏洞有多严重",你说不出依据,就会变成安全岗和研发岗在会议室吵架。
通用的做法是:技术漏洞用 CVSS v3.1 的 Base Score 做基础评分,同时用 CWE 编号标识漏洞类型。但 CVSS 分数是"通用环境"下的严重程度,不能直接等于业务风险。比如一个 CVSS 9.8 的 SQL 注入点只在管理后台,且管理后台不对公网开放,它的实际可利用性就没那么高。所以我会在报告里引入"业务影响因子",把 CVSS 分数结合资产暴露面,折算成高/中/低三个业务风险级。
参考这个映射逻辑:
| CVSS 评分 | 暴露面条件 | 业务风险级 |
|---|---|---|
| 9.0-10.0 | 公网可访问,无需特殊权限 | 严重 |
| 7.0-8.9 | 公网可访问但需认证,或内网核心系统 | 高危 |
| 4.0-6.9 | 内网受限访问,或需要高权限角色 | 中危 |
| 0.1-3.9 | 本地文件读取、代码注释等低影响场景 | 低危 |
这里要说清楚:评级不只是给个分数,还要写出"为什么是这个等级"。我的习惯是在缺陷描述里注明"CVSS 向量 + 业务判断",比如AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N再加一句"该接口未做登录校验,且返回完整用户信息"。这样研发能理解等级不是拍脑袋定的,报告也更经得起推敲。CWE 编号用来分类,比如 CWE-89 SQL 注入、CWE-798 硬编码凭据,便于研发在内部知识库里检索同类问题。
2.3 工具链选型:SAST、SCA 与人工审计的分工边界
源代码安全审计不可能靠人逐行读完一个几百万行的系统。工具是必须的,但工具只是辅助。常见组合是 SAST + SCA + 人工复核。SAST(静态应用安全测试)负责从源码里找缺陷模式,SCA(软件成分分析)负责查依赖组件的已知漏洞,人工负责确认那些需要业务上下文才能判断的问题。
SAST 工具怎么选?商业产品像 Fortify、Checkmarx 功能全,但贵,而且要部署服务端。开源方案里,Semgrep 因为规则可写、速度快、误报率相对可控,已经成为我这边的主力。CodeQL 适合深度数据流分析,但需要写 QL 查询,学习曲线陡,适合引擎级代码审计。如果团队没有专职安全开发,先从 Semgrep 开始最稳妥。
SCA 工具里,OWASP Dependency-Check 免费且成熟,支持 Java、Python、JavaScript 等主流生态,缺点是依赖 NVD 数据,每年会有几个月的空窗期。商业的 Snyk、JFrog Xray 数据源更全,但收费。我用 Dependency-Check 作为基线,对高危组件再用 NVD 官网或厂商公告人工核对一次。
这里要强调一点:SAST 工具跑出来的结果只能叫"疑似缺陷",必须经过人工确认真实可利用性,才能写进正式审计报告。工具的原理是模式匹配或约束求解,它无法理解业务逻辑,比如一个参数是不是用户可控,需要看进出口函数的调用链。所以团队里至少要有一个人能读懂代码,否则工具输出的两千条告警没法收敛成一份报告。工具链的边界就是:机器负责找嫌疑,人负责定罪和量刑。
3. 自动化扫描落地:SAST 工具的最小可用配置与结果解读
工具链定好后,进入实际操作。这一章给出我平时最常用的一套最小可用配置,基于 Semgrep 和 Dependency-Check。你可以照抄,然后根据自己系统的技术栈改路径和规则集。
3.1 用 Semgrep 跑通第一轮规则扫描:安装、规则配置与输出解析
Semgrep 原生支持多种语言。假设我们要审计一个 Java 后端项目,先进入代码目录,跑一次全量扫描:
# 安装 semgrep(需要 Python 3.8+) pip install semgrep # 用内置规则集扫描,输出 JSON 格式,方便后续处理 semgrep scan --config "p/java" --json --output semgrep-java.json .说明:--config "p/java"表示使用 Semgrep Registry 里的 Java 规则集,里面包含数千条常用漏洞模式。--output指定 JSON 输出文件而不是打印到终端,因为告警量通常很大,终端刷屏根本看不过来。最后.表示扫描当前目录。如果是首次运行,Semgrep 会自动下载规则包,网络不好的时候会卡住,建议提前确认能访问规则仓库。
跑完后,semgrep-java.json里的结果数组每一条都是一个"命中"。查看结构可以用 jq:
# 统计各种规则命中的数量,先看清整体分布 jq -r '.results[].check_id' semgrep-java.json | sort | uniq -c | sort -rn | head -20这条命令按规则 ID 分组计数,能快速判断哪些规则命中多。如果命中的前几名全是java.lang.security.audit这类通用审计规则,说明项目里有大量"可能有问题"的代码,但真正需要优先处理的是那些带CWE-89、CWE-79、CWE-798的规则。接下来把高危命中的详情展开:
# 提取指定规则的命中位置和代码片段 jq -r '.results[] | select(.check_id | contains("CWE-89")) | "\(.path):\(.start.line)\n\(.extra.lines)\n---"' semgrep-java.json这一步会列出所有 SQL 注入疑似点的文件路径、行号和代码片段。注意,Semgrep 的 CWE 编号有时候会映射得不准确,看到CWE-89时还要读一下代码,确认拼接的确实是 SQL 查询字符串而不是某个参数名。
参数方面,我经常用--severity ERROR过滤掉低优先级告警,或者用--exclude跳过生成的代码目录。比如:
semgrep scan --config "p/java" --severity ERROR --exclude "gen" --exclude "target" --json --output semgrep-java-err.json .这里--severity ERROR只保留高严重度命中,--exclude排除gen和target目录,避免生成代码和构建产物污染结果。注意,不同规则集内每个规则有自己的严重度定义,不是所有严重告警都等于真实可利用,所以这个命令只适合做第一轮快速收敛。
3.2 用 OWASP Dependency-Check 做依赖组件风险评估
自研代码扫完,接着审第三方依赖。OWASP Dependency-Check 是从项目依赖清单角度出发,与 NVD 数据库核对版本号的工具。以 Java Maven 项目为例,先到项目根目录构建一次,确保依赖已下载,再扫描 pom.xml:
# 先编译,确保依赖解析完成 mvn -q compile # 扫描 pom.xml,输出 JSON 格式到 dc-result 目录 dependency-check --scan pom.xml --format JSON --out dc-result --project "MySystem"参数说明:--scan是要扫描的路径,这里指向pom.xml,它会自动识别 Maven 项目并解析依赖。--format JSON输出机器可读结果,--out指定输出目录,扫描完成后会在该目录下生成 JSON 和 HTML 报告。--project给这次扫描命名,方便在报告里区分多个项目。
Dependency-Check 首次运行会下载 NVD 数据,数据量很大,可能要等十几分钟到半小时。如果 Jenkins 里跑,建议把数据缓存挂到固定目录,否则每次构建都重新下载,时间浪费在无意义的网络等待上。可以用--nvdApiKey指定 NVD API Key 来加速同步;如果是内网环境,则要把 NVD 镜像提前准备好,再通过镜像参数指定。没有网络的内网审计,常见做法是在外网机器上导出依赖清单,扫描后把结果带回内网分析。
拿到 JSON 结果后,重点关注vulnerabilities数组里的cvssv3.baseScore和cwe。高危组件通常有个明显特征:CVSS 9.x 且影响多个版本。但这里有个坑:NVD 里把很多组件的漏洞描述得特别宽泛,导致误报率不低。比如某个组件只有某个不起眼的模块受影响,Dependency-Check 也会标记整个 jar 包为高危。所以对高危组件,最好到厂商官网或 GitHub Releases 看漏洞修复版本,确认影响范围再写进报告。
3.3 误报清洗:把结果变成可审计的缺陷台账
两轮工具跑完后,会产生大量原始命中。这些命中不能直接进报告,要先清洗。我的清洗流程分四步:
第一步,合并 SAST 和 SCA 的结果,按文件路径、规则 ID、组件名称建立统一的缺陷台账;第二步,根据范围清单排除测试代码和生成代码;第三步,对剩余命中逐条做"三问"——这个输入是外部可控的吗?可控的输入能到达危险函数吗?到达后是否有过滤或编码?三问都满足才是真漏洞;第四步,给每条缺陷分配一个唯一 ID,如AUDIT-2025-001,并标记状态:确认、待修复、误报、重复。
清洗过程中我一般写一个 Python 脚本来合并 JSON 结果,减少手工操作:
import json def load_semgrep(path): with open(path) as f: data = json.load(f) rows = [] for r in data.get("results", []): rows.append({ "tool": "semgrep", "rule": r["check_id"], "path": r["path"], "line": r["start"]["line"], "severity": r["extra"].get("severity", ""), "message": r["extra"].get("message", "").splitlines()[0][:200] }) return rows def load_dc(path): with open(path) as f: data = json.load(f) rows = [] for d in data.get("dependencies", []): for v in d.get("vulnerabilities", []): rows.append({ "tool": "dependency-check", "rule": v.get("cwe", "CWE-unk") + ":" + v.get("name", ""), "path": d.get("fileName", ""), "line": 0, "severity": "HIGH" if (v.get("cvssv3", {}) or {}).get("baseScore", 0) >= 7 else "MED", "message": v.get("description", "")[:200] }) return rows if __name__ == "__main__": all_rows = load_semgrep("semgrep-java-err.json") + load_dc("dc-result") with open("audit-table.json", "w") as f: json.dump(all_rows, f, ensure_ascii=False, indent=2) print(f"total hits: {len(all_rows)}")这段脚本把 Semgrep 和 Dependency-Check 的结果统一成同一结构,输出到audit-table.json。之后用 Excel 打开这个 JSON,或者再写几行代码转成 CSV,就能在表格里逐条审。脚本里的severity字段统一了大小写,message截断到 200 字符,避免表格被过长的描述撑爆。参数上,你可以根据自己的规则过滤条件调整severity阈值,比如只保留 MED 及以上。
清洗过程中的血泪经验是:永远不要在清洗前开始写报告。否则你写的是一个工具输出摘要,不是安全审计结论。真正的结论是经过人工判定后,从几百条命中里筛出的十几条真实可利用缺陷。
4. 高危漏洞的代码定位:SQL 注入、硬编码密钥与不安全反序列化的审计要点
清洗后留下来的都是"疑似高危"。这一章挑三个最常见的高危类别,讲怎么在代码里确认它们是真漏洞而不是误报。这三个类别差不多占了审计报告高危项的八成,而且都是攻击者最喜欢利用的点。
4.1 SQL 注入:从入口函数到拼接点的污点追踪
SQL 注入的审计核心是回答一个问题:用户输入是否未经任何处理地拼进了 SQL 语句。静态代码里最容易看到的是字符串拼接。下面这段是典型的漏洞代码:
public List<User> findUser(String name) { String sql = "SELECT * FROM users WHERE name = '" + name + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // ... }审计时先找executeQuery、executeUpdate、prepareStatement这类数据库执行函数,再看传入的sql字符串是不是由变量拼接的。上面例子中name直接被拼进字符串,没有任何参数化处理,可以确认是 SQL 注入。
但实际业务代码里往往没有这么直白。一个接口从 Controller 接收参数,传给 Service,Service 再拼 SQL。这时要向上追溯name的来源链路。我一般用 Semgrep 的污点追踪规则,比如p/java里的注入规则会自动标记 source 到 sink 的路径。没有规则时,就手动在 IDE 里按住 Ctrl 点击方法调用逐层看。重点看是否在链路某处做了escapeString、replaceAll("'", "''"),或者用了 MyBatis 的${}而不是#{}。
MyBatis 里有个典型误用,是审计时的高频点:
<select id="queryUser" resultType="User"> SELECT * FROM users WHERE name = '${name}' </select>${name}是字符串替换,和拼接 SQL 完全等价;#{name}是预编译参数,才安全。审计报告里写清这一点,研发改起来非常顺手。还有一类是 Order By 的列名/排序方向拼接,数据库不能参数化,但可以通过白名单映射解决。这类问题危害不低,容易被漏掉,因为不显眼。
4.2 硬编码密钥与凭据:正则匹配之外的上下文判断
硬编码密钥的审计是另一个高频项。扫描器通常用正则找类似password = "xxx"、secretKey = "xxx"的模式。但这类正则误报率极高,因为变量名可以叫password,实际存的是用户输入的表单字段。真正的硬编码密钥有很强的上下文特征。
看这个例子:
private static final String AES_KEY = "s3cr3t!Key123456";AES_KEY是静态常量,值是一个固定字符串,且类名/变量名暗示这是加密密钥。这种可以直接判定。还有一种更隐蔽的,密钥会做编码或拆分:
private static final String PART1 = "s3cr"; private static final String PART2 = "3tKey"; private static final String fullKey = PART1 + PART2;审计时,如果把所有字符串常量拉出来做熵检测,能更快定位这类问题。熵值高的字符串(包含大小写字母、数字、特殊字符且无明显含义)很可能是密钥或令牌。可以用一个简单脚本对代码库里的字符串常量做熵分析:
import re import math from collections import Counter def entropy(s): if not s: return 0 p, lns = Counter(s), float(len(s)) return -sum(count / lns * math.log2(count / lns) for count in p.values()) str_pattern = re.compile(r'["\']([^"\']{12,})["\']') for line in open("AppConfig.java", encoding="utf-8", errors="ignore"): for m in str_pattern.finditer(line): s = m.group(1) if entropy(s) > 3.5: print(f"{entropy(s):.2f} {s}")这个例子只扫描单个文件,实际用时会遍历整个仓库的.java、.py、.js文件。超过 3.5 的高熵字符串先人工看一眼,是密钥就标注,是密码算法里的固定盐则要确认是否可分离。这里要提醒:不是所有高熵字符串都是密钥,JWT 的签名算法填充值、某些 UUID 也可能高熵,必须放在上下文里判断。报告里对硬编码密钥的处置建议,一般不是"删掉"而是"迁移到密钥管理服务,比如 Vault、KMS,或环境变量注入"。
4.3 不安全反序列化:识别危险入口与 gadget 链痕迹
反序列化漏洞的审计比前两个更依赖对框架和组件生态的了解。核心在于找危险的反序列化入口。Java 里常见的有ObjectInputStream.readObject()、XMLDecoder.readObject()、fastjson JSON.parseObject()以及各类 RPC 框架自带的序列化协议。
看这个例子:
public void doPost(HttpServletRequest req) throws Exception { ObjectInputStream ois = new ObjectInputStream(req.getInputStream()); Object obj = ois.readObject(); // ... }req.getInputStream()是外部可控的输入,readObject()会从流里还原任意 Java 对象。如果 Classpath 里有 Commons-Collections、Spring 等常见 gadget 库,攻击者就可能构造恶意序列化数据实现 RCE。审计时,除了代码本身,还要看依赖里是否有历史上有知名 gadget chains 的库。
Fastjson 的审计要点是版本和autoType配置:
JSONObject obj = JSON.parseObject(text, Feature.SupportNonPublicField);如果项目还在用 1.2.24 或 1.2.25 之前版本,且配置了autoType支持,几乎可以确认高危。新版 fastjson 已经默认关闭 autoType,但稳妥起见还是建议换成 Jackson 或 Gson。审计报告要把"影响版本"和"推荐替代方案"写清楚,研发才愿意改。反序列化问题的修复通常有两类:一是入口处校验数据类型白名单;二是升级组件并关闭危险特性。如果你在报告里只写"存在反序列化漏洞"而不给修复方向,这个报告基本等于被研发打回重写。
5. 安全审计避坑:5 个让报告翻车的常见问题与排查方法
做了几年的源代码审计,踩过的坑比扫出来的漏洞多。下面五条是让报告翻车的高频原因,每条按现象、原因、解决来写,可以直接对照自查。
5.1 现象:扫描器报告里出现大量"文件类型不支持"告警,但程序完全正常
原因:SAST 工具对某些语言或构建产物识别不了。比如 Lombok 注解处理后的.java文件、JSP 编译后的 class,或者.vue单文件组件。Semgrep 对 Java 支持较好,但对 JSP、Vue 模板的支持容易漏报。工具不识别不等于没有漏洞,只能说明这部分代码不在工具能力范围内。
解决:在报告里明确写出"本次工具覆盖范围,以下语言/文件类型不在扫描范围",并列出清单。对于 JSP、Vue 等特殊文件,安排人工审计或使用专门模板扫描规则。千万不要把工具的解析能力误解为代码安全。审计结论里必须留出"已知限制"一节,否则后续出了问题,别人会反过来质疑你"当时为什么没审出来"。
5.2 现象:同一个漏洞在工具报告里被重复标了十几次,看起来危机四伏
原因:SAST 工具基于函数调用链做数据流分析,当一个危险函数被多个入口引用时,会为每条调用路径生成一条独立告警。它们本质上是同一个根因,比如一个公共查询方法没做参数化,所有调用它的接口都被标记了。
解决:用"根因聚合"的方式去重。按危险函数的定义位置聚合,而不是按调用点。报告中只保留一个主缺陷条目,在"影响范围"里列出所有受影响的调用路径。这样报告从五百条降到三十条,可读性大幅提升,研发也愿意逐条看。聚合的关键是确定根因位置,我通常会以"包含危险函数的那个方法"作为主条目,并把调用链作为附件列在缺陷后面。
5.3 现象:高危漏洞已确认,研发也改了,复测时又报出来,双方僵持
原因:大多数情况是编译产物或打包产物没有被清理干净。复测扫描的是整个目录,前一次构建的target/、node_modules/、dist/里残留着旧代码的快照。SAST 工具扫描了多个副本,自然会报出旧的漏洞。
解决:复测前先执行一次干净的清理操作:
# 前端项目清掉旧构建产物 rm -rf node_modules dist npm ci npm run build # Java 项目清掉旧 class 和目标 jar mvn clean mvn compile另外要在扫描命令里明确排除输出目录,Semgrep 用--exclude target,Dependency-Check 用--exclude参数。这条是复测流程里最容易被忽略的坑,回头想想,大概有一半的"复测不通过"是因为这个原因。
5.4 现象:依赖组件报了 CVE,但升级到最新版后系统启动失败,功能异常
原因:组件漏洞修复通常不兼容旧 API。比如某个基础库从 2.x 升到 3.x,包名和类路径全变了,代码要跟着改。研发一升级发现编译错误,就把漏洞搁置了,结果审计报告里高危到处飘。
解决:在报告里,对依赖组件漏洞给出"最低修复版本"而不是"最新版本"。去 NVD 或厂商安全公告里查该 CVE 的 affected 版本范围和 fixed 版本,写清楚"升级到 X.Y.Z 可修复",同时建议研发在测试环境做兼容性验证。如果某个组件确实无法升级,必须写明缓解措施,比如禁用某个接口、加网络层访问控制,并在报告里标记为"暂缓风险"并设置复核周期。这样既推动修复,又不让研发陷入"修复等于重构"的恐慌。
5.5 现象:报告读起来像扫描器输出,每条标题是"Semgrep 发现 SQL 注入",但研发不知道改哪里
原因:工具输出的路径、行号、规则 ID 是一堆机械信息,缺少"业务风险描述"和"修复方式"两块关键内容。研发打开报告,看到的只是"某行某文件有漏洞",还得自己一步步找,耗时耗力。
解决:每条缺陷的描述强制使用结构化模板:
| 字段 | 内容示例 |
|---|---|
| 问题 ID | AUDIT-2025-001 |
| 风险等级 | 高危 |
| 位置 | UserService.java:42 |
| 问题描述 | name参数直接拼接 SQL 语句 |
| 触发路径 | GET /user/search->UserService.findUser->Statement.executeQuery |
| 危害 | 攻击者可构造' OR '1'='1绕过登录校验,读取全部用户数据 |
| 修复建议 | 改用PreparedStatement,占位符绑定参数 |
| 修复参考 | 见附录 A:PreparedStatement 改造示例 |
报告的价值在于降低理解成本,不是把工具结果转置一下。我给团队定的要求是:每条缺陷至少要写三句话,第一句说清楚问题在哪、第二句说清楚会造成什么后果、第三句说清楚怎么改。多读几遍这三句,报告的质量就不会太差。
6. 报告结构、整改闭环与复测:让审计报告真正落地
6.1 报告正文的 7 个板块
一份能直接交付的《系统源代码安全审计报告.pdf》,我一般按 7 个板块组织:审计概述、审计方法、风险综述、缺陷详情、整改建议、复测记录、附录。审计概述里写清审计对象、范围清单、commit 版本、审计时间;审计方法里写工具和版本、规则集、人工复核方式;风险综述里用表格列出各等级缺陷数量,不用饼图——饼图在纸质报告里信息密度太低;缺陷详情按严重度排序,每条包含定位、危害、修复建议;整改建议给优先级和责任矩阵;复测记录留出首次与复测结果对比;附录放原始扫描输出和误报清单。
6.2 复测验证:用同一套命令证明漏洞已经关闭
复测是闭环的收尾,但必须用和首次审计一致的命令、规则集和排除项,否则结果没有可比性。我习惯把扫描命令存成脚本:
#!/usr/bin/env bash # 首次与复测使用同一套命令,保证结果可比 set -euo pipefail semgrep scan --config "p/java" --exclude "target" --json --output semgrep.json . jq -r '.results[] | "\(.path):\(.start.line):\(.check_id)"' semgrep.json | sort > semgrep-findings.txt复测后,用comm对比新旧清单:
comm -23 semgrep-findings.txt previous-findings.txt # 仍存在的问题 comm -13 semgrep-findings.txt previous-findings.txt # 已消失的问题comm要求输入文件已排序,所以脚本里先sort。这样复测报告可以明确列出"已修复 12 条,剩余 2 条,其中 1 条为误报已人工确认",而不是一句模糊的"复测未通过"。
最后提醒一个习惯:报告发给研发前,让团队里另一个人按报告里的定位步骤去代码里复现一遍。复现不出来,就说明定位描述不准确,需要改。每一条缺陷都要经得起研发逐行验证,而不是只经得起安全组自嗨。这个习惯来自我踩过的坑,看似多花几小时,实际上省了无数轮"这里到底在哪"的来回沟通。希望帮到你。
本文还有配套的精品资源,点击获取