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可以自动运行语法检查、静态分析和只读查询,但数据库写入必须分级管理。

最重要的三道防线是:

  1. 业务数据库账号没有删表权限。
  2. 测试只能连接明确授权的隔离数据库。
  3. 测试启动前强制校验环境、主机和数据库名称。

这次事故最沉重的教训是:

不要要求AI“永远不要犯错”,而要让系统在AI犯错时也无法破坏真实数据。

posted @ 2026-09-01 10:01  pine007  阅读(20)  评论(0)    收藏  举报