mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-05 08:43:29 +08:00
fix: harden unicode safety checks
This commit is contained in:
@@ -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 {
|
||||
|
||||
**模式 2:Null/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 {
|
||||
|
||||
**模式 4:Import 錯誤**
|
||||
```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: 測試仍然通過
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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: 阻擋:發現關鍵或高優先問題
|
||||
|
||||
## 專案特定指南(範例)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 報告已產生
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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: 生產環境沒有回歸
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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: 文件已更新
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user