# Verify Quality Security

> 安全加固——假设每个外部输入是敌意的。当涉及用户输入、认证、数据存储、上线前审查，或提到"XSS""SQL注入""CSRF""OWASP"

- Skill: `zeroz-lab/verify-quality-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zeroz-lab/verify-quality-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zeroz-lab/verify-quality-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: zeroz-lab (https://skillmd.com/u/zeroz-lab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zeroz-lab/verify-quality-security

---


# Security — 安全加固


## 入口/出口
- **入口**: 涉及用户输入、认证授权、敏感数据、或 `/review` 和 `/ship` 前
- **出口**: 加固代码 + 安全审查通过
- **指向**: 安全问题修复后重新进入 `/review`
- **前置加载**: CANON.md
- **输出路径**: 安全审查结论 → verify-workflow-review（合并入审查报告）或 build-workflow-execute（安全问题需退回修复时）

## 何时不使用
- 变更完全不接触输入、权限、敏感数据、网络边界或部署配置
- 只是解释安全概念，不审查或修改具体实现
- 当前是纯视觉/文案产物，且没有外部数据或权限边界

## Iron Law

<HARD-GATE>
```
每个外部输入视为敌意。每个密钥视为神圣。每个鉴权检查视为强制。
安全不是"一个阶段"——安全是每行代码的约束。
安全是无条件说"不"的唯一领域。不通过安全审查 = 不能上线。
```
</HARD-GATE>

## 三级边界系统

| 级别 | 规则 | 示例 |
|------|------|------|
| **Always Do** | 提交前验证输入、参数化查询、哈希密码、HTTPS only cookies | 输入 sanitization、bcrypt、helmet |
| **Ask First** | 泄露用户数据、改权限模型、引入认证依赖、加密算法选择 | 新 OAuth provider、加密库选型 |
| **Never Do** | 提交密钥、信任客户端验证、`eval()` 用户输入、innerHTML 用户数据、暴露堆栈给用户 | `.env` 在仓库、`dangerouslySetInnerHTML` |

## OWASP Top 10 防御

### 1. 注入 (SQL/NoSQL/Command)

```typescript
// Bad: SQL 拼接
const task = await db.query(`SELECT * FROM tasks WHERE id = '${req.params.id}'`);

// Good: 参数化查询
const task = await db.tasks.findById(req.params.id);

// 命令行参数必须通过 exec/spawn 的参数数组，不拼接字符串
```

### 2. 跨站脚本 (XSS)

```typescript
// Bad: 直接插入用户内容
<div>{user.bio}</div>  // 如果 bio 包含 <script>...

// Good: 框架自动转义 + DOMPurify for rich text
<div>{user.bio}</div>  // React 默认转义
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(richBio) }} />
```

### 3. 敏感数据暴露

```typescript
// Bad: 错误暴露内部信息
catch (err) {
  res.status(500).json({ error: err.message, stack: err.stack });
}

// Good: 用户可见错误不含内部信息
catch (err) {
  logger.error('Task creation failed', { taskId, error: err.message });
  res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: '处理请求时发生错误' } });
}
```

### 4. 访问控制缺失

```typescript
// Bad: 任何登录用户都能删任何任务
app.delete('/tasks/:id', auth, async (req, res) => {
  await db.tasks.delete(req.params.id);
});

// Good: 验证所有权
app.delete('/tasks/:id', auth, async (req, res) => {
  const task = await db.tasks.findById(req.params.id);
  if (!task) return res.status(404).end();
  if (task.ownerId !== req.user.id) return res.status(403).end();
  await db.tasks.delete(req.params.id);
});
```

### 5. 安全配置错误

```typescript
// 必须: helmet、CORS 白名单、HTTPS 重定向、安全 cookies
app.use(helmet());
app.use(cors({ origin: ALLOWED_ORIGINS }));
app.use(session({ cookie: { secure: true, httpOnly: true, sameSite: 'strict' } }));
```

### 6. 依赖漏洞

```bash
npm audit          # 检查已知 CVE
npm audit fix      # 自动修复兼容补丁
```

**分类:**
- Critical/High → 必须修复（修复或替换依赖）
- Moderate → 评估实际可利用性（仅在特定条件下可能利用）
- Low → 排期但不阻塞上线

## 输入验证

```typescript
import { z } from 'zod';

const CreateTaskSchema = z.object({
  title: z.string().min(1).max(200),
  description: z.string().max(5000).optional(),
  priority: z.enum(['low', 'medium', 'high']),
  assigneeId: z.string().uuid().optional(),
});
```

**规则:** 所有外部输入在边界（API handler）验证。验证完之后内部信任。

## 密钥管理

```
项目结构:
├── .env.example          # 模板（可提交）
├── .env                  # 真实密钥（在 .gitignore，绝不提交）
└── src/config.ts         # 从环境变量读取

.gitignore 必须包含:
.env
.env.local
*.pem
*.key
credentials.json
```

**检查是否误提交:**
```bash
git log --all --full-history -- '*.env' '*.pem' 'credentials.*'
```

## 好坏示例

### Good — OWASP 清单逐项验证
A01 注入：SQL 全部参数化 ✓ → A02 XSS：框架自动转义 + DOMPurify ✓ → A04 访问控制：每个写端点验证所有权 ✓ → A06 依赖：npm audit 无 high/critical ✓ → 输入验证：所有外部输入 Zod 校验 ✓。每项有通过/未通过判定。

### Bad — "我们没有用户输入" 不做检查
"这只是内部工具，用户输入在前端验证过了" — 后端没有输入验证，一行 `curl` 即可绕过前端。SQL 拼接、未参数化查询、无权限检查。内网被攻破后横向移动无阻碍。

## 输出模板

安全审查结论（合并入 `04-review.md` 的 Security 轴区块）：

```markdown
## Security 审查

