fix: harden unicode safety checks

This commit is contained in:
Affaan Mustafa
2026-03-29 08:59:06 -04:00
parent dd675d4258
commit 866d9ebb53
239 changed files with 3780 additions and 3962 deletions

View File

@@ -11,7 +11,7 @@
<div align="center">
**🌐 Language / 语言 / 語言**
**Language / 语言 / 語言**
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](README.md) | [日本語](../../docs/ja-JP/README.md) | [한국어](../ko-KR/README.md)
@@ -59,7 +59,7 @@
---
## 🚀 快速開始
## 快速開始
在 2 分鐘內快速上手:
@@ -75,7 +75,7 @@
### 第二步:安裝規則(必需)
> ⚠️ **重要提示:** Claude Code 外掛程式無法自動分發 `rules`,需要手動安裝:
> WARNING: **重要提示:** Claude Code 外掛程式無法自動分發 `rules`,需要手動安裝:
```bash
# 首先複製儲存庫
@@ -98,11 +98,11 @@ cp -r everything-claude-code/rules/* ~/.claude/rules/
/plugin list everything-claude-code@everything-claude-code
```
**完成!** 您現在使用 15+ 代理程式、30+ 技能和 20+ 指令。
**完成!** 您現在使用 15+ 代理程式、30+ 技能和 20+ 指令。
---
## 🌐 跨平台支援
## 跨平台支援
此外掛程式現已完整支援 **Windows、macOS 和 Linux**。所有鉤子和腳本已使用 Node.js 重寫以獲得最佳相容性。
@@ -137,7 +137,7 @@ node scripts/setup-package-manager.js --detect
---
## 📦 內容概覽
## 內容概覽
本儲存庫是一個 **Claude Code 外掛程式** - 可直接安裝或手動複製元件。
@@ -237,7 +237,7 @@ everything-claude-code/
---
## 🛠️ 生態系統工具
## 生態系統工具
### ecc.tools - 技能建立器
@@ -259,7 +259,7 @@ everything-claude-code/
---
## 📥 安裝
## 安裝
### 選項 1以外掛程式安裝建議
@@ -295,7 +295,7 @@ everything-claude-code/
---
### 🔧 選項 2手動安裝
### 選項 2手動安裝
如果您偏好手動控制安裝內容:
@@ -328,7 +328,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
---
## 🎯 核心概念
## 核心概念
### 代理程式Agents
@@ -386,7 +386,7 @@ You are a senior code reviewer...
---
## 🧪 執行測試
## 執行測試
外掛程式包含完整的測試套件:
@@ -402,7 +402,7 @@ node tests/hooks/hooks.test.js
---
## 🤝 貢獻
## 貢獻
**歡迎並鼓勵貢獻。**
@@ -424,7 +424,7 @@ node tests/hooks/hooks.test.js
---
## 📖 背景
## 背景
我從實驗性推出就開始使用 Claude Code。2025 年 9 月與 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 打造 [zenith.chat](https://zenith.chat),贏得了 Anthropic x Forum Ventures 黑客松。
@@ -432,7 +432,7 @@ node tests/hooks/hooks.test.js
---
## ⚠️ 重要注意事項
## WARNING: 重要注意事項
### 上下文視窗管理
@@ -455,13 +455,13 @@ node tests/hooks/hooks.test.js
---
## 🌟 Star 歷史
## Star 歷史
[![Star History Chart](https://api.star-history.com/svg?repos=affaan-m/everything-claude-code&type=Date)](https://star-history.com/#affaan-m/everything-claude-code&Date)
---
## 🔗 連結
## 連結
- **簡明指南(從這裡開始):** [Everything Claude Code 簡明指南](https://x.com/affaanmustafa/status/2012378465664745795)
- **完整指南(進階):** [Everything Claude Code 完整指南](https://x.com/affaanmustafa/status/2014040193557471352)
@@ -471,7 +471,7 @@ node tests/hooks/hooks.test.js
---
## 📄 授權
## 授權
MIT - 自由使用、依需求修改、如可能請回饋貢獻。

View File

@@ -101,12 +101,12 @@ c) 依影響排序優先順序
**模式 1型別推論失敗**
```typescript
// 錯誤Parameter 'x' implicitly has an 'any' type
// FAIL: 錯誤Parameter 'x' implicitly has an 'any' type
function add(x, y) {
return x + y
}
// 修復:新增型別註解
// PASS: 修復:新增型別註解
function add(x: number, y: number): number {
return x + y
}
@@ -114,25 +114,25 @@ function add(x: number, y: number): number {
**模式 2Null/Undefined 錯誤**
```typescript
// 錯誤Object is possibly 'undefined'
// FAIL: 錯誤Object is possibly 'undefined'
const name = user.name.toUpperCase()
// 修復:可選串聯
// PASS: 修復:可選串聯
const name = user?.name?.toUpperCase()
// Null 檢查
// PASS:Null 檢查
const name = user && user.name ? user.name.toUpperCase() : ''
```
**模式 3缺少屬性**
```typescript
// 錯誤Property 'age' does not exist on type 'User'
// FAIL: 錯誤Property 'age' does not exist on type 'User'
interface User {
name: string
}
const user: User = { name: 'John', age: 30 }
// 修復:新增屬性到介面
// PASS: 修復:新增屬性到介面
interface User {
name: string
age?: number // 如果不是總是存在則為可選
@@ -141,10 +141,10 @@ interface User {
**模式 4Import 錯誤**
```typescript
// 錯誤Cannot find module '@/lib/utils'
// FAIL: 錯誤Cannot find module '@/lib/utils'
import { formatDate } from '@/lib/utils'
// 修復 1檢查 tsconfig paths 是否正確
// PASS: 修復 1檢查 tsconfig paths 是否正確
{
"compilerOptions": {
"paths": {
@@ -153,22 +153,22 @@ import { formatDate } from '@/lib/utils'
}
}
// 修復 2使用相對 import
// PASS: 修復 2使用相對 import
import { formatDate } from '../lib/utils'
// 修復 3安裝缺少的套件
// PASS: 修復 3安裝缺少的套件
npm install @/lib/utils
```
**模式 5型別不符**
```typescript
// 錯誤Type 'string' is not assignable to type 'number'
// FAIL: 錯誤Type 'string' is not assignable to type 'number'
const age: number = "30"
// 修復:解析字串為數字
// PASS: 修復:解析字串為數字
const age: number = parseInt("30", 10)
// 或:變更型別
// PASS: 或:變更型別
const age: string = "30"
```
@@ -177,34 +177,34 @@ const age: string = "30"
**關鍵:做最小可能的變更**
### 應該做:
在缺少處新增型別註解
在需要處新增 null 檢查
修復 imports/exports
新增缺少的相依性
更新型別定義
修復設定檔
PASS: 在缺少處新增型別註解
PASS: 在需要處新增 null 檢查
PASS: 修復 imports/exports
PASS: 新增缺少的相依性
PASS: 更新型別定義
PASS: 修復設定檔
### 不應該做:
重構不相關的程式碼
變更架構
重新命名變數/函式(除非是錯誤原因)
新增功能
變更邏輯流程(除非是修復錯誤)
優化效能
改善程式碼風格
FAIL: 重構不相關的程式碼
FAIL: 變更架構
FAIL: 重新命名變數/函式(除非是錯誤原因)
FAIL: 新增功能
FAIL: 變更邏輯流程(除非是修復錯誤)
FAIL: 優化效能
FAIL: 改善程式碼風格
**最小差異範例:**
```typescript
// 檔案有 200 行,第 45 行有錯誤
// 錯誤:重構整個檔案
// FAIL: 錯誤:重構整個檔案
// - 重新命名變數
// - 抽取函式
// - 變更模式
// 結果50 行變更
// 正確:只修復錯誤
// PASS: 正確:只修復錯誤
// - 在第 45 行新增型別註解
// 結果1 行變更
@@ -212,12 +212,12 @@ function processData(data) { // 第 45 行 - 錯誤:'data' implicitly has 'any
return data.map(item => item.value)
}
// 最小修復:
// PASS: 最小修復:
function processData(data: any[]) { // 只變更這行
return data.map(item => item.value)
}
// 更好的最小修復(如果知道型別):
// PASS: 更好的最小修復(如果知道型別):
function processData(data: Array<{ value: number }>) {
return data.map(item => item.value)
}
@@ -232,7 +232,7 @@ function processData(data: Array<{ value: number }>) {
**建置目標:** Next.js 生產 / TypeScript 檢查 / ESLint
**初始錯誤:** X
**已修復錯誤:** Y
**建置狀態:** 通過 / 失敗
**建置狀態:** PASS: 通過 / FAIL: 失敗
## 已修復的錯誤
@@ -260,11 +260,11 @@ Parameter 'market' implicitly has an 'any' type.
## 驗證步驟
1. TypeScript 檢查通過:`npx tsc --noEmit`
2. Next.js 建置成功:`npm run build`
3. ESLint 檢查通過:`npx eslint .`
4. 沒有引入新錯誤
5. 開發伺服器執行:`npm run dev`
1. PASS: TypeScript 檢查通過:`npx tsc --noEmit`
2. PASS: Next.js 建置成功:`npm run build`
3. PASS: ESLint 檢查通過:`npx eslint .`
4. PASS: 沒有引入新錯誤
5. PASS: 開發伺服器執行:`npm run dev`
```
## 何時使用此 Agent
@@ -287,13 +287,13 @@ Parameter 'market' implicitly has an 'any' type.
## 成功指標
建置錯誤解決後:
- `npx tsc --noEmit` 以代碼 0 結束
- `npm run build` 成功完成
- 沒有引入新錯誤
- 變更行數最小(< 受影響檔案的 5%
- 建置時間沒有顯著增加
- 開發伺服器無錯誤執行
- 測試仍然通過
- PASS: `npx tsc --noEmit` 以代碼 0 結束
- PASS: `npm run build` 成功完成
- PASS: 沒有引入新錯誤
- PASS: 變更行數最小(< 受影響檔案的 5%
- PASS: 建置時間沒有顯著增加
- PASS: 開發伺服器無錯誤執行
- PASS: 測試仍然通過
---

View File

@@ -81,15 +81,15 @@ model: opus
問題API 金鑰暴露在原始碼中
修復:移至環境變數
const apiKey = "sk-abc123"; // 錯誤
const apiKey = "sk-abc123"; // FAIL: 錯誤
const apiKey = process.env.API_KEY; // ✓ 正確
```
## 批准標準
- 批准:無關鍵或高優先問題
- ⚠️ 警告:僅有中優先問題(可謹慎合併)
- 阻擋:發現關鍵或高優先問題
- PASS: 批准:無關鍵或高優先問題
- WARNING: 警告:僅有中優先問題(可謹慎合併)
- FAIL: 阻擋:發現關鍵或高優先問題
## 專案特定指南(範例)

View File

@@ -109,14 +109,14 @@ c) 資料保護
**影響:** 大表上查詢快 100-1000 倍
```sql
-- 錯誤:外鍵沒有索引
-- FAIL: 錯誤:外鍵沒有索引
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
-- 缺少索引!
);
-- 正確:外鍵有索引
-- PASS: 正確:外鍵有索引
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
@@ -134,11 +134,11 @@ CREATE INDEX orders_customer_id_idx ON orders (customer_id);
| **Hash** | 僅等於 | `=`(比 B-tree 略快)|
```sql
-- 錯誤JSONB 包含用 B-tree
-- FAIL: 錯誤JSONB 包含用 B-tree
CREATE INDEX products_attrs_idx ON products (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- 正確JSONB 用 GIN
-- PASS: 正確JSONB 用 GIN
CREATE INDEX products_attrs_idx ON products USING gin (attributes);
```
@@ -147,11 +147,11 @@ CREATE INDEX products_attrs_idx ON products USING gin (attributes);
**影響:** 多欄位查詢快 5-10 倍
```sql
-- 錯誤:分開的索引
-- FAIL: 錯誤:分開的索引
CREATE INDEX orders_status_idx ON orders (status);
CREATE INDEX orders_created_idx ON orders (created_at);
-- 正確:複合索引(等於欄位在前,然後範圍)
-- PASS: 正確:複合索引(等於欄位在前,然後範圍)
CREATE INDEX orders_status_created_idx ON orders (status, created_at);
```
@@ -167,11 +167,11 @@ CREATE INDEX orders_status_created_idx ON orders (status, created_at);
**影響:** 透過避免表查找,查詢快 2-5 倍
```sql
-- 錯誤:必須從表獲取 name
-- FAIL: 錯誤:必須從表獲取 name
CREATE INDEX users_email_idx ON users (email);
SELECT email, name FROM users WHERE email = 'user@example.com';
-- 正確:所有欄位在索引中
-- PASS: 正確:所有欄位在索引中
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
```
@@ -180,10 +180,10 @@ CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
**影響:** 索引小 5-20 倍,寫入和查詢更快
```sql
-- 錯誤:完整索引包含已刪除的列
-- FAIL: 錯誤:完整索引包含已刪除的列
CREATE INDEX users_email_idx ON users (email);
-- 正確:部分索引排除已刪除的列
-- PASS: 正確:部分索引排除已刪除的列
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
```
@@ -196,11 +196,11 @@ CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
**影響:** 關鍵 - 資料庫強制的租戶隔離
```sql
-- 錯誤:僅應用程式篩選
-- FAIL: 錯誤:僅應用程式篩選
SELECT * FROM orders WHERE user_id = $current_user_id;
-- Bug 意味著所有訂單暴露!
-- 正確:資料庫強制的 RLS
-- PASS: 正確:資料庫強制的 RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
@@ -220,11 +220,11 @@ CREATE POLICY orders_user_policy ON orders
**影響:** RLS 查詢快 5-10 倍
```sql
-- 錯誤:每列呼叫一次函式
-- FAIL: 錯誤:每列呼叫一次函式
CREATE POLICY orders_policy ON orders
USING (auth.uid() = user_id); -- 1M 列呼叫 1M 次!
-- 正確:包在 SELECT 中(快取,只呼叫一次)
-- PASS: 正確:包在 SELECT 中(快取,只呼叫一次)
CREATE POLICY orders_policy ON orders
USING ((SELECT auth.uid()) = user_id); -- 快 100 倍
@@ -235,10 +235,10 @@ CREATE INDEX orders_user_id_idx ON orders (user_id);
### 3. 最小權限存取
```sql
-- 錯誤:過度寬鬆
-- FAIL: 錯誤:過度寬鬆
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
-- 正確:最小權限
-- PASS: 正確:最小權限
CREATE ROLE app_readonly NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_readonly;
GRANT SELECT ON public.products, public.categories TO app_readonly;
@@ -260,36 +260,36 @@ REVOKE ALL ON SCHEMA public FROM public;
**影響:** 批量插入快 10-50 倍
```sql
-- 錯誤:個別插入
-- FAIL: 錯誤:個別插入
INSERT INTO events (user_id, action) VALUES (1, 'click');
INSERT INTO events (user_id, action) VALUES (2, 'view');
-- 1000 次往返
-- 正確:批次插入
-- PASS: 正確:批次插入
INSERT INTO events (user_id, action) VALUES
(1, 'click'),
(2, 'view'),
(3, 'click');
-- 1 次往返
-- 最佳:大資料集用 COPY
-- PASS: 最佳:大資料集用 COPY
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);
```
### 2. 消除 N+1 查詢
```sql
-- 錯誤N+1 模式
-- FAIL: 錯誤N+1 模式
SELECT id FROM users WHERE active = true; -- 回傳 100 個 IDs
-- 然後 100 個查詢:
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 2;
-- ... 還有 98 個
-- 正確:用 ANY 的單一查詢
-- PASS: 正確:用 ANY 的單一查詢
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
-- 正確JOIN
-- PASS: 正確JOIN
SELECT u.id, u.name, o.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
@@ -301,11 +301,11 @@ WHERE u.active = true;
**影響:** 無論頁面深度,一致的 O(1) 效能
```sql
-- 錯誤OFFSET 隨深度變慢
-- FAIL: 錯誤OFFSET 隨深度變慢
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
-- 掃描 200,000 列!
-- 正確:游標式(總是快)
-- PASS: 正確:游標式(總是快)
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
-- 使用索引O(1)
```
@@ -313,11 +313,11 @@ SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
### 4. UPSERT 用於插入或更新
```sql
-- 錯誤:競態條件
-- FAIL: 錯誤:競態條件
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
-- 兩個執行緒都找不到,都插入,一個失敗
-- 正確:原子 UPSERT
-- PASS: 正確:原子 UPSERT
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
@@ -329,27 +329,27 @@ RETURNING *;
## 要標記的反模式
### 查詢反模式
### FAIL: 查詢反模式
- 生產程式碼中用 `SELECT *`
- WHERE/JOIN 欄位缺少索引
- 大表上用 OFFSET 分頁
- N+1 查詢模式
- 非參數化查詢SQL 注入風險)
### 結構描述反模式
### FAIL: 結構描述反模式
- IDs 用 `int`(應用 `bigint`
- 無理由用 `varchar(255)`(應用 `text`
- `timestamp` 沒有時區(應用 `timestamptz`
- 隨機 UUIDs 作為主鍵(應用 UUIDv7 或 IDENTITY
- 需要引號的混合大小寫識別符
### 安全性反模式
### FAIL: 安全性反模式
- `GRANT ALL` 給應用程式使用者
- 多租戶表缺少 RLS
- RLS 政策每列呼叫函式(沒有包在 SELECT 中)
- RLS 政策欄位沒有索引
### 連線反模式
### FAIL: 連線反模式
- 沒有連線池
- 沒有閒置逾時
- Transaction 模式連線池使用 Prepared statements

View File

@@ -220,28 +220,28 @@ test('market search with complex query', async ({ page }) => {
**1. 競態條件**
```typescript
// 不穩定:不要假設元素已準備好
// FAIL: 不穩定:不要假設元素已準備好
await page.click('[data-testid="button"]')
// 穩定:等待元素準備好
// PASS: 穩定:等待元素準備好
await page.locator('[data-testid="button"]').click() // 內建自動等待
```
**2. 網路時序**
```typescript
// 不穩定:任意逾時
// FAIL: 不穩定:任意逾時
await page.waitForTimeout(5000)
// 穩定:等待特定條件
// PASS: 穩定:等待特定條件
await page.waitForResponse(resp => resp.url().includes('/api/markets'))
```
**3. 動畫時序**
```typescript
// 不穩定:在動畫期間點擊
// FAIL: 不穩定:在動畫期間點擊
await page.click('[data-testid="menu-item"]')
// 穩定:等待動畫完成
// PASS: 穩定:等待動畫完成
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.click('[data-testid="menu-item"]')
@@ -290,13 +290,13 @@ use: {
## 成功指標
E2E 測試執行後:
- 所有關鍵旅程通過100%
- 總體通過率 > 95%
- 不穩定率 < 5%
- 沒有失敗測試阻擋部署
- 產出物已上傳且可存取
- 測試時間 < 10 分鐘
- HTML 報告已產生
- PASS: 所有關鍵旅程通過100%
- PASS: 總體通過率 > 95%
- PASS: 不穩定率 < 5%
- PASS: 沒有失敗測試阻擋部署
- PASS: 產出物已上傳且可存取
- PASS: 測試時間 < 10 分鐘
- PASS: HTML 報告已產生
---

View File

@@ -146,22 +146,22 @@ e) 驗證測試仍通過
### 1. 未使用的 Imports
```typescript
// 移除未使用的 imports
// FAIL: 移除未使用的 imports
import { useState, useEffect, useMemo } from 'react' // 只有 useState 被使用
// 只保留使用的
// PASS: 只保留使用的
import { useState } from 'react'
```
### 2. 無用程式碼分支
```typescript
// 移除不可達的程式碼
// FAIL: 移除不可達的程式碼
if (false) {
// 這永遠不會執行
doSomething()
}
// 移除未使用的函式
// FAIL: 移除未使用的函式
export function unusedHelper() {
// 程式碼庫中沒有參考
}
@@ -169,18 +169,18 @@ export function unusedHelper() {
### 3. 重複元件
```typescript
// 多個類似元件
// FAIL: 多個類似元件
components/Button.tsx
components/PrimaryButton.tsx
components/NewButton.tsx
// 整合為一個
// PASS: 整合為一個
components/Button.tsx variant prop
```
### 4. 未使用的相依性
```json
// 已安裝但未 import 的套件
// FAIL: 已安裝但未 import 的套件
{
"dependencies": {
"lodash": "^4.17.21", // 沒有在任何地方使用
@@ -261,12 +261,12 @@ components/Button.tsx帶 variant prop
## 成功指標
清理工作階段後:
- 所有測試通過
- 建置成功
- 沒有 console 錯誤
- DELETION_LOG.md 已更新
- Bundle 大小減少
- 生產環境沒有回歸
- PASS: 所有測試通過
- PASS: 建置成功
- PASS: 沒有 console 錯誤
- PASS: DELETION_LOG.md 已更新
- PASS: Bundle 大小減少
- PASS: 生產環境沒有回歸
---

View File

@@ -128,12 +128,12 @@ b) 審查高風險區域
### 1. 寫死密鑰(關鍵)
```javascript
// 關鍵:寫死的密鑰
// FAIL: 關鍵:寫死的密鑰
const apiKey = "sk-proj-xxxxx"
const password = "admin123"
const token = "ghp_xxxxxxxxxxxx"
// 正確:環境變數
// PASS: 正確:環境變數
const apiKey = process.env.OPENAI_API_KEY
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
@@ -143,11 +143,11 @@ if (!apiKey) {
### 2. SQL 注入(關鍵)
```javascript
// 關鍵SQL 注入弱點
// FAIL: 關鍵SQL 注入弱點
const query = `SELECT * FROM users WHERE id = ${userId}`
await db.query(query)
// 正確:參數化查詢
// PASS: 正確:參數化查詢
const { data } = await supabase
.from('users')
.select('*')
@@ -157,11 +157,11 @@ const { data } = await supabase
### 3. 命令注入(關鍵)
```javascript
// 關鍵:命令注入
// FAIL: 關鍵:命令注入
const { exec } = require('child_process')
exec(`ping ${userInput}`, callback)
// 正確:使用函式庫,而非 shell 命令
// PASS: 正確:使用函式庫,而非 shell 命令
const dns = require('dns')
dns.lookup(userInput, callback)
```
@@ -169,10 +169,10 @@ dns.lookup(userInput, callback)
### 4. 跨站腳本 XSS
```javascript
// XSS 弱點
// FAIL:XSS 弱點
element.innerHTML = userInput
// 正確:使用 textContent 或清理
// PASS: 正確:使用 textContent 或清理
element.textContent = userInput
// 或
import DOMPurify from 'dompurify'
@@ -182,10 +182,10 @@ element.innerHTML = DOMPurify.sanitize(userInput)
### 5. 伺服器端請求偽造 SSRF
```javascript
// SSRF 弱點
// FAIL:SSRF 弱點
const response = await fetch(userProvidedUrl)
// 正確:驗證和白名單 URL
// PASS: 正確:驗證和白名單 URL
const allowedDomains = ['api.example.com', 'cdn.example.com']
const url = new URL(userProvidedUrl)
if (!allowedDomains.includes(url.hostname)) {
@@ -197,10 +197,10 @@ const response = await fetch(url.toString())
### 6. 不安全的驗證(關鍵)
```javascript
// 關鍵:明文密碼比對
// FAIL: 關鍵:明文密碼比對
if (password === storedPassword) { /* login */ }
// 正確:雜湊密碼比對
// PASS: 正確:雜湊密碼比對
import bcrypt from 'bcrypt'
const isValid = await bcrypt.compare(password, hashedPassword)
```
@@ -208,13 +208,13 @@ const isValid = await bcrypt.compare(password, hashedPassword)
### 7. 授權不足(關鍵)
```javascript
// 關鍵:沒有授權檢查
// FAIL: 關鍵:沒有授權檢查
app.get('/api/user/:id', async (req, res) => {
const user = await getUser(req.params.id)
res.json(user)
})
// 正確:驗證使用者可以存取資源
// PASS: 正確:驗證使用者可以存取資源
app.get('/api/user/:id', authenticateUser, async (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' })
@@ -227,13 +227,13 @@ app.get('/api/user/:id', authenticateUser, async (req, res) => {
### 8. 財務操作中的競態條件(關鍵)
```javascript
// 關鍵:餘額檢查中的競態條件
// FAIL: 關鍵:餘額檢查中的競態條件
const balance = await getBalance(userId)
if (balance >= amount) {
await withdraw(userId, amount) // 另一個請求可能同時提款!
}
// 正確:帶鎖定的原子交易
// PASS: 正確:帶鎖定的原子交易
await db.transaction(async (trx) => {
const balance = await trx('balances')
.where({ user_id: userId })
@@ -253,13 +253,13 @@ await db.transaction(async (trx) => {
### 9. 速率限制不足(高)
```javascript
// 高:沒有速率限制
// FAIL: 高:沒有速率限制
app.post('/api/trade', async (req, res) => {
await executeTrade(req.body)
res.json({ success: true })
})
// 正確:速率限制
// PASS: 正確:速率限制
import rateLimit from 'express-rate-limit'
const tradeLimiter = rateLimit({
@@ -277,10 +277,10 @@ app.post('/api/trade', tradeLimiter, async (req, res) => {
### 10. 記錄敏感資料(中)
```javascript
// 中:記錄敏感資料
// FAIL: 中:記錄敏感資料
console.log('User login:', { email, password, apiKey })
// 正確:清理日誌
// PASS: 正確:清理日誌
console.log('User login:', {
email: email.replace(/(?<=.).(?=.*@)/g, '*'),
passwordProvided: !!password
@@ -302,7 +302,7 @@ console.log('User login:', {
- **高優先問題:** Y
- **中優先問題:** Z
- **低優先問題:** W
- **風險等級:** 🔴 高 / 🟡 中 / 🟢
- **風險等級:** 高 / 中 / 低
## 關鍵問題(立即修復)
@@ -324,7 +324,7 @@ console.log('User login:', {
**修復:**
```javascript
// 安全的實作
// PASS: 安全的實作
```
**參考:**
@@ -365,13 +365,13 @@ console.log('User login:', {
## 成功指標
安全性審查後:
- 未發現關鍵問題
- 所有高優先問題已處理
- 安全性檢查清單完成
- 程式碼中無密鑰
- 相依性已更新
- 測試包含安全性情境
- 文件已更新
- PASS: 未發現關鍵問題
- PASS: 所有高優先問題已處理
- PASS: 安全性檢查清單完成
- PASS: 程式碼中無密鑰
- PASS: 相依性已更新
- PASS: 測試包含安全性情境
- PASS: 文件已更新
---

View File

@@ -220,26 +220,26 @@ jest.mock('@/lib/openai', () => ({
## 測試異味(反模式)
### 測試實作細節
### FAIL: 測試實作細節
```typescript
// 不要測試內部狀態
expect(component.state.count).toBe(5)
```
### 測試使用者可見的行為
### PASS: 測試使用者可見的行為
```typescript
// 測試使用者看到的
expect(screen.getByText('Count: 5')).toBeInTheDocument()
```
### 測試相互依賴
### FAIL: 測試相互依賴
```typescript
// 不要依賴前一個測試
test('creates user', () => { /* ... */ })
test('updates same user', () => { /* 需要前一個測試 */ })
```
### 獨立測試
### PASS: 獨立測試
```typescript
// 在每個測試中設定資料
test('updates user', () => {

View File

@@ -65,20 +65,20 @@ open artifacts/search-results.png
## 最佳實務
**應該做:**
- 使用 Page Object Model 以利維護
- 使用 data-testid 屬性作為選擇器
- 等待 API 回應,不要用任意逾時
- 測試關鍵使用者旅程端對端
- 合併到主分支前執行測試
- 測試失敗時審查產出物
- PASS: 使用 Page Object Model 以利維護
- PASS: 使用 data-testid 屬性作為選擇器
- PASS: 等待 API 回應,不要用任意逾時
- PASS: 測試關鍵使用者旅程端對端
- PASS: 合併到主分支前執行測試
- PASS: 測試失敗時審查產出物
**不應該做:**
- 使用脆弱的選擇器CSS class 可能改變)
- 測試實作細節
- 對生產環境執行測試
- 忽略不穩定的測試
- 失敗時跳過產出物審查
- 用 E2E 測試每個邊界情況(使用單元測試)
- FAIL: 使用脆弱的選擇器CSS class 可能改變)
- FAIL: 測試實作細節
- FAIL: 對生產環境執行測試
- FAIL: 忽略不穩定的測試
- FAIL: 失敗時跳過產出物審查
- FAIL: 用 E2E 測試每個邊界情況(使用單元測試)
## 快速指令

View File

@@ -70,9 +70,9 @@ govulncheck ./...
| 狀態 | 條件 |
|------|------|
| 批准 | 沒有關鍵或高優先問題 |
| ⚠️ 警告 | 只有中優先問題(謹慎合併)|
| 阻擋 | 發現關鍵或高優先問題 |
| PASS: 批准 | 沒有關鍵或高優先問題 |
| WARNING: 警告 | 只有中優先問題(謹慎合併)|
| FAIL: 阻擋 | 發現關鍵或高優先問題 |
## 與其他指令的整合

View File

@@ -49,20 +49,20 @@ REPEAT: 下一個功能/情境
## TDD 最佳實務
**應該做:**
- 在任何實作前先撰寫測試
- 在實作前執行測試並驗證它們失敗
- 撰寫最小程式碼使測試通過
- 只在測試通過後才重構
- 新增邊界情況和錯誤情境
- 目標 80% 以上覆蓋率(關鍵程式碼 100%
- PASS: 在任何實作前先撰寫測試
- PASS: 在實作前執行測試並驗證它們失敗
- PASS: 撰寫最小程式碼使測試通過
- PASS: 只在測試通過後才重構
- PASS: 新增邊界情況和錯誤情境
- PASS: 目標 80% 以上覆蓋率(關鍵程式碼 100%
**不應該做:**
- 在測試之前撰寫實作
- 跳過每次變更後執行測試
- 一次撰寫太多程式碼
- 忽略失敗的測試
- 測試實作細節(測試行為)
- Mock 所有東西(優先使用整合測試)
- FAIL: 在測試之前撰寫實作
- FAIL: 跳過每次變更後執行測試
- FAIL: 一次撰寫太多程式碼
- FAIL: 忽略失敗的測試
- FAIL: 測試實作細節(測試行為)
- FAIL: Mock 所有東西(優先使用整合測試)
## 覆蓋率要求

View File

@@ -12,7 +12,7 @@ description: Backend architecture patterns, API design, database optimization, a
### RESTful API 結構
```typescript
// 基於資源的 URL
// PASS: 基於資源的 URL
GET /api/markets #
GET /api/markets/:id #
POST /api/markets #
@@ -20,7 +20,7 @@ PUT /api/markets/:id # 替換資源
PATCH /api/markets/:id #
DELETE /api/markets/:id #
// 用於過濾、排序、分頁的查詢參數
// PASS: 用於過濾、排序、分頁的查詢參數
GET /api/markets?status=active&sort=volume&limit=20&offset=0
```
@@ -120,7 +120,7 @@ export default withAuth(async (req, res) => {
### 查詢優化
```typescript
// 良好:只選擇需要的欄位
// PASS: 良好:只選擇需要的欄位
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
@@ -128,7 +128,7 @@ const { data } = await supabase
.order('volume', { ascending: false })
.limit(10)
// 不良:選擇所有欄位
// FAIL: 不良:選擇所有欄位
const { data } = await supabase
.from('markets')
.select('*')
@@ -137,13 +137,13 @@ const { data } = await supabase
### N+1 查詢問題預防
```typescript
// 不良N+1 查詢問題
// FAIL: 不良N+1 查詢問題
const markets = await getMarkets()
for (const market of markets) {
market.creator = await getUser(market.creator_id) // N 次查詢
}
// 良好:批次取得
// PASS: 良好:批次取得
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds) // 1 次查詢

View File

@@ -86,7 +86,7 @@ ORDER BY hour DESC;
### 高效過濾
```sql
-- 良好:先使用索引欄位
-- PASS: 良好:先使用索引欄位
SELECT *
FROM markets_analytics
WHERE date >= '2025-01-01'
@@ -95,7 +95,7 @@ WHERE date >= '2025-01-01'
ORDER BY date DESC
LIMIT 100;
-- 不良:先過濾非索引欄位
-- FAIL: 不良:先過濾非索引欄位
SELECT *
FROM markets_analytics
WHERE volume > 1000
@@ -106,7 +106,7 @@ WHERE volume > 1000
### 聚合
```sql
-- 良好:使用 ClickHouse 特定聚合函式
-- PASS: 良好:使用 ClickHouse 特定聚合函式
SELECT
toStartOfDay(created_at) AS day,
market_id,
@@ -119,7 +119,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
GROUP BY day, market_id
ORDER BY day DESC, total_volume DESC;
-- 使用 quantile 計算百分位數(比 percentile 更高效)
-- PASS: 使用 quantile 計算百分位數(比 percentile 更高效)
SELECT
quantile(0.50)(trade_size) AS median,
quantile(0.95)(trade_size) AS p95,
@@ -162,7 +162,7 @@ const clickhouse = new ClickHouse({
}
})
// 批量插入(高效)
// PASS: 批量插入(高效)
async function bulkInsertTrades(trades: Trade[]) {
const values = trades.map(trade => `(
'${trade.id}',
@@ -178,7 +178,7 @@ async function bulkInsertTrades(trades: Trade[]) {
`).toPromise()
}
// 個別插入(慢)
// FAIL: 個別插入(慢)
async function insertTrade(trade: Trade) {
// 不要在迴圈中這樣做!
await clickhouse.query(`

View File

@@ -38,12 +38,12 @@ description: Universal coding standards, best practices, and patterns for TypeSc
### 變數命名
```typescript
// 良好:描述性名稱
// PASS: 良好:描述性名稱
const marketSearchQuery = 'election'
const isUserAuthenticated = true
const totalRevenue = 1000
// 不良:不清楚的名稱
// FAIL: 不良:不清楚的名稱
const q = 'election'
const flag = true
const x = 1000
@@ -52,12 +52,12 @@ const x = 1000
### 函式命名
```typescript
// 良好:動詞-名詞模式
// PASS: 良好:動詞-名詞模式
async function fetchMarketData(marketId: string) { }
function calculateSimilarity(a: number[], b: number[]) { }
function isValidEmail(email: string): boolean { }
// 不良:不清楚或只有名詞
// FAIL: 不良:不清楚或只有名詞
async function market(id: string) { }
function similarity(a, b) { }
function email(e) { }
@@ -66,7 +66,7 @@ function email(e) { }
### 不可變性模式(關鍵)
```typescript
// 總是使用展開運算符
// PASS: 總是使用展開運算符
const updatedUser = {
...user,
name: 'New Name'
@@ -74,7 +74,7 @@ const updatedUser = {
const updatedArray = [...items, newItem]
// 永遠不要直接修改
// FAIL: 永遠不要直接修改
user.name = 'New Name' // 不良
items.push(newItem) // 不良
```
@@ -82,7 +82,7 @@ items.push(newItem) // 不良
### 錯誤處理
```typescript
// 良好:完整的錯誤處理
// PASS: 良好:完整的錯誤處理
async function fetchData(url: string) {
try {
const response = await fetch(url)
@@ -98,7 +98,7 @@ async function fetchData(url: string) {
}
}
// 不良:無錯誤處理
// FAIL: 不良:無錯誤處理
async function fetchData(url) {
const response = await fetch(url)
return response.json()
@@ -108,14 +108,14 @@ async function fetchData(url) {
### Async/Await 最佳實務
```typescript
// 良好:可能時並行執行
// PASS: 良好:可能時並行執行
const [users, markets, stats] = await Promise.all([
fetchUsers(),
fetchMarkets(),
fetchStats()
])
// 不良:不必要的順序執行
// FAIL: 不良:不必要的順序執行
const users = await fetchUsers()
const markets = await fetchMarkets()
const stats = await fetchStats()
@@ -124,7 +124,7 @@ const stats = await fetchStats()
### 型別安全
```typescript
// 良好:正確的型別
// PASS: 良好:正確的型別
interface Market {
id: string
name: string
@@ -136,7 +136,7 @@ function getMarket(id: string): Promise<Market> {
// 實作
}
// 不良:使用 'any'
// FAIL: 不良:使用 'any'
function getMarket(id: any): Promise<any> {
// 實作
}
@@ -147,7 +147,7 @@ function getMarket(id: any): Promise<any> {
### 元件結構
```typescript
// 良好:具有型別的函式元件
// PASS: 良好:具有型別的函式元件
interface ButtonProps {
children: React.ReactNode
onClick: () => void
@@ -172,7 +172,7 @@ export function Button({
)
}
// 不良:無型別、結構不清楚
// FAIL: 不良:無型別、結構不清楚
export function Button(props) {
return <button onClick={props.onClick}>{props.children}</button>
}
@@ -181,7 +181,7 @@ export function Button(props) {
### 自訂 Hooks
```typescript
// 良好:可重用的自訂 hook
// PASS: 良好:可重用的自訂 hook
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
@@ -203,25 +203,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
### 狀態管理
```typescript
// 良好:正確的狀態更新
// PASS: 良好:正確的狀態更新
const [count, setCount] = useState(0)
// 基於先前狀態的函式更新
setCount(prev => prev + 1)
// 不良:直接引用狀態
// FAIL: 不良:直接引用狀態
setCount(count + 1) // 在非同步情境中可能過時
```
### 條件渲染
```typescript
// 良好:清晰的條件渲染
// PASS: 良好:清晰的條件渲染
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <DataDisplay data={data} />}
// 不良:三元地獄
// FAIL: 不良:三元地獄
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
```
@@ -244,7 +244,7 @@ GET /api/markets?status=active&limit=10&offset=0
### 回應格式
```typescript
// 良好:一致的回應結構
// PASS: 良好:一致的回應結構
interface ApiResponse<T> {
success: boolean
data?: T
@@ -275,7 +275,7 @@ return NextResponse.json({
```typescript
import { z } from 'zod'
// 良好Schema 驗證
// PASS: 良好Schema 驗證
const CreateMarketSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().min(1).max(2000),
@@ -338,14 +338,14 @@ types/market.types.ts # 型別用 camelCase 加 .types 後綴
### 何時註解
```typescript
// 良好:解釋「為什麼」而非「什麼」
// PASS: 良好:解釋「為什麼」而非「什麼」
// 使用指數退避以避免在服務中斷時壓垮 API
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
// 為了處理大陣列的效能,此處刻意使用突變
items.push(newItem)
// 不良:陳述顯而易見的事實
// FAIL: 不良:陳述顯而易見的事實
// 將計數器加 1
count++
@@ -385,12 +385,12 @@ export async function searchMarkets(
```typescript
import { useMemo, useCallback } from 'react'
// 良好:記憶化昂貴的計算
// PASS: 良好:記憶化昂貴的計算
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// 良好:記憶化回呼函式
// PASS: 良好:記憶化回呼函式
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
@@ -401,7 +401,7 @@ const handleSearch = useCallback((query: string) => {
```typescript
import { lazy, Suspense } from 'react'
// 良好:延遲載入重型元件
// PASS: 良好:延遲載入重型元件
const HeavyChart = lazy(() => import('./HeavyChart'))
export function Dashboard() {
@@ -416,13 +416,13 @@ export function Dashboard() {
### 資料庫查詢
```typescript
// 良好:只選擇需要的欄位
// PASS: 良好:只選擇需要的欄位
const { data } = await supabase
.from('markets')
.select('id, name, status')
.limit(10)
// 不良:選擇所有欄位
// FAIL: 不良:選擇所有欄位
const { data } = await supabase
.from('markets')
.select('*')
@@ -449,12 +449,12 @@ test('calculates similarity correctly', () => {
### 測試命名
```typescript
// 良好:描述性測試名稱
// PASS: 良好:描述性測試名稱
test('returns empty array when no markets match query', () => { })
test('throws error when OpenAI API key is missing', () => { })
test('falls back to substring search when Redis unavailable', () => { })
// 不良:模糊的測試名稱
// FAIL: 不良:模糊的測試名稱
test('works', () => { })
test('test search', () => { })
```
@@ -465,12 +465,12 @@ test('test search', () => { })
### 1. 過長函式
```typescript
// 不良:函式超過 50 行
// FAIL: 不良:函式超過 50 行
function processMarketData() {
// 100 行程式碼
}
// 良好:拆分為較小的函式
// PASS: 良好:拆分為較小的函式
function processMarketData() {
const validated = validateData()
const transformed = transformData(validated)
@@ -480,7 +480,7 @@ function processMarketData() {
### 2. 過深巢狀
```typescript
// 不良5 層以上巢狀
// FAIL: 不良5 層以上巢狀
if (user) {
if (user.isAdmin) {
if (market) {
@@ -493,7 +493,7 @@ if (user) {
}
}
// 良好:提前返回
// PASS: 良好:提前返回
if (!user) return
if (!user.isAdmin) return
if (!market) return
@@ -505,11 +505,11 @@ if (!hasPermission) return
### 3. 魔術數字
```typescript
// 不良:無解釋的數字
// FAIL: 不良:無解釋的數字
if (retryCount > 3) { }
setTimeout(callback, 500)
// 良好:命名常數
// PASS: 良好:命名常數
const MAX_RETRIES = 3
const DEBOUNCE_DELAY_MS = 500

View File

@@ -12,7 +12,7 @@ description: Frontend development patterns for React, Next.js, state management,
### 組合優於繼承
```typescript
// 良好:元件組合
// PASS: 良好:元件組合
interface CardProps {
children: React.ReactNode
variant?: 'default' | 'outlined'
@@ -283,17 +283,17 @@ export function useMarkets() {
### 記憶化
```typescript
// useMemo 用於昂貴計算
// PASS: useMemo 用於昂貴計算
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// useCallback 用於傳遞給子元件的函式
// PASS: useCallback 用於傳遞給子元件的函式
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
// React.memo 用於純元件
// PASS: React.memo 用於純元件
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
return (
<div className="market-card">
@@ -309,7 +309,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
```typescript
import { lazy, Suspense } from 'react'
// 延遲載入重型元件
// PASS: 延遲載入重型元件
const HeavyChart = lazy(() => import('./HeavyChart'))
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
@@ -504,7 +504,7 @@ export class ErrorBoundary extends React.Component<
```typescript
import { motion, AnimatePresence } from 'framer-motion'
// 列表動畫
// PASS: 列表動畫
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
return (
<AnimatePresence>
@@ -523,7 +523,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
)
}
// Modal 動畫
// PASS: Modal 動畫
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>

View File

@@ -27,12 +27,12 @@ description: Pattern for progressively refining context retrieval to solve the s
┌─────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ DISPATCH │─────│ EVALUATE │ │
│ │ DISPATCH │─────│ EVALUATE │ │
│ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ LOOP │─────│ REFINE │ │
│ │ LOOP │─────│ REFINE │ │
│ └──────────┘ └──────────┘ │
│ │
│ 最多 3 個循環,然後繼續 │

View File

@@ -21,13 +21,13 @@ description: Use this skill when adding authentication, handling user input, wor
### 1. 密鑰管理
#### 絕不這樣做
#### FAIL: 絕不這樣做
```typescript
const apiKey = "sk-proj-xxxxx" // 寫死的密鑰
const dbPassword = "password123" // 在原始碼中
```
#### 總是這樣做
#### PASS: 總是這樣做
```typescript
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
@@ -107,14 +107,14 @@ function validateFileUpload(file: File) {
### 3. SQL 注入預防
#### 絕不串接 SQL
#### FAIL: 絕不串接 SQL
```typescript
// 危險 - SQL 注入漏洞
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
```
#### 總是使用參數化查詢
#### PASS: 總是使用參數化查詢
```typescript
// 安全 - 參數化查詢
const { data } = await supabase
@@ -139,10 +139,10 @@ await db.query(
#### JWT Token 處理
```typescript
// 錯誤localStorage易受 XSS 攻擊)
// FAIL: 錯誤localStorage易受 XSS 攻擊)
localStorage.setItem('token', token)
// 正確httpOnly cookies
// PASS: 正確httpOnly cookies
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
```
@@ -299,18 +299,18 @@ app.use('/api/search', searchLimiter)
#### 日誌記錄
```typescript
// 錯誤:記錄敏感資料
// FAIL: 錯誤:記錄敏感資料
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })
// 正確:遮蔽敏感資料
// PASS: 正確:遮蔽敏感資料
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })
```
#### 錯誤訊息
```typescript
// 錯誤:暴露內部細節
// FAIL: 錯誤:暴露內部細節
catch (error) {
return NextResponse.json(
{ error: error.message, stack: error.stack },
@@ -318,7 +318,7 @@ catch (error) {
)
}
// 正確:通用錯誤訊息
// PASS: 正確:通用錯誤訊息
catch (error) {
console.error('Internal error:', error)
return NextResponse.json(

View File

@@ -24,7 +24,7 @@
#### 最小權限原則
```yaml
# 正確:最小權限
# PASS: 正確:最小權限
iam_role:
permissions:
- s3:GetObject # 只有讀取存取
@@ -32,7 +32,7 @@ iam_role:
resources:
- arn:aws:s3:::my-bucket/* # 只有特定 bucket
# 錯誤:過於廣泛的權限
# FAIL: 錯誤:過於廣泛的權限
iam_role:
permissions:
- s3:* # 所有 S3 動作
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
#### 雲端密鑰管理器
```typescript
// 正確:使用雲端密鑰管理器
// PASS: 正確:使用雲端密鑰管理器
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' });
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
const apiKey = JSON.parse(secret.SecretString).key;
// 錯誤:寫死或只在環境變數
// FAIL: 錯誤:寫死或只在環境變數
const apiKey = process.env.API_KEY; // 未輪換、未稽核
```
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
#### VPC 和防火牆設定
```terraform
# 正確:限制的安全群組
# PASS: 正確:限制的安全群組
resource "aws_security_group" "app" {
name = "app-sg"
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
}
}
# 錯誤:對網際網路開放
# FAIL: 錯誤:對網際網路開放
resource "aws_security_group" "bad" {
ingress {
from_port = 0
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
#### CloudWatch/日誌設定
```typescript
// 正確:全面日誌記錄
// PASS: 正確:全面日誌記錄
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
const logSecurityEvent = async (event: SecurityEvent) => {
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
#### 安全管線設定
```yaml
# 正確:安全的 GitHub Actions 工作流程
# PASS: 正確:安全的 GitHub Actions 工作流程
name: Deploy
on:
@@ -237,7 +237,7 @@ jobs:
#### Cloudflare 安全設定
```typescript
// 正確:帶安全標頭的 Cloudflare Workers
// PASS: 正確:帶安全標頭的 Cloudflare Workers
export default {
async fetch(request: Request): Promise<Response> {
const response = await fetch(request);
@@ -281,7 +281,7 @@ export default {
#### 自動備份
```terraform
# 正確:自動 RDS 備份
# PASS: 正確:自動 RDS 備份
resource "aws_db_instance" "main" {
allocated_storage = 20
engine = "postgres"
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
### S3 Bucket 暴露
```bash
# 錯誤:公開 bucket
# FAIL: 錯誤:公開 bucket
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
# 正確:私有 bucket 並有特定存取
# PASS: 正確:私有 bucket 並有特定存取
aws s3api put-bucket-acl --bucket my-bucket --acl private
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
```
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
### RDS 公開存取
```terraform
# 錯誤
# FAIL: 錯誤
resource "aws_db_instance" "bad" {
publicly_accessible = true # 絕不這樣做!
}
# 正確
# PASS: 正確
resource "aws_db_instance" "good" {
publicly_accessible = false
vpc_security_group_ids = [aws_security_group.db.id]

View File

@@ -313,39 +313,39 @@ npm run test:coverage
## 常見測試錯誤避免
### 錯誤:測試實作細節
### FAIL: 錯誤:測試實作細節
```typescript
// 不要測試內部狀態
expect(component.state.count).toBe(5)
```
### 正確:測試使用者可見行為
### PASS: 正確:測試使用者可見行為
```typescript
// 測試使用者看到的內容
expect(screen.getByText('Count: 5')).toBeInTheDocument()
```
### 錯誤:脆弱的選擇器
### FAIL: 錯誤:脆弱的選擇器
```typescript
// 容易壞掉
await page.click('.css-class-xyz')
```
### 正確:語意選擇器
### PASS: 正確:語意選擇器
```typescript
// 對變更有彈性
await page.click('button:has-text("Submit")')
await page.click('[data-testid="submit-button"]')
```
### 錯誤:無測試隔離
### FAIL: 錯誤:無測試隔離
```typescript
// 測試互相依賴
test('creates user', () => { /* ... */ })
test('updates same user', () => { /* 依賴前一個測試 */ })
```
### 正確:獨立測試
### PASS: 正確:獨立測試
```typescript
// 每個測試設置自己的資料
test('creates user', () => {