jim800121chen d8a9517c9d feat(task-scheduler): Phase 0.8b — API key auth + /result endpoint
Auth pillar 從 OAuth 2.0 resource server 改成 pre-shared API key
(visionA ↔ converter 1:1 internal trust)。新增 GET /api/v1/jobs/:id/result
streaming endpoint 給 visionA backend 中轉 NEF 下載。

Phase A(auth 切換):
- 新增 apiKeyMiddleware(constant-time compare、tokenFingerprint、4 audit events)
- 砍 OAuth middleware + JWKS(保留 oauthClient 供 promote → FAA 使用)
- 4 個 endpoint 換掛 requireApiKey
- 加 TRUST_PROXY env + Express trust proxy 設定(forensic source_ip)

Phase B(/result endpoint):
- streaming NEF download with 5min timeout + concurrent cap 10
- Two-tier rate limit(burst 5/10s + sustained 20/min)
- Bandwidth quota(1 GB/hr + 6 GB/24hr)by token_fingerprint
- Range header silently ignored + Accept-Ranges: none
- filename quote-escape + RFC 5987 fallback + sanitize
- 8 個 /result audit events(forensic 完整)

設計演進記錄:docs/TODO-visionA-integration-v2.md(5/2 OAuth → 5/16 API key
→ 5/16 download via converter;對應 visionA repo ADR-015/016)

Tests: 597 → 666 (+69)、29 suites all pass
Security: APPROVE WITH CONDITIONS(單 instance 部署、6 新 env、24hr 監控)
npm audit: 3 vuln → 0(transitive AWS SDK xml chain)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:47:28 +08:00

25 KiB
Raw Permalink Blame History

Security Notes — Phase 1 + Phase 0.8b

本文件記錄 Phase 1 + 0.8b 已知的安全設計決策、被接受的風險、以及對應的 mitigation 與 Phase 2 改進候補方案。

更新時機每次安全審查Reviewer / Security Auditor發現新風險或變更現有 trust assumption 時,必須更新此檔案。

Phase 0.8b 主要變動visionA → converter 對外 auth 從 OAuth JWT 改 pre-shared API keyADR-010。Trust boundary 模型不變,但 auth mechanism 大幅簡化。詳見下方「Auth Security」與「API Key Management」章節。

索引

Section 內容
Trust Boundary user_id 來源信任問題Phase 1 接受風險Phase 0.8b 風險模型不變)
Input Validation 已落實的輸入驗證機制
Storage Security MinIO object key 控制與 cleanup 策略
Auth Security Phase 0.8b 改寫API key + (保留) OAuth client for promote
API Key Management Phase 0.8b 新增CONVERTER_API_KEY rotation / 部署 / 外洩處理
Rate Limiting 雙層 rate limiter 設計
Logging 結構化 log 與敏感資料保護
Phase 2 候補方案清單 已知待補強的設計

Trust Boundary重要 design risk

user_id 為 multipart field受信任 visionA-backend 帶來

設計

POST /api/v1/jobsuser_id 從 multipart form field 傳入,不是從 token claim derive。Converter 完全信任 visionA-backend 端把對的 user_id 傳進來。

Phase 0.8b 後:

visionA-backend                Converter
     │                             │
     ├── 帶 CONVERTER_API_KEY ──────│  (pre-shared、雙端對齊)
     │                             │
     ├── POST /api/v1/jobs ────────→│  Form-Data:
     │     Authorization:           │   user_id: "alice"  ← visionA 端決定
     │       Bearer <CONVERTER_     │   model: <file>
     │       API_KEY>               │   ...
     │                             │
     │                             │  Converter 端:
     │                             │   - constant-time compare API keyOK
     │                             │   - 信任 user_id 是「真正提交的 user」
     │                             │   - 不再驗證 user_id 與 caller 的關係

Trust assumptionPhase 1 + 0.8b

visionA-backend 端:

  1. 程式碼安全 — 無 XSS / SSRF / RCE 漏洞user_id 來源可信
  2. infra 安全 — network ACL、IP allow-list、TLS 確保只有 visionA-backend 能呼叫此 API
  3. credential 管理CONVERTER_API_KEY 不外洩、不放 git、不寫 logPhase 0.8b 改:原為 client_secret
  4. audit log 健全 — visionA 端能追溯「哪個真實用戶觸發了哪次轉檔」

Risk被接受

