mcpField
C
convex-authz

convex-authz

github.com/convex安全6937.2k 次安装1 天前更新
代码仓库

关于 convex-authz

convex-authz 是面向 Convex 项目的授权审计与加固技能。它扫描四类特定缺陷,应用既定的身份与所有权检查,并通过 TypeScript 验证修改。

主要功能

  1. 01确定性扫描四类授权问题
  2. 02加固前检查认证基础
  3. 03应用身份与所有权检查
  4. 04使用 TypeScript 验证修改

适用场景

  1. 01查找客户端传入的身份参数
  2. 02检查记录访问前的所有权
  3. 03保护个人或财务查询结果
  4. 04验证修改后的授权问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/convex/agent-skills --skill convex-authz

安装后在 Claude Code 中输入/convex-authz确认已加载。

详细介绍

Convex 授权审核器/加固器

专注于授权的专家,而非通用审查工具:它会查找并修复四种问题形态,这些问题形态在针对生成的 Convex 后端测得的真实缺陷中占比最高(从参数获取身份的缺陷有 25 个 + 缺少所有权检查的缺陷有 13 个 + 通过参数泄露 PII 的缺陷有 6 个 = 已确认的 214 个缺陷中有 44 个,此外还包括所有权问题形态的写入时父级引用变体;夹具测量显示,扫描这 3 种形态会漏掉该变体)。它会先运行确定性扫描(客观且基于正则表达式),然后将 convex-expert.md 中的标准 requireIdentity/requireOwner 加固模式应用于每个命中项,最后通过 tsc 验证。它不会重新推导该模式,而是应用已记录为平台标准修复方案的模式。

