关于 sql-security
用于检查宿主语言代码中 SQL 拼接方式的安全技能。Node.js 扫描器会标记特定的单行插值和字符串拼接,并说明检测盲区及使用 secretlint 检查凭据的方法。
主要功能
- 01扫描源码中的 SQL 插值模式
- 02标记 SQL 字符串拼接
- 03忽略生成文件和测试文件
- 04用注释跳过已审查结果
- 05使用 secretlint 检测凭据
适用场景
- 01推送更改前运行 SQL 扫描
- 02将扫描结果加入 PR 评审
- 03手动核查可能的误报
- 04检查仓库中的硬编码凭据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mizchi/ skills --skill sql-security安装后在 Claude Code 中输入/sql-security确认已加载。
附带文件README.mdscripts/sql-injection-scan.mjs
详细介绍
SQL 安全
当项目通过宿主代码(MoonBit / TS / Rust / Go)执行 SQL,并希望针对 SQL 领域事故的两大常见来源设置一道低成本防线时使用:
- SQL 注入:字符串模板将值插入 SQL 片段,而不是通过占位符绑定。
- 查询中的机密信息:硬编码的令牌或连接字符串泄漏到 git 历史记录中。此问题由
secretlint处理,而不是本技能——请参阅下方的“配套工具:secretlint”。
sql-injection-scan.mjs
node scripts/sql-injection-scan.mjs your-project/src
扫描器会遍历目录,忽略生成文件(db/gen/、sqlc_*.mbt、*.test.mbt、_build/、dist/、target/),并标记:
- template-interp:以 SQL 关键字(
SELECT、INSERT INTO、WHERE、AND (等)开头且包含${...}占位符的反引号字符串字面量。示例:`WHERE c.vector_id IN (${placeholders})`。 - string-concat:引号括起的 SQL 字符串与
+和标识符相邻。示例:"SELECT * FROM " + table。
关键字匹配有意区分大小写——from / where / join 经常出现在英文文本中;如果忽略大小写进行匹配,会产生数百条误报。
发现问题时,扫描器以状态码 1 退出——每个命中项都值得人工审查,即使最终确认它是安全的。
局限性——扫描无命中并不能证明安全
扫描器逐行读取内容。其模板字面量规则要求起始反引号、${...} 占位符和结束反引号都位于同一物理行。因此:
-
多行模板字面量会被静默漏检。 如果查询的起始反引号位于一行,而
${value}位于后续行(这是一种常见的格式风格),扫描器将不报告任何问题并以状态码 0 退出,尽管这确实存在注入风险。以下是扫描器无法捕获的示例:const sql = ` SELECT id FROM users WHERE name = ${name} // <-- real injection, not flagged `; -
状态码 0 表示“未发现单行命中项”,绝不表示“安全”。 审查 SQL 注入风险时,也要手动检查任何多行或以编程方式拼装的 SQL;不要把扫描无命中当作通过。若希望扫描器检测到多行查询,可以将其折叠为单行。
这是在成本和覆盖率之间有意做出的取舍(零依赖、无解析器),并非错误——但这是一个盲点,你必须在审查中加以弥补。
阅读扫描结果
发现问题并不一定意味着存在注入。以下情况都可能是合理的:
- 内部占位符扩展:根据服务端得出的数量,为 IN 子句构造
?,?,?,...,?。插入的值始终是?,而非用户输入。添加选择退出注释,以便后续扫描可以忽略它。 - sqlc 无法解析的 FTS5 / 向量查询:动态子句构造器会插入列列表或
?的数量。用户输入仍会通过.bind(...)传入。 - 内联迁移脚本:在部署时构造 DDL。
扫描器有意设置得较为敏感,因为漏掉一次真实 SQL 注入的代价,远高于排查 5 条误报的代价。
选择退出标记
要忽略已知安全的行,请在该行或上一行添加包含标记 sql-security: ok 的注释:
// sql-security: ok (placeholders is server-derived `?` count, values bind separately)
const sql = `WHERE c.vector_id IN (${placeholders})`;
扫描器接受 //(TS / MoonBit / Rust / Go)和 #(Python / shell)注释标记。
配套工具:secretlint
对于凭据泄漏(即“SQL 安全”的另一部分),请使用 secretlint。对于由 pkfire 管理的仓库,建议在 pre-push 钩子中进行如下设置:
pnpm add -D secretlint @secretlint/secretlint-rule-preset-recommend
然后在 pre-push 时运行:
pnpm exec secretlint --secretlintignore .gitignore "**/*"
这是针对单个仓库的设置,可与任何用户全局的 secretlint 配置互补。mizchi/skills/pkfire 技能为使用 pkfire 钩子的项目提供了现成的方案:assets/recipes/14-secretlint-pre-push.pkl。
何时调用
- pre-push:成本低,在将代码发送到远程之前运行一次。
- pre-commit:可选。在这里运行
secretlint可能会因差异较大而变慢;sql-injection-scan的运行成本足够低。 - PR 审查:将扫描器输出粘贴到审查清单中。
不在范围内
- 存储型 XSS / 模板注入:这类问题经过前端 / 模板层,而非 SQL。
- 授权绕过:行级访问检查属于应用逻辑,而非 SQL 问题。
- 通过高开销查询造成 DoS:由
sql-plan-audit(查找新增的 SCAN 条目)和边缘限流来处理。
引擎扩展性
扫描器的正则表达式不依赖特定引擎。SQL 关键字集开箱即支持 SQLite / Postgres / MySQL。如果项目大量使用 Postgres,需要更广泛的覆盖范围,可以添加 RETURNING / OVERLAPS / LIMIT OFFSET。
要求
- Node 20 或更高版本。
文件
scripts/sql-injection-scan.mjs— 零依赖文本扫描器。