### OWASP Top 10 检查
| 类目 | 状态 | 发现 |
|------|------|------|
| A01 注入 | PASS / FAIL | 具体发现描述 |
| A02 XSS | PASS / FAIL | ... |
| A03 敏感数据暴露 | PASS / FAIL | ... |
| A04 访问控制缺失 | PASS / FAIL | ... |
| A05 安全配置错误 | PASS / FAIL | ... |
| A06 依赖漏洞 | PASS / FAIL | npm audit 结果摘要 |

### 输入验证覆盖
- 已验证的输入端: <列出>
- 未验证的输入端: <列出> (如有则为 Blocking)

### 密钥管理
- 密钥存储方式: 环境变量 / 配置文件 / 硬编码 (如有硬编码则为 Blocking)
- 误提交检查: git log 结果 (如有误提交则为 Blocking)

### 结论
- 审查结果: PASS / FAIL
- Blocking 问题: <列出> (如有)
- 建议: <列出>
```

## 验证证据

输出或记录必须包含：
- **输入/来源**: 读取的 spec、plan、代码、反馈或发布上下文。
- **执行动作**: 实际完成的检查、生成、修复、导出或发布步骤。
- **验证结果**: 命令、审查结论、产物路径、截图或人工确认。
- **阻塞/回退**: 未通过项、回退路径或需要 human partner 决策的问题。

## 常见说辞

| 说辞 | 现实 | 后果 |
|------|------|------|
| "只在内部用，不对外开放" | 内网不是信任边界。Dependency confusion、内网横向移动——内部应用仍需要安全加固。 | 内网被攻破后横向移动无阻碍，一台内网机器沦陷 = 整个内网沦陷。Target 数据泄露就是从空调供应商内网渗透的。 |
| "npm audit 报的都是低风险" | 低风险可能组合成高风险。至少读一遍 audit 报告。 | 低风险 CVE 组合利用的攻击链已经发生多次（Log4j 就是"低风险"日志库），忽略 audit = 赌博。 |
| "用户输入在前端验证过了" | 前端验证是 UX 优化，不是安全。攻击者发 curl 直接绕过。 | 一行 `curl -X POST` 即可绕过所有前端验证。未后端验证的输入 = SQL 注入 / XSS 的直接入口。 |
| "这是个简单的内部工具" | 简单工具暴露一个未验证输入就被用作内网跳板。 | 内部工具被钓鱼或供应链攻击攻破后成为内网跳板，损失范围从 1 个工具扩大到整个基础设施。 |
| "以后再加强安全" | 安全债和技术债不同——安全债可能导致数据泄露。不能之后补。 | 数据泄露赔偿 + 声誉损失 + 用户流失。安全事件后补救的成本是预防成本的 10-50x（IBM 安全报告）。 |

**违反字面规则就是违反精神。** 没有灰色地带。

## 验证失败处理

| 失败场景 | 处理方式 |
|---------|---------|
| 发现 SQL/命令注入（OWASP A01） | Blocking — 立即退回 build 修复，修复后重新提交安全审查，不能上线 |
| 发现 XSS / innerHTML 用户数据（OWASP A02） | Blocking — 立即退回 build 修复，必须使用参数化或 DOMPurify |
| 密钥或令牌硬编码/误提交 | Blocking — 立即移除密钥，轮换泄露密钥值，加入 .gitignore，重新提交 |
| 访问控制缺失（OWASP A04） | Blocking — 添加所有权验证或权限检查，修复后重新审查 |
| npm audit 有 Critical/High CVE | Blocking — 修复或替换依赖后才可上线，Low 可排期但不忽略 |
| 未验证输入端存在 | Blocking — 所有外部输入必须在边界验证，遗漏 = 潜在注入入口 |

## 红旗 — STOP

<HARD-GATE>
以下任何一个出现，立即停止并修复：

- 用户输入到了 SQL/Shell/HTML 中而未参数化/转义
- 密钥或令牌出现在代码或日志中
- `Authorization: Bearer <token>` 在没有 HTTPS 的连接上
- 用 `<` 或 `>` 字符串比较做权限检查（可绕过）
- 没有速率限制的认证端点（暴力破解）
- `innerHTML`、`dangerouslySetInnerHTML`、`eval()` 含用户数据
- API 返回的 stack trace 包含文件路径或内部库名
</HARD-GATE>

## 验证清单

- [ ] 所有用户输入在边界验证（Zod / Yup / Joi）
- [ ] SQL 查询使用参数化（无字符串拼接）
- [ ] HTML 中的用户内容被转义
- [ ] 错误响应不含 stack trace 或内部路径
- [ ] 每个写端点验证了资源所有权
- [ ] 密钥不在代码仓库中
- [ ] `npm audit`（或同等）无 critical/high
- [ ] 安全头设置（helmet、CORS 白名单、secure cookies）