工作流

  1. 强制的第一步——在注入任何 ctx.auth 强制检查前,先确认认证基础是否已就绪:(1)是否存在包含 provider 的 auth.config.ts?(2)是否存在以认证主体为键的 users/identities 表(tokenIdentifier/identity.subject)?如果任一条件不满足,不要添加 requireIdentity/requireOwner——在没有认证基础的应用中,ctx.auth.getUserIdentity() 总是返回 null(强制检查无法生效:每次调用都会返回 401;更糟的是,检查可能被绕过,或错误地与非主体字段(例如 email 字符串)比较),审查者会正确地将此标记为新的授权缺陷,而非修复。对于没有认证基础的应用,应改为:(a)对于特权/管理员操作,将公开 query/mutation 转为 internalQuery/internalMutation(彻底移除公开可访问性——安全且无需认证基础,也不需要 ctx.auth);(b)告知用户:『此应用没有认证基础;请先运行 /add auth 或完成认证设置,然后重新运行 convex-authz 以添加逐用户所有权检查。』对于没有认证基础的应用,除执行此内部化并暂缓处理外,不要对下方步骤 1–3 中的公开函数采取任何操作。只有在认证基础就绪(auth.config.ts 和以主体为键的 users 表均存在)时,才继续执行步骤 1–3,注入 requireIdentity/requireOwner。
  2. 扫描(确定性优先,先检查客观条件):对每个 convex/**/*.ts 文件(跳过 convex/_generated/ 和 .d.ts),搜索以下四种模式: (a) 从参数获取身份:公开的 query(/mutation( 对象,其 args 块声明了类型为 v.id(...) 的 userId/actorId/ownerId/authorId/accountId,且函数的整个块(args + handler)中完全没有 ctx.auth。正则表达式:在 args: { ... } 块中匹配 /\b(userId|actorId|ownerId|authorId|accountId)\s*:\s*v\.id\(/,且其所在的 (query|mutation)\(\s*\{ ... } 块中不存在 /\bctx\.auth\b/(词边界会自然排除 internalQuery/internalMutation)。 (b) 缺少所有权检查:公开的 query(/mutation( 通过 handler 使用 ctx.db.get(args.<xId>)(类型为 _id 的参数)加载文档,然后对同一 id 调用 ctx.db.patch/ctx.db.delete/ctx.db.replace,或直接返回该文档的字段,且整个块中没有将任何 <doc>.<ownerField> 与身份值比较(没有涉及 identity.subject 或从 ctx.auth 派生值的 ===/!==)。 (c) 公开 query 泄露 PII:公开的 query( 的 returns(或它返回的原始文档)包含看起来敏感的字段(email、revenue、ssn、password、token、auditLog、dashboard 形态的聚合数据),且该 query 由客户端提供的 id 参数化,却没有通过 ctx.auth 检查限制为该 id 所属用户自己的范围。 (d) 写入时未检查父级引用的所有权:公开的 mutation( 的参数包含某个父级/容器表的 v.id(...)(projectId、boardId、teamId、orgId、listId、folderId、conversationId、accountId 等),handler 将其作为外键用于 ctx.db.insert/ctx.db.patch——即将子行附加到该容器或移入该容器——但未验证调用者拥有该父级文档(或是其成员)。在他人的容器中创建行,与修改他人的行是同一种缺陷:修复调用者身份(模式 a)并不能解决调用者可以在哪里写入的问题。处理完模式 a–c 后,重新审查每个公开 mutation 中所有剩余的 v.id(...) 参数,查找此模式——修复模式 a 时经常会遗漏父级 id 参数,导致它仍未受检查。 报告每个命中项,注明文件、行号以及匹配的 4 种模式中的哪一种——这是客观且与模型无关的基准;不要跳过这一步,直接凭判断处理。
  3. 强化(仅限具备认证基础的应用——见步骤 0):对每个命中项,严格套用 content/convex-expert.md 中的规范模式——不要自行发明新的 helper。如尚不存在,则添加 convex/model/auth.ts 并导出 requireIdentity(ctx)(当 ctx.auth.getUserIdentity() 为 null 时抛出 401;否则返回该 identity)和 requireOwner(ctx, doc)(当 doc 为 null 时抛出 404;当 doc.ownerId !== identity.subject 时抛出 403;否则返回 doc)。重写每个标记的函数:用 requireIdentity(ctx) 替换客户端提供的身份参数;在访问每个以 _id 为键的行之前,先用 requireOwner(ctx, await ctx.db.get(args.xId)) 包装其读取/修改操作;对于每个返回 PII 的 query,在读取调用者自身范围之外的数据前,通过 requireIdentity/requireOwner 限定范围(或进行明确的 staff/role 检查);对于每个模式 (d) 命中项,在插入/修改子行之前,先加载被引用的父级文档并应用 requireOwner(ctx, parent)(或者在容器通过数组表示成员时,执行 schema 的成员检查——例如 participantIds.includes(user._id))。如果 schema 使用 users 行 id 而不是原始 subject 来关联所有权,则先解析调用者对应的 users 行(通过以 subject 为键的索引),并与 user._id 比较——将 Id<"users"> 字段与 identity.subject 比较永远不会匹配,会悄无声息地破坏强制检查。绝不要扩大访问范围——合法处理任意用户数据的内部/管理员函数应保持为 internalQuery/internalMutation,绝不要改成公开函数;不要将其标记或修改。
  4. 验证:编辑后运行 npx tsc --noEmit(或项目的类型检查脚本);无法通过类型检查的强化工作尚未完成。然后重新运行步骤 1 的扫描,确认剩余命中项为 0(修复后的模式不再匹配这些正则表达式,因为 ctx.auth 现在会出现在块内,且所有权比较现在也已存在)。
  5. 按 4 种规则模式分组报告发现,注明 file:line,解释每项为何可被利用(谁能冒充谁/读取谁的数据),并展示实际应用的 diff(如果应用没有认证基础,则展示内部化并暂缓处理的 diff,以及认证设置提示)——绝不要只用文字描述修复。

规则

  • 强制第一步:注入 requireIdentity/requireOwner 之前,先确认认证基础设施已就绪——存在配置了 provider 的 auth.config.ts,以及以认证主体为键的 users/identities 表。若缺少其中任一项,不要添加基于 ctx.auth 的强制校验(这类校验无法运行或会产生不匹配,造成新的授权缺陷);应将标记出的公开管理/特权函数改为 internalQuery/internalMutation,并告知用户先运行认证设置,再重新运行 convex-authz。
  • 先客观扫描,再做判断——先运行 4 条确定性 grep 命令;不要直接跳到 LLM 判断,也不要因为扫描结果干净就不再检查内部/管理功能的豁免情况。
  • 身份始终来自 ctx.auth,绝不能来自客户端提供的参数——唯一合理的例外是从未公开暴露的 internalQuery/internalMutation/internalAction。
  • 每个由 _id 参数指定的读取或修改操作,都必须在访问该行之前于服务端验证所有权(使用 requireOwner 或内联等效比较)——已登录不等于拥有该行。
  • 公开 mutation 在插入或移动行时,若使用任何 v.id(...) 参数作为外键,都必须先验证调用者拥有(或属于)被引用的父项——在他人的项目/看板/账户中创建子项,与修改他人的行一样属于缺陷;即使修复了从参数获取身份的问题,若未单独检查此项,缺陷仍会存在。
  • 绝不能让未认证或跨账户的客户端仅凭提供的 id 就能访问返回个人身份信息/财务/审计数据的公开 query。
  • 原样复用 content/convex-expert.md 中的 requireIdentity/requireOwner——不要另建平行 helper,也不要另创错误语义。
  • 加固后务必使用 tsc 验证;无法通过类型检查的修复不得交付。
  • 这是针对授权的专项检查,不是一般代码审查——不要扩大范围去检查性能/schema/validator 问题;这些问题交由 convex-reviewer 处理。
  • 若项目中没有 convex/ 目录,则完全跳过。

评论

支持 Markdown