visionA-backend 一旦被 compromiseattacker 可用同一個合法 CONVERTER_API_KEYPhase 0.8b 後Phase 1 為 OAuth 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 上
拿任意 NEFPhase 0.8b 新增 /result API key 模式下 /result 沒 per-job authorization、attacker 可拿任意 jobID 的 NEF

Phase 1 / 0.8b 決策2026-04-25 + 2026-05-16 使用者裁決)

接受此風險。 理由:

  1. visionA-backend 是內部受控系統(非 Internet-facingcompromise 機率低
  2. Phase 1 重點是把核心 pipeline 跑通,安全強化排在 Phase 2
  3. Phase 0.8b2026-05-16改 API key 後trust model 沒有比 OAuth 更不安全也沒有更安全
    • OAuth 模型client_credentials 一旦外洩attacker 也能冒充 visionA 取 token + 建 job
    • API key 模型API key 一旦外洩attacker 直接打 API、相同攻擊面
    • 唯一差別OAuth 有 token expiry短週期API key 是長期 secret更需要 rotation 流程)
  4. 不引入 HMAC-signed user_id / OBO
    • HMAC 仍是 symmetric secret被同樣的 compromise 場景突破
    • OBO 需要 MC 實作 token exchange、與 ADR-015 「砍跨團隊依賴」精神相反
    • 未來真有 multi-caller 需求再回頭加

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 天 TTLfail-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.jsSec M3
Unicode RTL override 嚴格白名單拒絕非 ASCII 同上
Glob / shell metachar in user_id / version 嚴格白名單拒絕 `*?[];& $` 等
ref_image OOM (100 × 500MB) per-file 10MB 上限 createJob.integration.test.jsSec 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'

  • 不對 → 不掛 multerPOST /api/v1/jobs 直接回 500 misconfiguration
  • 不會吃 multipart body避免 misconfig 也消耗 500MB 記憶體
  • GET / DELETE / download-tokens 不依賴 storage backend仍正常掛

Auth Security

Phase 0.8b:分兩條軸

流向 Auth mechanism Phase 0.8b 狀態
對外 API visionA → converter Pre-shared API keyCONVERTER_API_KEY 新(取代 OAuth
Promote converter → FAA OAuth client_credentials既有 保留

1. 對外 APIAPI key middlewarePhase 0.8b 新)

設計

  • HeaderAuthorization: Bearer <CONVERTER_API_KEY>(重用 Bearer 格式、client 不需改)
  • 比對crypto.timingSafeEqual constant-time compare
  • 長度64 hex charsopenssl rand -hex 32128 bits 安全強度、遠超 NIST 推薦的 80 bits
  • req.auth 設定:通過後 req.auth.clientId = 'visionA-service'(固定值、給 rate limiter / log 用)
  • 失敗行為401 invalid_token + 主動 socket.destroy()(沿用 OAuth M2 行為)
  • Fail-fastenv 未設定 → 503 拒絕所有 request不 silently allow

