jim800121chen cff9236699 docs: migrate Autoflow shared documents to docs/autoflow/
Move PRD, design specs, architecture docs, and TDD from .autoflow/
(personal/per-branch layer) to docs/autoflow/ (shared layer that
goes into git) per the new Autoflow workspace layout.

Files moved:
- 02-prd/PRD.md
- 03-design/design-review.md
- 03-design/user-flow-cross-system.md
- 04-architecture/TDD.md
- 04-architecture/design-doc.md
- 04-architecture/security.md

The originals were never tracked, so git mv reduced to a filesystem
rename with no history to preserve. .autoflow/ remains for personal
notes (progress.md, review reports, testing logs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:59:21 +08:00

348 lines
15 KiB
Markdown
Raw 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.

# Security Notes — Phase 1
> 本文件記錄 Phase 1 已知的安全設計決策、被接受的風險、以及對應的 mitigation 與 Phase 2 改進候補方案。
>
> **更新時機**每次安全審查Reviewer / Security Auditor發現新風險或變更現有 trust assumption 時,必須更新此檔案。
## 索引
| Section | 內容 |
|---------|------|
| [Trust Boundary](#trust-boundary重要-design-risk) | user_id 來源信任問題Phase 1 接受風險) |
| [Input Validation](#input-validation) | 已落實的輸入驗證機制 |
| [Storage Security](#storage-security) | MinIO object key 控制與 cleanup 策略 |
| [Auth Security](#auth-security) | JWT / JWKS 配置、algorithm pin |
| [Rate Limiting](#rate-limiting) | 雙層 rate limiter 設計 |
| [Logging](#logging) | 結構化 log 與敏感資料保護 |
| [Phase 2 候補方案清單](#phase-2-候補方案清單) | 已知待補強的設計 |
---
## Trust Boundary重要 design risk
### user_id 為 multipart field受信任 visionA-backend 帶來
#### 設計
`POST /api/v1/jobs``user_id` 從 multipart form field 傳入,**不是**從 JWT claim derive。Converter 完全信任 visionA-backend 端把對的 `user_id` 傳進來。
```
visionA-backend Converter
│ │
├── client_credentials ──────→│ (取得 access token)
│ │
├── POST /api/v1/jobs ────────→│ Form-Data:
│ Authorization: Bearer … │ user_id: "alice" ← visionA 端決定
│ │ model: <file>
│ │ ...
│ │
│ │ Converter 端:
│ │ - 用 token 驗 clientOK
│ │ - 信任 user_id 是「真正提交的 user」
│ │ - 不再驗證 user_id 與 token 的關係
```
#### Trust assumptionPhase 1
visionA-backend 端:
1. **程式碼安全** — 無 XSS / SSRF / RCE 漏洞user_id 來源可信
2. **infra 安全** — network ACL、IP allow-list、TLS 確保只有 visionA-backend 能呼叫此 API
3. **credential 管理**`client_secret` 不外洩、不放 git、不寫 log
4. **audit log 健全** — visionA 端能追溯「哪個真實用戶觸發了哪次轉檔」
#### Risk被接受
visionA-backend **一旦被 compromise**attacker 可用同一個合法 `client_credentials`
| 攻擊面 | 影響 |
|-------|------|
| 為任意 `user_id` 建 job | 冒充任何 useruser_id 完全由 attacker 控制)|
| 鎖定特定 user 7 天 | active_job conflict 機制被當武器(任意 user_id 一旦被鎖,正常請求也 409|
| 偽造的 job 計入 victim user_id 的 history | `user:{victim}:jobs` Set 被汙染,未來查 history 看到不是自己的紀錄 |
| 累計 victim 的 job count如有 quota / billing | Phase 2 若引入 per-user quota / billing會誤計到 victim 上 |
#### Phase 1 決策2026-04-25 使用者裁決)
**接受此風險。** 理由:
1. visionA-backend 是內部受控系統(非 Internet-facingcompromise 機率低
2. Phase 1 重點是把核心 pipeline 跑通,安全強化排在 Phase 2
3. 引入 HMAC / OBO 會增加 visionA 端的整合工作量,目前未取得對方確認
#### MitigationsPhase 1 已採用)
| Mitigation | 說明 |
|-----------|------|
| **per-client_id rate limiter300 req / 5min** | 限制單一 client即被 compromise 的 visionA-backend的攻擊速度 |
| **input 完全由 server 控制 object_key** | `jobs/{server-生成 uuidv4}/input/{sanitize 後 filename}`attacker 無法控制 prefix |
| **filename / user_id 嚴格 sanitize** | 阻擋 path traversal / Redis key injection / log injection / XSS / glob pattern |
| **structured audit log含 client_id + user_id pair** | 可從 log 反查「哪個 client 為哪個 user_id 建了 job」發現 compromise 時加速 forensics |
| **active_job 7 天 TTL**fail-safe| 即便 worker 異常未清TTL 也會自動 GC避免 attacker 鎖死後永久不釋放 |
#### Phase 2 候補方案
##### 方案 1HMAC-signed user_id推薦短期
visionA-backend 用共享 secret HMAC 簽 user_idConverter 驗簽:
```
visionA-backend Converter
hmac = HMAC-SHA256( 收到 multipart 後:
secret, recv_user_id, recv_hmac
user_id || timestamp) ↓
if HMAC-SHA256(secret, recv_user_id || ts) != recv_hmac:
POST /api/v1/jobs ─────────→ return 401 invalid_hmac
Form: if abs(now - ts) > 60s:
user_id: "alice" return 401 hmac_expired
x_user_id_hmac: "<hex>" else:
x_user_id_ts: "<unix>" accept user_id
```
**優點**:實作簡單,雙方只需要共享 secret + 規範 hash。
**缺點**:仍是 symmetric secret有外洩風險不會解決「visionA 自己被 compromise」的場景attacker 也能簽)。
##### 方案 2OBO Token / Token Exchange業界標準推薦中期
visionA-backend 為每個 user 取 user-context token例如 OBO flow / Token Exchange RFC 8693Converter 從 JWT claims 取 `user_id`
```
visionA-backend Member Center Converter
POST /token ──────────────→ grant_type=token-exchange
subject_token=<user-token> audience=converter
subject_token_type=jwt ↓
new token with claims:
sub: "alice" ← user 身份
actor: { sub: "visionA-client" } ← 委託 client
←─── new access token ─────
POST /api/v1/jobs ─────────────────────────────────────────────────→
Authorization: Bearer <new token> ↓
Converter 從 token claims
取 user_id不是 multipart
```
**優點**
- 完全消除 trust boundary 問題user_id 來自 Member Center 簽過的 JWT
- 業界標準,跨 vendor 適用
- 自動 audit chainactor + subject 雙重身份)
**缺點**
- visionA / Member Center 都需要實作 Token Exchange 流程
- 性能:每次 POST /jobs 多一次 Token Exchange round-trip可 cache 緩解)
##### 方案 3Audit Anomaly Detection補強
偵測同 `client_id` 短期內出現大量不同 `user_id` 的異常 pattern
```
監控 metric
unique_user_ids_per_client_per_5min{client_id="visionA-backend-client"}
正常 pattern
- 一個 client_id 5 分鐘內可能服務 5-50 個不同 user_id
異常 pattern
- 一個 client_id 5 分鐘內出現 500 個不同 user_id
- 一個 client_id 連續 1 小時內每 5 分鐘出現 < 1 秒的 burst自動化攻擊
告警 → 人工介入 → 視情況 revoke client_credentials
```
**優點**:不需要改動 protocol可獨立實作對已 deployed 系統最容易加上。
**缺點**:被動防禦(事後發現),無法即時阻擋。
---
## Input Validation
### 已落實Phase 1
| 項目 | 實作位置 | 機制 |
|------|---------|------|
| **filename sanitize** | `src/utils/sanitize.js` `sanitizeFilename` | NUL byte truncation / path.posix.basename / 控制字元 / 白名單字元 / 截長 200 / leading-dot 移除 |
| **user_id 嚴格白名單** | `src/utils/sanitize.js` `validateUserId` | `^[A-Za-z0-9._-]+$` regexSec M1 強化)+ 額外 `..` 拒絕 |
| **version 嚴格白名單** | `src/routes/v1/validators/createJob.js` | `^[A-Za-z0-9._-]+$` regexSec M3|
| **model_id 數字範圍** | 同上 | `^\d+$` + 1 ≤ x ≤ 65535 |
| **platform enum** | 同上 | `{520, 720, 530, 630, 730}` |
| **enable_* boolean** | 同上 | 嚴格 `'true'` / `'false'` 字串 |
| **metadata JSON object** | 同上 | JSON.parse + 拒絕 array / null / primitive |
| **model 副檔名白名單** | 同上 | `{.onnx, .tflite}`PRD F-01|
| **model file 大小** | multer + handler | 預設 500MBmulter LIMIT_FILE_SIZE → 413|
| **ref_image per-file 大小** | `validateCreateJobRequest` | 10MBSec C2 修正,避免 100 張 × 500MB = 50GB OOM|
| **ref_image 張數** | multer fields config | maxCount=100 |
### 攻擊向量驗證清單
| 攻擊向量 | 防禦點 | 測試 |
|---------|-------|------|
| Path traversal in filename | `sanitizeFilename` `path.posix.basename` + leading-dot strip | `sanitize.test.js` |
| NUL byte truncation | `sanitizeFilename` `split('\0')` | 同上 |
| Windows path / backslash | `sanitizeFilename` `replace(/\\/g, '/')` | 同上 |
| Redis key injection in user_id | `validateUserId` 拒絕 `:` | 同上 |
| XSS in user_id | `validateUserId` 嚴格白名單 | 同上Sec M1|
| XSS in version | `version` 嚴格白名單 | `createJob.validator.test.js`Sec M3|
| Unicode RTL override | 嚴格白名單拒絕非 ASCII | 同上 |
| Glob / shell metachar in user_id / version | 嚴格白名單拒絕 `*?[];&|$` 等 | 同上 |
| ref_image OOM (100 × 500MB) | per-file 10MB 上限 | `createJob.integration.test.js`Sec C2|
| log injection (CRLF) | 嚴格白名單拒絕 `\r\n` | 同上 |
---
## Storage Security
### MinIO object key 完全 server 控制
```
inputObjectKey = `jobs/${jobId}/input/${safeFilename}`
^uuidv4 ^server-controlled prefix ^sanitized
refImageKey = `jobs/${jobId}/ref_images/${index}_${safeFilename}`
```
attacker 無法控制:
- prefix `jobs/` — server hardcode
- jobId — server 用 `uuidv4()` 生成
- ref_images index — server 用 `idx` 自增
attacker 部分控制(已 sanitize
- safeFilename — 經 `sanitizeFilename` 處理(最壞情況產生合法的相對檔名)
### M5 方案 A先寫 MinIO 後 Lua claim
避免「拿到 Lua claim 但 MinIO 失敗」需要 rollback Redis 的複雜度:
- MinIO 失敗 → 直接回 502Redis 完全乾淨
- Lua conflict / throw → cleanup MinIOfire-and-forget靠 7d lifecycle 兜底)
- enqueue 失敗 → 補償 release Redis + cleanup MinIOSec M2 + Reviewer Major-2 修正)
### Sec M4寫入放大 pre-check
handler 在 `writeInputToMinIO` 之前先廉價 GET `user:{userId}:active_job`,若已存在直接回 409。
避免 conflict request 還是上傳完 500MB 才被 Lua reject節省頻寬與記憶體
> ⚠️ pre-check 與 Lua claim 之間仍有 race兩個 request 同時通過 pre-check最終 atomicity 仍由 Lua 保證pre-check 純粹是「optimization」。
### Sec M5mount-time STORAGE_BACKEND 檢查
`createJobsRouter` 在 mount 時就檢查 `STORAGE_BACKEND === 'minio'`
- 不對 → **不掛 multer**POST /api/v1/jobs 直接回 500 misconfiguration
- 不會吃 multipart body避免 misconfig 也消耗 500MB 記憶體
- GET / DELETE / download-tokens 不依賴 storage backend仍正常掛
---
## Auth Security
### JWT Algorithm PinSec m3
`src/auth/jwks.js` 明確 pin 接受的 JWT signing algorithm
```js
const ALLOWED_JWT_ALGS = ['RS256', 'ES256', 'PS256'];
```
拒絕:
- `none`jose 預設拒絕,但仍明確列出)
- `HS256` / `HS384` / `HS512`HMAC避免演算法混淆攻擊
### JWKS Cache
- TTL 10 分鐘(`JWKS_CACHE_MAX_AGE_MS` env override
- Cooldown 30 秒(避免 JWKS endpoint 失敗時 thundering herd
- 模組層級 cache同一個 jwksUrl 共用一個 RemoteJWKSet
### Token 驗證
| 檢查項 | jose 預設 | Converter 加碼 |
|-------|----------|----------------|
| signature | ✅ | — |
| exp | ✅ | clockTolerance 60s |
| nbf | ✅ | — |
| issuer | — | ✅(`MEMBER_CENTER_ISSUER`|
| audience | — | ✅(`KNERON_CONVERTER_AUDIENCE`|
| algorithm | 拒絕 none | ✅ pin to RS256/ES256/PS256Sec m3|
---
## Rate Limiting
### 雙層設計
```
Request → IP-based limiter (200 req / 15min) ← app.js 全域
→ requireAuth (驗 token)
→ per-client_id limiter (300 req / 5min) ← v1 jobs router
→ multer / handler
```
| 層級 | 目的 | 範圍 |
|------|------|------|
| IP-based | 防匿名流量 / DDoS | 全 `/api` 前綴 |
| per-client_id | 合約上限 / 攻擊速度限制 | `/api/v1/jobs` 寫入端點 |
### Phase 2 待補
- Memory store 警告:目前用 process-local memory**多 instance 部署需改 Redis store**
- 目前對「未認證但合法路由」的 quota 計算可能誤殺 — 預設 1 個 visionA-backend IP 帶多 user 共用,需要監控 IP-based 是否誤殺
---
## Logging
### 結構化 JSON
所有 log 使用結構化 JSON 格式,必含:
- `timestamp`ISO 8601
- `level`INFO / WARN / ERROR
- `service``task-scheduler`
- `request_id`(貫穿請求生命週期)
- `action``domain.action` 格式)
### 敏感資料保護
**絕對不寫 log**(已逐條檢查):
- token / Authorization header
- file body / model 內容
- MinIO secret / OAuth client_secret
- JWT payload 完整 dump只記 `client_id` / `tenant_id` / `user_id`
**遮罩處理**(如有需要在 Phase 2 加):
- 原始 filename已 sanitize— 通常不視為敏感
- IPlog 仍記,但 GDPR 場景可能需要遮罩)
### Sec C1 暫緩
`.env` 一度被 commit 進 git history健檢時發現**已加入 `.gitignore` 但 history 仍可追溯**。
決策:
- **Phase 1 暫緩**2026-04-25 使用者裁決)
- **Phase 1 ready 後**會做一次 git history rewrite + 強制 rotate 所有 secret
- 在那之前,所有 secret 都被視為「可能已外洩」,**dev 環境用 dummy secretprod 用全新生成的 secret**
---
## Phase 2 候補方案清單
### 已知待補強(依優先級)
| # | 項目 | 優先級 | 預期任務 |
|---|------|-------|---------|
| 1 | **HMAC-signed user_id 或 OBO token**(解決 Trust Boundary| HIGH | Phase 2 — auth 強化 |
| 2 | **Git history rewrite**(清掉 .env 洩漏)| HIGH | Phase 1 ready 收尾 |
| 3 | **MULTIPART_MODEL_MAX_BYTES env 串接**(目前寫死 500MB| MEDIUM | T10 |
| 4 | **MAX_CONCURRENT_UPLOADS semaphore**(防多 user 並發 OOM| MEDIUM | T10 |
| 5 | **Stream storage 評估**(取代 memoryStorage根本解決 OOM| MEDIUM | Phase 2 — infra |
| 6 | **Rate limiter Redis store**(多 instance 部署前提)| MEDIUM | Phase 2 — infra |
| 7 | **Audit anomaly detection**user_id pattern 異常告警)| LOW | Phase 2 — observability |
| 8 | **Filename Unicode normalization**(極端 unicode bypass| LOW | Phase 2 — security 細修 |
| 9 | **Metadata prototype pollution 防護**(白名單 keys| LOW | Phase 2 — security 細修 |
| 10 | **Token revocation list / JWT blacklist**(無此需求現在)| LOW | Phase 2 — auth |
---
## 變更歷史
| 日期 | 變更 | 觸發 |
|------|------|------|
| 2026-04-25 | 初版 | T5 Reviewer + Security Audit 修復 |