AI Agent自动执行测试引发的数据表误删事故复盘与防护实践
AI Agent自动执行测试引发的数据表误删事故复盘与防护实践
一、事故背景
在开发1688采购单返修功能时,为验证返修单继承原1688订单信息的逻辑,新增了一个PHPUnit单元测试。
测试需要构造采购单、采购明细、不良品、1688订单和返修关系,因此在测试初始化代码中使用了:
Schema::dropIfExists('purchase_order');
Schema::dropIfExists('purchase_order_details');
Schema::dropIfExists('purchase_platform_order');
Schema::dropIfExists('purchase_bad_goods');
Schema::dropIfExists('purchase_order_repair_relation');
代码原本期望操作内存SQLite测试库,但测试运行前没有完成数据库隔离。Laravel读取了项目 .env,最终连接到真实业务数据库。
结果是5张业务表被删除,随后又被测试代码重建成精简结构,并写入少量测试数据。
二、事故影响
受影响的表包括:
- 采购订单表
- 采购订单明细表
- 1688平台订单表
- 采购不良品表
- 采购返修关系表
事故发生后检查发现:
- 原表结构已经被测试结构覆盖。
- 原表数据已经丢失。
- MySQL未开启binlog。
- 服务器未发现可用的自动SQL备份。
- 无法通过普通SQL或binlog回放恢复数据。
这不是普通的测试失败,而是一次真实的数据破坏事故。
三、根本原因
直接原因很简单:
包含删表操作的单元测试连接到了业务数据库。
但真正的问题不只是某一行代码,而是多层安全防线同时缺失。
1. 测试没有强制隔离
项目没有确保PHPUnit只能连接内存SQLite或专用测试数据库。
2. 测试启动前没有数据库校验
运行测试时,没有检查:
- 当前环境是否为 testing
- 数据库连接类型
- 数据库主机和端口
- 数据库名称是否在测试白名单中
3. 数据库账号权限过大
应用使用的数据库账号拥有 DROP、CREATE 等DDL权限。即使代码发生错误,数据库也没有阻止删表。
4. 自动执行缺少风险分级
AI Agent将“运行单元测试”视为常规验证,没有先扫描测试中是否包含删表、清表或重建数据库的逻辑。
5. 缺少恢复能力
数据库没有binlog和有效备份,使一次本可恢复的事故变成了不可逆的数据损失。
四、为什么提示词规则不够
事故后可以在AI Agent全局规则中加入:
- 禁止私自删除表或清空表。
- 禁止使用 Schema::dropIfExists()。
- 破坏性操作必须单独确认。
- 测试必须连接隔离数据库。
这些规则有价值,但不能作为主要防线。
AI可能误解上下文,脚本可能间接调用危险代码,测试框架也可能通过Trait执行清库操作。因此,数据库安全必须由技术机制保证,而不是依赖AI“记得小心”。
五、推荐的防护方案
1. PHPUnit强制使用测试库
<php>
<env name="APP_ENV" value="testing" force="true"/>
<env name="DB_CONNECTION" value="sqlite" force="true"/>
<env name="DB_DATABASE" value=":memory:" force="true"/>
</php>
确实需要MySQL时,使用独立数据库,例如:
erp_unit_testingwms_unit_testingproject_a_testing
不能只排除某一个固定业务库名。规则必须适用于所有项目和数据库。
2. 增加测试启动熔断
$environment = app()->environment();
$database = DB::connection()->getDatabaseName();
$allowed = $database === ':memory:'
|| str_ends_with($database, '_testing')
|| str_ends_with($database, '_test');
if ($environment !== 'testing' || !$allowed) {
throw new RuntimeException(
"测试数据库未隔离,禁止运行:env={$environment}, database={$database}"
);
}
更稳妥的方式是使用项目级测试数据库白名单,而不是只依赖名称后缀。
3. 撤销应用账号DDL权限
普通应用账号只保留业务所需权限:
SELECT, INSERT, UPDATE, DELETE
撤销以下权限:
DROP, CREATE, ALTER, INDEX
数据库结构调整使用单独的部署账号,并由人工明确授权。这样即使AI Agent执行了危险测试,MySQL也会拒绝删表。
4. 执行测试前扫描危险代码
重点拦截:
Schema::drop
Schema::dropIfExists
dropAllTables
migrate:fresh
db:wipe
TRUNCATE
DROP TABLE
RefreshDatabase
DatabaseMigrations
发现这些内容时,不允许自动运行,必须人工检查数据库连接和影响范围。
5. 禁止直接运行裸测试命令
不要让自动化工具直接执行:
php vendor/phpunit/phpunit/phpunit
应统一通过安全包装脚本执行。包装脚本先验证环境、数据库连接和白名单,确认隔离后再启动PHPUnit。
6. 建立恢复能力
至少配置:
- MySQL binlog
- 每日全量备份
- 定时增量备份
- 异机或对象存储保存
- 虚拟机或磁盘快照
- 定期恢复演练
没有验证过能否恢复的备份,不能算真正的备份。
六、最终原则
AI Agent可以自动运行语法检查、静态分析和只读查询,但数据库写入必须分级管理。
最重要的三道防线是:
- 业务数据库账号没有删表权限。
- 测试只能连接明确授权的隔离数据库。
- 测试启动前强制校验环境、主机和数据库名称。
这次事故最沉重的教训是:
不要要求AI“永远不要犯错”,而要让系统在AI犯错时也无法破坏真实数据。

浙公网安备 33010602011771号