mcpField
S
sql-security

sql-security

github.com/mizchi安全41654 次安装1 周前更新
代码仓库

关于 sql-security

用于检查宿主语言代码中 SQL 拼接方式的安全技能。Node.js 扫描器会标记特定的单行插值和字符串拼接,并说明检测盲区及使用 secretlint 检查凭据的方法。

主要功能

  1. 01扫描源码中的 SQL 插值模式
  2. 02标记 SQL 字符串拼接
  3. 03忽略生成文件和测试文件
  4. 04用注释跳过已审查结果
  5. 05使用 secretlint 检测凭据

适用场景

  1. 01推送更改前运行 SQL 扫描
  2. 02将扫描结果加入 PR 评审
  3. 03手动核查可能的误报
  4. 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 领域事故的两大常见来源设置一道低成本防线时使用:

  1. SQL 注入:字符串模板将值插入 SQL 片段,而不是通过占位符绑定。
  2. 查询中的机密信息:硬编码的令牌或连接字符串泄漏到 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 — 零依赖文本扫描器。

评论

支持 Markdown