AI 写的 Python 代码的安全审查 · 第 02 题

第 2 课:把威胁模型写成契约

检查点 l0210 分自我报告——认领码来自 verify.py任一编码 Agent + 攻击重放装置

对每个函数,说清谁控制输入、他想要什么、代码必须拒绝什么——再把 TASK.md 里的每一条拒绝追查到一个测试,以及它背后的 Python 文档。

在用 Agent 做题?一句话开始整门课程:

Read https://flypython.com/skills/flypython/SKILL.md and start the FlyPython course `secure-ai-generated-code`.

第 2 课:把威胁模型写成契约

目标

你能对每个函数说出每个输入由谁控制、他想要什么;把 TASK.md 里的每一条 拒绝追查到一个测试;并说出让这条拒绝可靠(而不是碰巧有效)的标准库机制。

为什么有这节课

「把它弄安全」是你能给 Agent 的最含糊的请求。它产出的是去掉引号、检查 ..、或者查找 admin 字样的输入过滤——攻击者每一个都能绕过去。写成契约 的威胁模型说清必须拒绝什么,让测试去检查;你选的机制则决定了这条拒绝对 没人写过测试的输入是否依然成立。

本课内容

把测试并排打开,读 TASK.md。对每个函数,契约回答三个问题:

函数谁控制输入他想要什么拒绝它的机制
find_notes搜索的人别的 owner 的笔记? 占位符——sqlite3 文档推荐的绑定值方式;再加 instr,让 % 和 _ 保持字面意义
read_attachment给文件起名的人进程能读的任何文件先 Path.resolve() 再 is_relative_to(base)——检查路径真正指向哪里,符号链接也算
load_secret_key读得到你源码的人签名密钥没有缺省值;不足 32 个字符就拒绝启动
hash_password / verify_password偷到数据库的人密码通过 hashlib.pbkdf2_hmac 做加盐慢哈希;比较用 hmac.compare_digest
dump_session / load_session持有 cookie 的人另一个身份——或代码执行JSON 加 HMAC-SHA256 签名,在解码之前校验;绝不用 pickle

每个选择的一手来源都是 Python 文档:

  • pickle: 模块文档开头就警告 pickle 不安全,只应反序列化你信任的 数据。会话 token 来自客户端——它永远不是你信任的数据。
  • sqlite3: 文档里关于占位符的操作指南展示了用 ? 绑定值,并警告 不要用字符串格式化拼 SQL。
  • hmac.compare_digest: 以抵御计时攻击为设计目标进行比较;对秘密值 用 == 做不到这一点。
  • secrets: 用于安全 token 和盐的模块——不是 random。

在密码哈希上,契约选了 600 000 次迭代的 PBKDF2-HMAC-SHA256,因为它在 标准库里,而且这正是 OWASP Password Storage Cheat Sheet 给 PBKDF2 的设置。 同一份 cheat sheet 首选 Argon2id,其次 scrypt,在需要 FIPS-140 合规时 推荐 PBKDF2。在真实项目里,判断哪种情况适用于你。

练习

在你熟悉的项目里挑一个接收请求、文件或消息队列输入的函数,为它填同样的 三列。然后检查:你的机制真的是机制,还是一个只去掉了你恰好想到的攻击 字符串的过滤器?

检查点

能回答下面三题时,用 python verify.py --attest l02 认领本检查点:

  1. Path(base) / "/etc/passwd" 的结果是什么?为什么契约拒绝所有绝对路径 的 name——哪怕它恰好指向 base_dir 之内?
  2. 为什么 load_session 必须在解码 payload 之前校验签名?
  3. 为什么「从查询里去掉引号」不是 find_notes 可以接受的修复?

预期证据

为你拥有的一个函数写的三列威胁模型,并标明其机制是机制还是过滤器。

提示

卡住了?逐条打开。

提示 1 —— 本检查点考察什么

你能把 TASK.md 当成一张拒绝清单来读,把每一条追查到一个测试,并说出是哪个 Python 特性让这条拒绝可靠。

提示 2 —— 先划信任边界

在 read_attachment(base_dir, name) 里,base_dir 来自你的配置,name 来自请求。契约故意区别对待它们。

提示 3 —— 用机制,不用过滤

绑定参数、解析后的路径、带密钥的签名:每一种都消除一整类攻击。坏字符串黑名单只消除你想到的那些字符串。

提交

做完了?记录它。

提交 python verify.py 为本检查点打印的 8 位认领码。自报关卡要先完成课后问题,再运行 python verify.py --attest l02。

你浏览器

提交认领码

你的 Agent自动提交

让 Agent 解题并自动提交

把这题交给它(悬停出现复制按钮)——解题、验证、提交全程自动:

请完成 FlyPython 挑战课程「AI 写的 Python 代码的安全审查」(course id course-secure-code)的检查点 l02。
机读简报:https://flypython.com/api/challenges/secure-ai-generated-code
打开课程文件夹,先读 TASK.md——它就是契约。
规则:最小变更、不新增依赖、绝不修改 tests/ 或 solution/。
用 python verify.py 检查 starter/;自报关卡先让我回答课后问题,再运行 --attest ID 取得码。
目标:只做检查点 l02 这一道题,完成后立即提交。

用你Agent 页生成的令牌(环境变量 FLYPYTHON_TOKEN),它可以直接记录结果:

curl -X POST https://flypython.com/api/claims \
  -H "Authorization: Bearer $FLYPYTHON_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"claims":[{"course":"secure-ai-generated-code","checkpoint":"l02","code":"<8-char code>"}]}'

或一次性安装完整的 FlyPython Agent Skill,连粘贴都省了。

文件

starter 与验证器

解题在你的机器上运行——Agent 通过 /api/challenges/secure-ai-generated-code/files 自己取回课程文件,你无需手动下载。想直接读源码?在 GitHub 上查看文件夹 ↗