為什麼安全

  1. Constant-time comparecrypto.timingSafeEqual 避免 timing attack即使 attacker 用 differential analysis 也無法推斷 key 內容)
  2. 長度檢查在 timingSafeEqual 之前:避免 throw RangeError但長度本身公開key 長度為固定值)
  3. 64 hex chars = 256 bits 隨機(openssl rand -hex 32brute force 不可行
  4. 不 log key 內容任何方向expected / received 都不 log

失敗情境總表

詳見 auth.md §1.4。

2. PromoteOAuth client_credentials保留

Converter 仍以自己的身分取 files:upload.write token、PUT 結果檔到 FAA。

既有設計(不變)

  • JWT Algorithm Pin(既有 Sec m3auth/oauthClient.js 解析 MC 回應時不需 verify JWT取回後直接帶到 FAAFAA 端負責 verify、與 converter 無關
  • Token cacheper-scopedistance to expiresAt > refreshSkewMs預設 60s算 valid
  • In-flight dedup:同 scope 並發只發一次 token request
  • AbortController timeout:預設 10s
  • 錯誤分類OAuthClientError4xx不重試/ OAuthServerError5xx可重試/ OAuthTimeoutError(網路 / timeout可重試

401 處理

FAA PUT 回 401 → oauthClient.invalidate(scope) + retry 1 次;仍 401 → 503 auth_service_unavailable

3. 砍除Phase 0.8b

移除 原因
src/auth/middleware.jsOAuth resource server visionA → converter 不再驗 JWT
src/auth/jwks.js 不需要 JWKS cache
MEMBER_CENTER_ISSUER / JWKS_URL env 不驗 iss / 不取 JWKS
KNERON_CONVERTER_AUDIENCE env 不驗 aud
JWKS_CACHE_MAX_AGE_MS / JWKS_COOLDOWN_MS / JWT_CLOCK_TOLERANCE_SEC env 沒有 JWKS / JWT 了
ALLOWED_JWT_ALGS 演算法 pin 沒有 JWT 驗證了FAA 端有自己的演算法 pin

4. 保留promote 仍需)

保留 用途
src/auth/oauthClient.js converter → FAA OAuth client
MEMBER_CENTER_TOKEN_URL env token endpoint
KNERON_CONVERTER_CLIENT_ID / _CLIENT_SECRET env converter 作為 OAuth client 的身分
FILE_ACCESS_AGENT_AUDIENCE env FAA 的 audience取 token 時用)
OAUTH_TOKEN_REFRESH_SKEW_MS / OAUTH_TOKEN_TIMEOUT_MS env token cache 行為

API Key Management

1. 產生

openssl rand -hex 32
# 輸出64 個 hex chars
# 範例a3f9b2c1d8e7f6a5b4c3d2e1f0987654321fedcba9876543210abcdef1234567

2. 部署位置

環境 位置
dev apps/task-scheduler/.envgitignored
stage docker-compose env / k8s secret
prod docker secret / k8s secret / cloud secrets manager

3. 雙端對齊

  • visionA .env.stageVISIONA_CONVERTER_API_KEY=<same string>
  • converter .envCONVERTER_API_KEY=<same string>
  • 兩端必須完全相同字串

4. 每環境獨立

dev / stage / prod 各自 openssl rand -hex 32,絕不共用。

5. Rotation 流程

  1. 雙端各自準備 stop deployment或允許短暫 401 期)
  2. openssl rand -hex 32 產新 key
  3. 更新雙端 .env
  4. converter 先 redeploy接受新 key
  5. visionA 後 redeploy用新 key call
  6. 驗證:curl -i -H "Authorization: Bearer <NEW_KEY>" https://converter.../api/v1/jobs?user_id=test&limit=1

極小停機做法Phase 0.8b 不實作):暫時讓 converter 接受新舊兩把 keymiddleware 拓展成 array compare、visionA 切到新 key、再砍舊 key。

6. 外洩處理

  • 立即 rotate 雙端 key
  • 檢視 audit log在 rotation 前是否有可疑請求(用 request_id + user_id 追蹤)
  • 若有 anomalous activity同 client_id 短期內 100+ 不同 user_id通報 + forensics
  • Post-mortem分析洩漏來源git historyCI logslack並補強

7. 絕不做的事

  • 絕不進 git.gitignore 已 exclude .env、verify 一次)
  • 絕不寫進 Slack / email / 對話記錄
  • 絕不印 logmiddleware 內 log 用 api_key_lengthapi_key_set: true boolean、不印 key 本身)
  • 絕不在 commit message / PR description 中引用具體值

8. Sec C1 暫緩既有風險、Phase 0.8b 仍適用)

.env 一度被 commit 進 git history。Phase 0.8b 新加的 CONVERTER_API_KEY 必須極度注意不要重蹈覆轍。

