AI 工具基于大量 v1 代码训练,生成已弃用的 @validator 和 orm_mode 模式;v2 中行为改变,某些情况会静默接受非法数据。
AI assistants 的训练数据主要来自 2023 年以前的 Python 代码,因此它们在 Pydantic 上存在一个问题。Pydantic v2 于 2023 年 6 月发布,重写了 validator 核心——执行顺序不同、类型强制转换规则不同、配置键也被重命名——但公开代码仓库中的 v1 代码仍然远多于 v2。如今,当 Copilot 或 Claude Code 生成 FastAPI 路由时,输出结果经常仍在使用 @validator 装饰器和 orm_mode = True。这两种都是 v1 模式,在 v2 中要么行为不同,要么直接失效。这些代码可以通过类型检查,在许多配置下运行时也不会报错,但其中一部分会悄无声息地接受 API 从未打算接收的数据。
Pydantic v2 对验证行为所做的一些修改,会让 AI 生成的模型以不易察觉的方式出现问题:v1 的 validator 装饰器被替换为执行顺序不同的 field_validator;orm_mode = True 变成了 model_config = ConfigDict(from_attributes=True);数值类型的强制转换行为也发生了变化。对于基于 v1 模式训练的 AI 来说,这些差异都很容易处理错误。BrassCoders 的 Semgrep scanner 可以标记 v2 代码库中已弃用的 v1 模式,让 CI 能够在 AI assistant 使用过时语法时给出确定性的信号。
影响最深远的变化是 validator 的执行时机。在 v1 中,@validator 会在字段赋值周期的早期运行,也就是 Pydantic 最终确定字段值之前。在 v2 中,@field_validator 会按照明确指定的 mode 运行,而 mode 决定了函数接收到什么值,以及它必须返回什么类型。如果 AI 生成的 validator 返回了与其 mode 不匹配的类型,代码可能会在运行时抛出异常,也可能被悄无声息地强制转换成与类型注解描述不一致的值。
AI assistants 在生成代码时不会查阅 Pydantic v2 迁移指南。它们只是根据训练数据中的模式进行匹配,而这些数据明显偏向 v1。这是结构性问题,并非偶发现象——对不同的 AI assistants 使用同一个 prompt,你会看到相同的模式反复出现。
BrassCoders 的 Semgrep scanner 可以标记 v2 代码库中已弃用的 Pydantic v1 模式,但 AI 生成代码中更深层的验证反模式包括:validator 在字段赋值前运行,并悄无声息地返回错误类型;在 API 业务逻辑实际将某个字段视为必填时,却将其声明为 Optional[str];以及在不应发生强制转换的字段上遗漏 strict=True,导致错误输入被悄无声息地接受。
Optional[str] 反模式是其中最稳定、也最危险的一种。AI assistant 在生成用户资料模型时,经常会把 email 字段声明为 Optional[str]——如果业务规则允许 null,这在技术上确实没有问题——但随后路由处理函数却直接对该值调用 .split('@'),没有先检查 None。模型接受了 None,路由却抛出 AttributeError。类型注解承诺两种值都有效,但结合实际上下文,真正有效的只有一种。
缺少 strict=True 的问题更加隐蔽。默认情况下,对于 int 字段,Pydantic v2 会把 "123" 强制转换成 123。如果 API 应该拒绝字符串,只接受真正的整数,就需要使用 int = Field(..., strict=True)。AI assistants 几乎从不主动添加这一配置。请求发送 "123" 而不是 123 时,仍然能够通过验证;进入业务逻辑后,它看起来与格式正确的请求完全相同。对于 ID 字段或财务金额等必须精确区分类型的场景,这种强制转换缺口会带来数据完整性风险。
mypy 验证的是类型注解在结构上是否成立,并不会验证运行时的强制转换行为是否符合类型注解的真实意图。BrassCoders 的 Pyre/Pysa scanner 更进一步:Pysa 会跨越类型边界执行污点分析,并标记用户可控输入流向安全敏感 sink 的数据流,无论类型注解声称这些数据是什么类型。
具体的失败模式是:mypy 检查 Optional[str] 后,会确认 str | None 是有效类型。它不会追踪路由处理函数中的调用代码,检查其中是否在访问字符串方法之前对 None 做了防护。类型注解在结构上没有问题,但运行时行为存在问题——而 mypy 对两者都不会提出异议。
Pysa 采用了不同的方法。它会追踪数据从 HTTP request body——也就是污点 source——流经 Pydantic 模型赋值,再进入下游代码的完整过程。如果用户可控输入在未经 sanitizing function 处理的情况下进入数据库查询、文件路径或 subprocess 调用,Pysa 就会将其标记出来。FastAPI 文档中关于 request validation 的部分描述了 Pydantic 在数据进入系统时会验证什么;Pysa 则描述了验证通过之后会发生什么。
从静态角度看,代码似乎完全正确。但在运行时并非如此——而这种差异通常只有在格式错误的请求命中线上路由时才会暴露。
BrassCoders 的 Semgrep 和 Bandit scanners 会标记 AI 生成的 FastAPI/Pydantic 代码中与安全相关的模式:路由处理函数里的原始 SQL 查询、缺失的身份验证依赖,以及已弃用的 Pydantic 模式。这些旧模式往往意味着 v1→v2 迁移由 AI 完成,但 AI 并未同步更新自己的行为模式。
Semgrep 规则将 @validator 和 orm_mode 作为 v1 模式的信号。两者在 v2 中都已弃用,却依然会被使用陈旧训练数据的 AI assistants 稳定生成。Bandit scanner 则会捕获路由处理函数中使用原始 f-string 或 .format() 拼接 SQL 的写法——正是这种模式,会把宽松的验证假设变成 SQL 注入入口。
安装只需要一条命令:pip install brasscoders && brasscoders scan .。其 OSS core 会在本地运行全部 12 个 scanners,不会向外发送任何数据。BrassCoders 发布的 benchmark 显示,单独使用 Bandit 可以发现 12 个 AI 生成的安全漏洞中的 6 个;完整运行全部 12 个 scanners,则能发现其中 11 个。BrassCoders Paid 的价格为每位开发者每月 12 美元,它会额外通过 hosted gateway 执行基于 embedding 的 enrichment pass,把原始发现缩减为最值得优先排查的子集。不提供试用,可随时通过 brasscoders portal 取消。
Pydantic v2 的验证缺口,本质上是训练数据问题。只要项目允许 AI assistant 写入代码,而 reviewer 又认为 mypy 检查干净就意味着安全状况没有问题,这类缺陷就会反复出现。在 v2 代码库中发现一个 @validator,就是一个值得尽早捕获的信号。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。