OmarEltak/claude-legacy-php-safety
GitHub: OmarEltak/claude-legacy-php-safety
源自真实生产事故的 Claude Code 技能集,提供数据丢失事件响应流程和旧版 PHP 代码库危险扫描,帮助 AI 辅助开发时避免重蹈数据丢失覆辙。
Stars: 0 | Forks: 0
# Claude Code Skills — 旧版 PHP 安全
两个源自真实生产环境数据丢失事故的 Claude Code 技能。
一名缴费学生从旧版 PHP 大学注册系统中消失了,因为一个安全防护函数的主体在几年前被注释掉了,而且没有人注意到。当它最终引发事故时,恢复工作花了三个小时而不是三十分钟——因为我(Claude)把它当作一个搜索问题,而不是事故响应。
这些技能的存在是为了让未来的会话不再重蹈覆辙。
## 本仓库包含什么
### 数据丢失事件响应
通用技能(不特定于某种语言)。在出现“disappeared”、“missing”、“deleted”、“vanished”、“lost data”、“can't find”、“not showing up”等词汇时触发。
强制执行严格的事故响应顺序:
1. **冻结 (Freeze)** — 创建新的备份,在了解影响范围之前禁止任何写入
2. **评估爆炸半径 (Size blast radius)** — 与备份进行集合差分运算,在子表中进行孤儿记录扫描
3. **查找机制 (Find the mechanism)** — grep 搜索 DELETE,检查防护代码,在弄清楚它是*如何*发生之前不要进行恢复
4. **规划恢复 (Plan recovery)** — 仅对缺失的行进行精准的 INSERT 插入,绝不在现有活跃数据上还原整个数据库
5. **堵住漏洞 (Plug the hole)** — 修复防护逻辑,添加审计触发器,否则该 bug 明天又会执行删除操作
防止最常见的故障模式:将事故视为搜索问题,在确定范围之前运行写入查询,并且从不修补根本原因。
### legacy PHP 危险扫描
用于旧版 PHP 代码库(PHP 5.x 时代、MyISAM、手工身份验证、`` 短标签)的会话启动扫描器。检测:
- **损坏的防护函数** — 函数体被注释掉,return 使用了未初始化的变量(在 PHP 中 `NULL == 0` 会返回 `true`)
- **WHERE 条件中包含 `$_GET`/`$_POST`/`$_REQUEST` 的 DELETE** — 没有 CSRF,没有验证
- **硬编码的管理员用户名检查** — `if ($user == 'administrator')` 风格,只能有一个管理员
- **Register globals 时代的模式** — `extract($GLOBALS)`、`extract($_REQUEST)`、`import_request_variables`
- **在用户/订单/支付表上缺失软删除 / 审计字段**
- **依赖 `` 短标签** — 当 `short_open_tag=off` 时会导致源码泄露
按严重程度报告发现的结果。不会自动修复——由人工决定优先级。某些发现结果可能是需要业务上下文的预期内遗留行为。
随附了一个位于 `skills/legacy-php-hazard-scan/scripts/scan.sh` 的 bash 实现。
## 安装
### 选项 A — 符号链接到您的 Claude Code 技能目录(推荐)
```
git clone https://github.com/OmarEltak/claude-legacy-php-safety.git
cd claude-legacy-php-safety
./install.sh # symlinks skills/* into ~/.claude/skills/
```
当您 `git pull` 更新时,符号链接会保持同步。
### 选项 B — 手动复制
```
cp -r skills/data-loss-incident-response ~/.claude/skills/
cp -r skills/legacy-php-hazard-scan ~/.claude/skills/
```
### 选项 C — 项目级安装
```
cp -r skills/* /path/to/your/project/.claude/skills/
```
只有该项目的 Claude Code 会话能看到这些技能。
## 验证安装
```
ls ~/.claude/skills/
```
您应该会看到 `data-loss-incident-response/` 和 `legacy-php-hazard-scan/`。在您的下一次 Claude Code 会话中,技能名称将出现在可用技能列表中。
## 直接运行扫描器
危险扫描也可以独立运行(无需 Claude),这对于 CI 或预检非常有用:
```
./skills/legacy-php-hazard-scan/scripts/scan.sh /path/to/your/php-repo
```
按严重程度对发现的结果进行分组输出。退出代码始终为 0——该工具只负责报告,不进行拦截。
## 这些技能背后的故事
在 2026-07-01,一次会话以“在我们编辑后有一名学生消失了”开始。原本应该只需 30 分钟的恢复工作却花了三个小时。故障模式按顺序如下:
1. **第一个小时** 浪费在试图通过共享主机上的 phpMyAdmin 将完整的 50 MB 数据库备份导入到备用 schema 中。phpMyAdmin 卡死了。`max_allowed_packet` 限制导致重试失败。
2. **第二个小时** 浪费在提取越来越小的备份子集上——3 MB 的整表,然后是 5 KB 的过滤子集,最后是针对那一名学生的 1.3 KB 的单条 INSERT。
3. **第三个小时** 才是在进行真正的恢复:向 `mm_uni_student` 中插入了一行,接着发现 `mm_uni_student_semester_subject` 中也丢失了 41 条选课记录,随后又发现 `mm_uni_student_follow` 中丢失了 2 条管理员备注。
快到结束时才找到根本原因:位于 `admin/schoolastic/accredit_student/globals.inc` 中的 `gs_can_i_delete_student()` 看起来是这样的:
```
function gs_can_i_delete_student($id) {
global $dbo;
//$pointer = $dbo->db_query("SELECT COUNT(*) AS c FROM ...");
//$rst = $dbo->db_fetch_array($pointer);
//$c = $rst['c'];
return ($c == 0)?true:false;
}
```
`$c` 从未被赋值。在 PHP 中 `NULL == 0` 的计算结果为 `true`。因此,这个“安全”防护对每一个学生 ID 都返回了 `true`——包括那些拥有数百次考试记录、活跃选课以及已付费科目记录的学生。
有人在几年前(可能是在调试时)注释掉了检查代码,并且从未恢复。从那以后,每次点击删除按钮都在悄无声息地引发危险。当前数据库中还有 31 名其他缴费学生显示出与这种确切故障模式相匹配的孤儿记录模式,但他们都在最后一次备份之前被删除了——已无法恢复。
如果在进行会话时运行了危险扫描,`gs_can_i_delete_student` 的问题几周前就会被暴露出来。如果当时启用了 data-loss-incident-response 技能,“一名学生”的报告就会触发“冻结-评估-查找-恢复”的顺序,整个恢复工作将在 30 分钟内完成。
缺失这些技能的代价:在压力状态下多耗费了约 2.5 小时的工程时间,客户登录暂时失效,31 名历史孤儿学生记录无法恢复,并且在被显式修补之前,底层的 bug 依然处于活跃状态。
## 许可证
MIT — 请参阅 LICENSE。
## 贡献
欢迎提交 Issue 和 PR。特别是:
- 您在野外遇到的其他旧版 PHP 危险模式
- 将危险扫描器移植到其他旧版技术栈(classic ASP、ColdFusion、Perl CGI)
- 基于您自己的实战故事,对事故响应阶段顺序的改进建议
标签:AI辅助编程, Claude Code, 代码安全审计, 应用安全, 遗留系统(PHP)