实战中如何用自动化工具完成高效漏洞扫描与修复
谁更灵活?谁更省事?
最近在给一个老项目补安全基建,第一件事就是加漏洞扫描。不是那种黑盒打靶式的渗透测试,而是前端构建阶段就卡住常见 XSS、硬编码密钥、敏感路径泄露这类低级但高频的问题。我试了三套方案:ESLint 插件(@eslint-community/eslint-plugin-security)、Snyk CLI、还有自己撸的简单 AST 扫描器(基于 acorn + estree)。没搞那些云 SaaS 扫描平台——不是不好,是它们太重,我们 CI 里跑不起来,而且很多规则根本没法自定义。
结论先甩出来:我日常用 ESLint 插件打头阵,Snyk 做兜底,AST 小工具只在特殊场景临时救火。下面说说为啥。
ESLint 插件:最顺手,但也最“骗人”
我比较喜欢用它,因为和现有开发流程零摩擦。装上、配个规则、commit hook 一挂,新人写完 localStorage.setItem('token', res.data.token),保存瞬间就报红:
// .eslintrc.cjs
module.exports = {
plugins: ['security'],
rules: {
'security/detect-object-injection': 'error',
'security/detect-non-literal-fs-filename': 'error',
'security/detect-non-literal-regexp': 'error',
'security/detect-eval-with-expression': 'error'
}
};
爽是真爽。但这里注意——我踩过好几次坑:这个插件对模板字符串的检测很弱。比如 fetch( 它默认不报,除非你手动开 /api/user/${id})detect-non-literal-fs-filename 的严格模式(文档里藏得深,翻了三次才找到)。还有一次,它把 JSON.parse(input) 当成安全的放过去了,结果 input 是从 URL 参数里拿的,后面被绕过了一次 SSRF。不是插件 bug,是它设计哲学就是“只检明显危险字面量”,不是做数据流追踪。
所以我的用法是:它只当第一道过滤网,不是保命符。规则开了 7 条,关了 3 条(比如 detect-unsafe-regex 太吵,正则写得丑但不危险,我让它过)。
Snyk CLI:笨但靠谱,适合扫“脏东西”
Snyk 我一般在 CI 的最后一步跑:snyk test --severity-threshold=high --json > snyk-report.json。它不看代码逻辑,专盯依赖里的已知漏洞(CVE),比如你用了 lodash@4.17.11,它立马告诉你:“嘿,这个版本有原型污染,CVE-2023-29822”。
它比 npm audit 好在哪?能扫出 webpack、postcss 这些构建时依赖的漏洞,npm audit 只扫 dependencies 和 devDependencies 顶层包。有一次我们上线前扫出 glob-parent@5.1.2 被间接引入,影响了整个打包流程的安全边界——这玩意儿 npm audit 根本看不见。
但它也有烦人的点:报告太长,一堆 medium 级别警告,CI 里看着心慌。我最后加了过滤脚本,只保留 high/critical 且可修复的:
snyk test --json | jq -r '
.vulnerabilities[] |
select(.severity == "high" or .severity == "critical") |
select(.isUpgradable == true) |
"(.name) (.severity) (.identifiers.CVE[0])"
' | head -n 10
这个脚本我扔进了团队共享 gist,谁都能抄走用。一句话总结:Snyk 不帮你写安全代码,但它能揪出你根本不知道自己用了的危险模块。
自己写的 AST 扫描器:小而锋利,但别上头
为什么还要自己写?因为 ESLint 和 Snyk 都搞不定的一类问题:硬编码密钥 + 敏感环境变量误提交。比如有人写了 const API_KEY = "sk_live_abc123...";,ESLint 规则再全也拦不住这种“语义正确但业务错误”的写法。
我用 acorn 解析 AST,匹配 VariableDeclarator 节点,再正则扫值是否含 sk_live|api_key|password|secret 这类关键词:
// scan-secrets.js
import * as acorn from 'acorn';
import fs from 'fs';
const code = fs.readFileSync('./src/utils/api.js', 'utf8');
const ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: 'module' });
function walk(node) {
if (node.type === 'VariableDeclarator' && node.init?.type === 'Literal') {
const value = String(node.init.value);
if (/sk_live|api[_-]key|secret/i.test(value)) {
console.warn(⚠️ 发现疑似硬编码密钥:${value.slice(0, 30)}...);
}
}
for (const key in node) {
if (node[key] && typeof node[key] === 'object') {
walk(node[key]);
}
}
}
walk(ast);
这个脚本就 40 行,跑得飞快,CI 里加一行 node scan-secrets.js 就行。但它有个致命缺陷:误报率高。比如 const DEFAULT_THEME = "dark"; 里带个 dark,正则一扫就中。所以我后来加了白名单和上下文判断(比如只扫 const.*=.*["'].*["']; 这种赋值语句),但维护成本上来了。
我的建议:这种小工具只在特定项目里用,别想着通用化。真要长期用,不如直接上 Git hooks + pre-commit 拦截 commit message 和文件名(比如禁止 push 包含 .env 的文件)。
性能对比:差距比我想象的大
我拿一个 200 个文件的 React 项目实测(Mac M1,Node 20):
- ESLint(开 security 插件):平均 12s
- Snyk CLI(首次运行,含依赖分析):48s;缓存后:8s
- AST 小工具:0.8s(纯 JS,无依赖)
ESLint 最慢,但它在 dev server 里是增量检查,实际开发中感知不到。Snyk 首次慢是因为要下载漏洞库,后面极快。AST 工具快是快,但功能太窄——它不会告诉你 fetch() 里拼接了用户输入,只管“有没有 sk_live”。
我的选型逻辑
现在我们项目的标准配置是:
- 开发阶段:ESLint + security 插件(开 7 条核心规则)+ commit hook 自动 fix
- CI 构建阶段:先跑 ESLint,再跑 Snyk(只报 high/critical),失败直接中断
- 发布前人工抽检:跑一遍 AST 小工具扫密钥(毕竟人肉看 config 文件还是容易漏)
没上商业扫描平台,因为我们的项目没有复杂后端交互,也不碰支付/金融数据,没必要为“可能的风险”付出 3000 元/年订阅费。如果你们是银行系统或者 SaaS 平台,那另说——但那就不是我这篇文章要聊的范围了。
以上是我的对比总结,有不同看法欢迎评论区交流。比如你用过 Semgrep?或者用 GitHub Code Scanning 搞出了什么骚操作?求分享。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论