供內部或第三方安全審計使用的逐項檢查清單。所有項目均可回溯至具體程式碼。
- AES-256-GCM 金鑰長度為 256 bit(
dr.js,aead.js) - IV 長度為 96 bit(12 bytes)且每次加密獨立隨機產生(
dr.js:450,aead.js:115-120) - 同一金鑰下 IV 不重複(依賴 CSPRNG 隨機性)
- GCM authentication tag 正確驗證(WebCrypto API 自動處理)
- 解密失敗時不洩漏明文(
dr.js安全政策第 1 條)
- HKDF-SHA256 正確使用 salt 和 info(
dr.js:67-81) - Root Key 衍生:
hkdfBytes(rk||dhOut, 'dr-rk', 'root', 64)(dr.js:84) - Chain Key 衍生:
hkdfBytes(ck, 'dr-ck', 'chain', 64)(dr.js:88) - X3DH SK 衍生:
hkdfBytes(DH1||DH2||DH3||DH4, 'x3dh-salt', 'x3dh-root', 64) - Info tag 白名單限制在
aead.js:33-42中的 8 個合法值 - 不同用途的金鑰使用不同 info tag(防止 domain separation failure)
- 參數:m=64MiB, t=3, p=1(
kdf.js) - Salt 為 16 bytes random(
kdf.js) - 輸出用於 AES-256 金鑰
- 低端裝置效能可接受
- Ed25519 金鑰產生使用
sodium.crypto_sign_keypair()(libsodium-wrappers-sumo,經審計)(prekeys.js) - X25519 金鑰產生使用
sodium.crypto_box_keypair()(libsodium-wrappers-sumo,經審計)(prekeys.js) - Ed25519→X25519 轉換使用
sodium.crypto_sign_ed25519_pk_to_curve25519()/crypto_sign_ed25519_sk_to_curve25519()(libsodium 內建,取代自訂 field arithmetic) - X25519 clamping 由 libsodium 內部處理(
自訂)ed2curve.js手刻 clamping 已移除 - SPK 簽章驗證在 X3DH initiator 端執行
- TOFU:首次 X3DH 儲存 peer Identity Key,後續偵測 key 變更(
contact-secrets.js:checkAndStorePeerIk) - Safety Number:雙方可透過 60 位數字指紋帶外驗證身份(
safety-number.js:computeSafetyNumber) - Identity Key 變更時觸發
dr:identity-key-changed事件(dr-session.js)
- 4 個 DH 操作正確執行(DH1-DH4)(
dr.js:158-238) - DH 結果正確串接(
dh1 || dh2 || dh3 || dh4) - SK 正確衍生(HKDF with 'x3dh-salt', 'x3dh-root')
- OPK 正確消耗(使用後從 bundle 移除)
- Ephemeral key 在 X3DH 完成後丟棄
- DH ratchet 在收到新 ephemeral key 時正確觸發(
dr.js:504+) -
⚠️ Send-side ratchet 停用 — 確認此為設計決策而非 bug(dr.js:357-364) - Root Key 和 Chain Key 分離正確
- Chain Key 在每次訊息後正確推進
- Send counter (Ns) 每次發送後遞增
- Receive counter (Nr) 每次接收後遞增
- Previous counter (PN) 在 DH ratchet 時正確記錄
- Counter 包含在 AAD 中
- Skipped keys 正確儲存(
dr.js:128-156) - 每 chain 最多 100 keys
- 使用後立即刪除(take-and-delete)
- 總量上限是否合理(防止記憶體攻擊)
- AAD 格式:
v:{version};d:{deviceId};c:{counter}(dr.js:44-65) - Version、deviceId、counter 正確序列化
- AAD 在加密和解密時一致
- 篡改任一欄位導致 GCM 驗證失敗
- 32 bytes random(
crypto.getRandomValues) - 僅存於客戶端記憶體(
store.js:_MK_RAW) - Wrapped blob 格式正確(
kdf.js) - 密碼變更時正確重新 wrap
- IK/SPK/OPK 正確產生
- 私鑰使用 MK 加密儲存於伺服器
- 公鑰正確發佈至 prekey bundle
- OPK 補充邏輯正確(登入時檢查剩餘量)
- 登出時清除記憶體中金鑰
-
⚠️ JavaScript GC 不保證立即清除 - sessionStorage 在 tab 關閉時清除
-
localStorage— ✅ 已確認/修復:contactSecrets-v2登出時是否清除secureLogout()呼叫localStorage.clear();app-ui.jsonLogout()修正為清除所有非 SIM localStorage key
-
JWT 驗證正確— ✅ 已遷移至jose套件(panva/jose,經安全審計)-
HMAC-SHA256 簽章驗證— ✅jose.jwtVerify內部使用crypto.subtle.verify做 constant-time 比較 -
過期時間檢查— ✅ jose 自動驗證exp,配置clockTolerance: 5(HS256)/30(RS256) -
Header 格式驗證— ✅ 嚴格algorithms白名單(['HS256']/['RS256'])防止 alg confusion -
RS256 voucher token exp 驗證— ✅ 由 jose 自動處理(原verifyJwtRS256完全未驗證exp,已修復)
-
- WebSocket 二次認證(
account-ws.js:232+) - Stale session 拒絕(
account-ws.js:571-591) - NFC SDM CMAC 驗證(
worker.js)
- 訊息發送需 conversation ACL 驗證
- Prekey bundle 存取控制
- Media presigned URL 授權
- Vault 存取限於帳號所有者
- WebSocket 訊息大小限制(Signal: 16KB, SDP: 64KB)
- Ephemeral buffer 限制(50 messages, 5 min TTL)
- SQL injection 防護 — 全部 358 處使用 parameterized queries(
?N) - 輸入正規化:account_digest(64 hex)、conversation_id(8-128 alphanum)等
- 訊息 counter 嚴格遞增(server-side
counter <= MAX(previous)檢查) -
Account token 明文儲存(應 hash 後儲存)— ✅ Phase 1 已修復:新增account_token_hash欄位,驗證時優先比對 hash、舊帳號 fallback 明文並自動回填 hash(worker.js、0012_add_account_token_hash.sql) -
Rate limiting 非分散式— ✅ 已修復:新增RateLimiterDurable Object,全域 IP 限流 + 認證/prekey/訊息/pairing code 分層限流 -
Error messages 洩漏狀態("CounterTooLow" 含 maxCounter)— ✅ 已修復:移除lastCtr、maxCounter、details等內部狀態欄位(worker.js) -
Debug endpoints 未停用(— ✅ 已修復:透過/auth/sdm/debug-kit,/auth/opaque/debug)ENABLE_DEBUG_ENDPOINTS環境變數控制,生產環境預設false,僅 UAT 啟用(wrangler.toml、worker.js) - 無 CSRF token 驗證 — ⬇️ 降級為 Low:系統不使用 cookie 認證(token 透過
x-account-tokenheader 傳送),傳統 CSRF 攻擊不成立,可選加Originheader 驗證作為縱深防禦
- 使用
@cloudflare/opaque-ts@0.7.5with SRI - Registration 流程正確(init → finish)
- Login 流程正確(init → finish → session key)
- 密碼不經網路傳輸
- Per-chunk 獨立 HKDF salt + random IV
- Key = HKDF(MK, salt, 'media/chunk-v1')
-
無 AAD(chunk index 未綁定)— ✅ 已修復:所有 AES-GCM 操作加入 info tag 作為additionalData,v2 格式向下相容 v1 legacy - Presigned URL 存取控制
- Manifest 加密:HKDF(MK, salt, 'media/manifest-v1')
- Manifest 包含 chunk metadata(iv, salt, size)
- Manifest 版本 v3
-
⚠️ 無獨立簽章
- CMK = HKDF(conv_token, 'call-master-key:{callId}:{epoch}', random_salt)
- cmkProof = HMAC(CMK, '{callId}:{epoch}')
- 方向性金鑰(caller/callee 分離)
- Per-media-type 金鑰(audio/video 分離)
- Counter-based nonce(防重放)
- InsertableStreams 正確設定
-
Epoch 輪換機制待確認— ✅ 已修復:caller 每rotateIntervalMs(預設 1 分鐘)自動遞增 epoch(key-manager.js),透過call-rekey信號同步 peer(signaling.js) -
InsertableStreams 不支援時的 fallback 行為— ✅ 已修復:本地或對端不支援時failCall拒絕通話,不允許靜默降級為未加密(media-session.js)
- ICE candidate 類型(是否限制為 relay only)
- TURN 認證
- DTLS/SRTP 底層加密
-
⚠️ P2P 連線可能暴露 IP
- 訊息內容加密 ✓
- 媒體內容加密 ✓
-
⚠️ 社交圖譜可見(conversation_acl明文) -
⚠️ 通訊時間可見(timestamp 明文) -
⚠️ 訊息大小可推知(無 padding) -
⚠️ 在線狀態可推知(WebSocket/presence)
- 一次性 token 正確消耗(atomic)
- Guest 臨時身份(不關聯常駐帳號)
- Session 到期自動銷毀
- 暫存訊息不寫入 D1
-
⚠️ 暫存訊息在 DO 記憶體中(最多 50 則, 5 分鐘 TTL)
- SRI 驗證所有 CDN 載入(OPAQUE、Argon2id)
-
CSP headers 設定待確認— ✅ Phase 1 已修復:_headers新增 CSP(白名單 CDN +'unsafe-inline')、X-Frame-Options: DENY、X-Content-Type-Options: nosniff、Referrer-Policy、Permissions-Policy -
CORS 設定待確認— ✅ 已修復:Pages Function 改用CORS_ALLOWED_ORIGINS白名單([[path]].ts);Data Worker 啟用CORS_ORIGINS環境變數(wrangler.toml);明確列舉 allow-headers 取代* -
HTTP API rate limiting 待確認— ✅ 已修復:與 M-1 共同處理,RateLimiterDO 全域 IP 限流覆蓋所有端點 - TLS 1.2+ 強制(Cloudflare 處理)
-
Debug 日誌是否在生產環境停用— ✅ 已修復(強化):debug-flags.js所有開關預設false(不再依賴__PRODUCTION__判斷),dr-session.js移除金鑰狀態/計數器敏感日誌,移除navigator.webdriver自動啟用,worker.jsvault 日誌僅安全欄位,entry-fetch.jsFETCH_LOG_ENABLED 預設 false
- GitHub Actions secrets 不透過
echo傳入指令(deploy.yml:94-105)— 已改用 env vars + printf - SSH 使用 key-based 認證而非密碼(
deploy.yml:177-179) - Debug page 不在生產環境可存取(
[[path]].ts:108-115) - Debug flags 在生產環境停用(
debug-flags.js) - Source maps 在生產環境停用(
build.mjs:53) -
build-manifest.json不公開可存取(build.mjs:346) - CSP header 設定限制 script-src(
_headers) - HSTS header 啟用(
_headers) -
CORS 設定不使用— ✅ 已修復:白名單比對取代 origin reflection(allow-origin: *[[path]].ts、wrangler.toml) - API proxy 有 rate limiting(
[[path]].ts:156-217) - UAT 與 Production 使用不同 API token(
setup-environments.sh) - Wipe script 需要明確確認(
wipe-all.sh)
- D1 訊息 TTL/清理機制
- R2 媒體 TTL/清理機制
- localStorage 登出時清除
- sessionStorage 依賴瀏覽器清理
-
⚠️ Cloudflare 日誌是否記錄 API request body