Phase 1 ready 後會做一次 git history rewrite + 強制 rotate 所有 secret含 CONVERTER_API_KEY / 既有的 OAuth client_secret / MinIO secret


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 格式,必含:

  • timestampISO 8601
  • levelINFO / WARN / ERROR
  • servicetask-scheduler
  • request_id(貫穿請求生命週期)
  • actiondomain.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 OBO token / Token Exchange(解決 Trust BoundaryPhase 0.8b 已決策不做 HMAC 中繼方案 MEDIUM Phase 2 — auth 強化前提MC 實作 Token Exchange RFC 8693
2 Git history rewrite(清掉 .env 洩漏,含 Phase 0.8b 新加的 CONVERTER_API_KEY HIGH Phase 1 ready 收尾
3 API key automatic rotation(整合 secrets manager / Vault MEDIUM Phase 2 — infra
4 API key 並存模式(讓 middleware 同時接受新舊兩把 key支援極小停機 rotation LOW Phase 2 — auth 細修
5 MULTIPART_MODEL_MAX_BYTES env 串接 DONE Phase 1 T10
6 MAX_CONCURRENT_UPLOADS semaphore DONE Phase 1 T10
7 Stream storage 評估(取代 memoryStorage MEDIUM Phase 2 — infra
8 Rate limiter + bandwidth quota + concurrent stream cap Redis store(多 instance 部署前提) HIGH2026-05-17 升級Phase B 後 /result 開放、attacker blast radius 放大、多 instance 部署前必補) Phase 2 — infra前提Phase B 後若有 multi-instance 需求)
9 Audit anomaly detectionuser_id pattern 異常告警) LOW Phase 2 — observability
10 Filename Unicode normalization(極端 unicode bypass LOW Phase 2 — security 細修
11 Metadata prototype pollution 防護(白名單 keys LOW Phase 2 — security 細修
12 /result per-job authorizationAPI key 模式下 attacker 可拿任意 jobID MEDIUMA.7 follow-up §4 升級) Phase 2 — auth考慮 client_id 隔離;前提是 #13 完成per-caller credential
13 加回 OAuth resource server 並存模式 / 多 caller credential(多 caller 場景) MEDIUMA.7 follow-up §4 升級) Phase 2 — 真有第二個 caller 時;#12 的前置工作
14 /result 404 vs 410 區分的 jobID enumeration risk LOWmarginal、待 #12 完成後可一併處理) Phase 2 — auth 細修;#12 補後可考慮統一為 404
15 /result per-job auth 啟用時、4xx 統一回 404不揭露 lifecycle2026-05-17 Security review §1 Q5 新增) LOW → 隨 #12 同步升級為 #12 的子任務 Phase 2 — auth當 #12 完成時、attacker 拿到的 key 不再給 full read access、區分 404/410 才變成真正的 leak、屆時應統一回 404
16 MinIO socketTimeout / connectTimeout 對齊 RESULT_STREAM_TIMEOUT_MS2026-05-17 Security review §1 Q1 / s2 新增) LOW Phase 2 — infra確認 MinIO SDK timeout 設定、對齊 5 min stream timeout建議 socketTimeout = STREAM_TIMEOUT - 30s、預留 server-side teardown 時間);避免 MinIO 端 timeout 在 response timeout 前觸發、attacker 看到 stream_error 而非 stream_timeout

變更歷史

日期 變更 觸發
2026-04-25 初版 T5 Reviewer + Security Audit 修復
2026-05-16 Phase 0.8b 重寫auth 從 OAuth 改 API key新增 API Key Management 章節Trust boundary 風險模型與 Phase 1 一致(只是 secret 形式改變Phase 2 候補清單更新OBO 從 HIGH 降 MEDIUM、新增 API key rotation 相關項) ADR-010 + ADR-011
2026-05-17 Phase B 啟動前 streaming/range design review候補 #12 / #13 升 MEDIUM新增候補 #14404 vs 410 區分的 jobID enumeration trade-off、當前 trust model 下 marginal risk、文件化保留 TODO-v2 §4.1 規格);/result 設計補充章節rate limit / Range / audit log / source_filename詳見 api/api-result.md §9-§14 Security Auditor A.7 follow-up §4 + Phase B 啟動前 design review
2026-05-17 Phase B Security design review 第二輪採納4 Major + 3 Minor候補 #8 從 MEDIUM 升 HIGH(多 instance 部署前必補 Redis store、涵蓋 rate limit + bandwidth quota + concurrent cap 三軸);新增候補 #15per-job auth 啟用時 4xx 統一回 404、隨 #12 同步)+ 候補 #16MinIO socketTimeout 對齊 RESULT_STREAM_TIMEOUT_MS/result 設計強化rate limit 從 60 req/min single tier 改為 two-tier5 req/10s burst + 20 req/min sustained+ bandwidth quota1 GB/hr + 6 GB/24hr新增 stream response timeout 5 min + concurrent stream cap 10 per-instanceRange header 處理三件事Accept-Ranges: none + silently ignore + log range_attemptedaudit log 從 8 event 擴到 12 event + 強制 A.7 五欄 + /result 特有四欄filename defense-in-depthquote-escape + RFC 5987 + buildFilename assertionBackend acceptance criteria 從 B1-B9 擴充到 AC-1 到 AC-12 + 6 個新 integration test詳見 api/api-result.md §9 / §10 / §11 / §13.4a / §15 / §14 Security Auditor Phase B streaming/range design review §1 Q1-Q6 + §2 修正 + §3 acceptance criteria