Files
vibe_coding/.cursor/rules/references/026-secure-coding-deep.md
2026-03-05 21:27:11 +08:00

242 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 026-secure-coding.mdc (Deep Reference)
> 该文件为原始详细规范归档,供 Tier 3 按需读取。
---
# 🔐 Secure Coding Standards (Project CodeGuard)
> 安全不是事后审查,而是编码时的默认选择。
## ❌ 禁用加密算法(绝对禁止)
以下算法已被证实不安全,**禁止在任何新代码中使用**
| 类型 | 禁用 | 替代 |
|------|------|------|
| 哈希 | MD2、MD4、**MD5**、SHA-0、**SHA-1** | SHA-256、SHA-3 |
| 对称加密 | RC2、RC4、Blowfish、DES、3DES、**AES-ECB** | **AES-GCM**、ChaCha20-Poly1305 |
| 密钥交换 | 静态 RSA、匿名 DH | ECDHE (X25519) |
```php
// ❌ 禁止
$hash = md5($password);
$hash = sha1($data);
openssl_encrypt($data, 'AES-128-ECB', $key);
// ✅ 正确
$hash = password_hash($password, PASSWORD_ARGON2ID);
$hash = hash('sha256', $data);
openssl_encrypt($data, 'AES-256-GCM', $key, 0, $iv, $tag);
```
## 💉 注入防护
### SQL 注入
- **100% 使用参数化查询**,禁止字符串拼接 SQL
- 使用 Hyperf ORM 或 PDO 绑定参数
```php
// ❌ 禁止
$result = Db::select("SELECT * FROM users WHERE name = '{$name}'");
// ✅ 正确
$result = Db::select('SELECT * FROM users WHERE name = ?', [$name]);
// 或
User::where('name', $name)->first();
```
### OS 命令注入
- 优先使用内置函数替代 shell 调用
- 禁止将用户输入直接传入 `exec()``system()``shell_exec()`
```php
// ❌ 禁止
exec("convert {$userFile} output.png");
// ✅ 正确(使用内置库)
$image = new Imagick($sanitizedPath);
```
### TypeScript Prototype Pollution
- 使用 `Map` / `Set` 替代对象字面量存储用户数据
- 合并对象时拦截 `__proto__``constructor``prototype`
```typescript
// ❌ 禁止
function merge(target, source) {
Object.assign(target, source) // 可能导致 prototype pollution
}
// ✅ 正确
const safe = Object.create(null)
const blocked = ['__proto__', 'constructor', 'prototype']
Object.keys(source).filter(k => !blocked.includes(k)).forEach(k => { safe[k] = source[k] })
```
## 🔑 密码存储
```php
// ❌ 禁止bcrypt 有 72 字节截断限制)
$hash = password_hash($password, PASSWORD_BCRYPT);
// ✅ 推荐Argon2id更安全无长度限制
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64 MiB
'time_cost' => 2,
'threads' => 1,
]);
// 验证(常量时间比较,防时序攻击)
$valid = password_verify($input, $hash);
```
## 🔒 访问控制IDOR / Mass Assignment 防护)
### IDOR 防护
- 永远不要只凭用户传入的 ID 查询资源,必须附加所有权校验
```php
// ❌ 禁止 — 用户可访问任意 ID 的数据
$order = Order::find($request->input('id'));
// ✅ 正确 — 限定当前用户范围
$order = $this->user->orders()->findOrFail($request->input('id'));
```
### Mass Assignment 防护
- Hyperf FormRequest 必须声明 `rules()` 白名单
- Model 必须使用 `$fillable` 而非 `$guarded = []`
```php
// ❌ 禁止
$user->fill($request->all());
// ✅ 正确(只允许明确字段)
$user->fill($request->only(['name', 'email', 'avatar']));
```
## 🍪 Session 和 Cookie 安全
```php
// 登录成功后必须轮转 Session ID防 Session Fixation
session_regenerate_id(true);
// Cookie 必须设置安全属性
setcookie('session', $id, [
'secure' => true, // 仅 HTTPS
'httponly' => true, // 禁止 JS 访问
'samesite' => 'Strict', // 防 CSRF
'path' => '/',
]);
```
- Session 超时:高风险操作 5 分钟,常规 30 分钟,绝对上限 8 小时
- **禁止在 `localStorage` / `sessionStorage` 存储 Session Token**XSS 可窃取)
## 🌐 客户端安全Vue 3 / TypeScript
### XSS 防护
```typescript
// ❌ 禁止 — 直接使用 v-html 且无净化
// <div v-html="userContent"></div>
// ✅ 正确 — 使用 DOMPurify 净化
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['b', 'i', 'p', 'a', 'ul', 'li'],
ALLOWED_ATTR: ['href'],
})
// <div v-html="clean"></div>
// ❌ 禁止 — 动态代码执行
eval(userCode)
new Function(userCode)()
setTimeout(userString, 100)
// ❌ 禁止 — 直接 DOM 写入
element.innerHTML = userInput
document.write(userInput)
```
### 外链安全
```html
<!-- ✅ 所有 target="_blank" 必须加防护 -->
<a href="..." target="_blank" rel="noopener noreferrer">外链</a>
```
### CSRF 防护
- 所有状态变更请求POST/PUT/DELETE/PATCH必须携带 CSRF Token
- 禁止用 GET 请求执行状态变更操作
## 📁 文件上传安全
```php
// ✅ 文件上传安全检查清单
// 1. 白名单扩展名(不是黑名单)
$allowed = ['jpg', 'png', 'gif', 'pdf'];
$ext = strtolower(pathinfo($file->getClientFilename(), PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) throw new ValidationException('不允许的文件类型');
// 2. 验证 magic bytes不信任 Content-Type
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($tmpPath);
// 3. 生成随机文件名(不使用用户提供的文件名)
$safeName = Str::uuid() . '.' . $ext;
// 4. 存储在 webroot 外
$storagePath = BASE_PATH . '/storage/uploads/' . $safeName;
// 5. 设置文件大小限制
if ($file->getSize() > 10 * 1024 * 1024) throw new ValidationException('文件过大');
```
## 📊 安全日志规范
```php
// ❌ 禁止记录敏感信息
Log::info('用户登录', ['password' => $password, 'token' => $token]);
// ✅ 正确 — 只记录安全信息,用 hash 标识 session
Log::info('用户登录', [
'user_id' => $userId,
'ip' => $request->getServerParams()['REMOTE_ADDR'],
'user_agent' => substr($request->getHeaderLine('user-agent'), 0, 200),
'session_id' => substr(hash('sha256', $sessionId), 0, 16), // 前16位不泄露原值
'timestamp' => date('c'),
]);
```
## ⚙️ API 安全
- **禁止在 URL 参数中传递敏感数据**(会出现在日志里)
- GraphQL生产环境禁用 introspection
- SSRF所有外部 HTTP 请求必须验证目标域名白名单
```php
// SSRF 防护示例
$allowedDomains = ['api.trusted.com', 'cdn.trusted.com'];
$parsedUrl = parse_url($userProvidedUrl);
if (!in_array($parsedUrl['host'], $allowedDomains)) {
throw new SecurityException('不允许的目标地址');
}
// 还需阻断私有 IP 范围10.x, 172.16.x, 192.168.x, 127.x
```
## ✅ 编码时自查清单
每次提交前确认:
- [ ] 无 MD5/SHA1/DES/AES-ECB 使用
- [ ] SQL 查询 100% 参数化
- [ ] 资源查询已限定所有权范围(无 IDOR 风险)
- [ ]`$request->all()` 直接绑定模型
- [ ] Cookie 包含 Secure + HttpOnly + SameSite 属性
- [ ] `v-html` 使用了 DOMPurify 净化
- [ ] 文件上传使用 UUID 文件名 + magic bytes 验证
- [ ] 日志未包含明文密码/Token/Session ID
> 📚 深度参考:`.cursor/skills/security-audit/references/codeguard/`