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>
25 KiB
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 key(ADR-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/jobs 的 user_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 key(OK)
│ │ - 信任 user_id 是「真正提交的 user」
│ │ - 不再驗證 user_id 與 caller 的關係
Trust assumption(Phase 1 + 0.8b)
visionA-backend 端:
- 程式碼安全 — 無 XSS / SSRF / RCE 漏洞,user_id 來源可信
- infra 安全 — network ACL、IP allow-list、TLS 確保只有 visionA-backend 能呼叫此 API
- credential 管理 —
CONVERTER_API_KEY不外洩、不放 git、不寫 log(Phase 0.8b 改:原為client_secret) - audit log 健全 — visionA 端能追溯「哪個真實用戶觸發了哪次轉檔」
Risk(被接受)
visionA-backend 一旦被 compromise,attacker 可用同一個合法 CONVERTER_API_KEY(Phase 0.8b 後;Phase 1 為 OAuth client_credentials):
| 攻擊面 | 影響 |
|---|---|
為任意 user_id 建 job |
冒充任何 user(user_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 上 |
拿任意 NEF(Phase 0.8b 新增 /result) |
API key 模式下 /result 沒 per-job authorization、attacker 可拿任意 jobID 的 NEF |
Phase 1 / 0.8b 決策(2026-04-25 + 2026-05-16 使用者裁決)
接受此風險。 理由:
- visionA-backend 是內部受控系統(非 Internet-facing),compromise 機率低
- Phase 1 重點是把核心 pipeline 跑通,安全強化排在 Phase 2
- Phase 0.8b(2026-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 流程)
- 不引入 HMAC-signed user_id / OBO:
- HMAC 仍是 symmetric secret,被同樣的 compromise 場景突破
- OBO 需要 MC 實作 token exchange、與 ADR-015 「砍跨團隊依賴」精神相反
- 未來真有 multi-caller 需求再回頭加
Mitigations(Phase 1 已採用)
| Mitigation | 說明 |
|---|---|
| per-client_id rate limiter(300 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 候補方案
方案 1:HMAC-signed user_id(推薦短期)
visionA-backend 用共享 secret HMAC 簽 user_id,Converter 驗簽:
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 也能簽)。
方案 2:OBO Token / Token Exchange(業界標準,推薦中期)
visionA-backend 為每個 user 取 user-context token(例如 OBO flow / Token Exchange RFC 8693),Converter 從 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 chain(actor + subject 雙重身份)
缺點:
- visionA / Member Center 都需要實作 Token Exchange 流程
- 性能:每次 POST /jobs 多一次 Token Exchange round-trip(可 cache 緩解)
方案 3:Audit 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._-]+$ regex(Sec M1 強化)+ 額外 .. 拒絕 |
| version 嚴格白名單 | src/routes/v1/validators/createJob.js |
^[A-Za-z0-9._-]+$ regex(Sec 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 | 預設 500MB(multer LIMIT_FILE_SIZE → 413) |
| ref_image per-file 大小 | validateCreateJobRequest |
10MB(Sec 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 失敗 → 直接回 502,Redis 完全乾淨
- Lua conflict / throw → cleanup MinIO(fire-and-forget,靠 7d lifecycle 兜底)
- enqueue 失敗 → 補償 release Redis + cleanup MinIO(Sec 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 M5:mount-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
Phase 0.8b:分兩條軸
| 軸 | 流向 | Auth mechanism | Phase 0.8b 狀態 |
|---|---|---|---|
| 對外 API | visionA → converter | Pre-shared API key(CONVERTER_API_KEY) |
新(取代 OAuth) |
| Promote | converter → FAA | OAuth client_credentials(既有) | 保留 |
1. 對外 API:API key middleware(Phase 0.8b 新)
設計
- Header:
Authorization: Bearer <CONVERTER_API_KEY>(重用 Bearer 格式、client 不需改) - 比對:
crypto.timingSafeEqualconstant-time compare - 長度:64 hex chars(
openssl rand -hex 32產,128 bits 安全強度、遠超 NIST 推薦的 80 bits) - req.auth 設定:通過後
req.auth.clientId = 'visionA-service'(固定值、給 rate limiter / log 用) - 失敗行為:401
invalid_token+ 主動socket.destroy()(沿用 OAuth M2 行為) - Fail-fast:env 未設定 → 503 拒絕所有 request(不 silently allow)
為什麼安全
- Constant-time compare:
crypto.timingSafeEqual避免 timing attack(即使 attacker 用 differential analysis 也無法推斷 key 內容) - 長度檢查在 timingSafeEqual 之前:避免 throw
RangeError,但長度本身公開(key 長度為固定值) - 64 hex chars = 256 bits 隨機(
openssl rand -hex 32),brute force 不可行 - 不 log key 內容(任何方向:expected / received 都不 log)
失敗情境總表
詳見 auth.md §1.4。
2. Promote:OAuth client_credentials(保留)
Converter 仍以自己的身分取 files:upload.write token、PUT 結果檔到 FAA。
既有設計(不變)
- JWT Algorithm Pin(既有 Sec m3):在
auth/oauthClient.js解析 MC 回應時不需 verify JWT(取回後直接帶到 FAA);FAA 端負責 verify、與 converter 無關 - Token cache:per-scope,distance to expiresAt > refreshSkewMs(預設 60s)算 valid
- In-flight dedup:同 scope 並發只發一次 token request
- AbortController timeout:預設 10s
- 錯誤分類:
OAuthClientError(4xx,不重試)/OAuthServerError(5xx,可重試)/OAuthTimeoutError(網路 / timeout,可重試)
401 處理
FAA PUT 回 401 → oauthClient.invalidate(scope) + retry 1 次;仍 401 → 503 auth_service_unavailable。
3. 砍除(Phase 0.8b)
| 移除 | 原因 |
|---|---|
src/auth/middleware.js(OAuth 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/.env(gitignored) |
| stage | docker-compose env / k8s secret |
| prod | docker secret / k8s secret / cloud secrets manager |
3. 雙端對齊
- visionA
.env.stage:VISIONA_CONVERTER_API_KEY=<same string> - converter
.env:CONVERTER_API_KEY=<same string> - 兩端必須完全相同字串
4. 每環境獨立
dev / stage / prod 各自 openssl rand -hex 32,絕不共用。
5. Rotation 流程
- 雙端各自準備 stop deployment(或允許短暫 401 期)
openssl rand -hex 32產新 key- 更新雙端
.env - converter 先 redeploy(接受新 key)
- visionA 後 redeploy(用新 key call)
- 驗證:
curl -i -H "Authorization: Bearer <NEW_KEY>" https://converter.../api/v1/jobs?user_id=test&limit=1
極小停機做法(Phase 0.8b 不實作):暫時讓 converter 接受新舊兩把 key(middleware 拓展成 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 history?CI log?slack?)並補強
7. 絕不做的事
- ❌ 絕不進 git(
.gitignore已 exclude.env、verify 一次) - ❌ 絕不寫進 Slack / email / 對話記錄
- ❌ 絕不印 log(middleware 內 log 用
api_key_length或api_key_set: trueboolean、不印 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 格式,必含:
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)— 通常不視為敏感
- IP(log 仍記,但 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 secret,prod 用全新生成的 secret
Phase 2 候補方案清單
已知待補強(依優先級)
| # | 項目 | 優先級 | 預期任務 |
|---|---|---|---|
| 1 | OBO token / Token Exchange(解決 Trust Boundary,Phase 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 部署前提) | HIGH(2026-05-17 升級:Phase B 後 /result 開放、attacker blast radius 放大、多 instance 部署前必補) |
Phase 2 — infra(前提:Phase B 後若有 multi-instance 需求) |
| 9 | Audit anomaly detection(user_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 authorization(API key 模式下 attacker 可拿任意 jobID) |
MEDIUM(A.7 follow-up §4 升級) | Phase 2 — auth;考慮 client_id 隔離;前提是 #13 完成(per-caller credential) |
| 13 | 加回 OAuth resource server 並存模式 / 多 caller credential(多 caller 場景) | MEDIUM(A.7 follow-up §4 升級) | Phase 2 — 真有第二個 caller 時;#12 的前置工作 |
| 14 | /result 404 vs 410 區分的 jobID enumeration risk |
LOW(marginal、待 #12 完成後可一併處理) | Phase 2 — auth 細修;#12 補後可考慮統一為 404 |
| 15 | /result per-job auth 啟用時、4xx 統一回 404(不揭露 lifecycle)(2026-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_MS(2026-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;新增候補 #14(404 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 三軸);新增候補 #15(per-job auth 啟用時 4xx 統一回 404、隨 #12 同步)+ 候補 #16(MinIO socketTimeout 對齊 RESULT_STREAM_TIMEOUT_MS);/result 設計強化:rate limit 從 60 req/min single tier 改為 two-tier(5 req/10s burst + 20 req/min sustained)+ bandwidth quota(1 GB/hr + 6 GB/24hr);新增 stream response timeout 5 min + concurrent stream cap 10 per-instance;Range header 處理三件事(Accept-Ranges: none + silently ignore + log range_attempted);audit log 從 8 event 擴到 12 event + 強制 A.7 五欄 + /result 特有四欄;filename defense-in-depth(quote-escape + RFC 5987 + buildFilename assertion);Backend 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 |