資安規範¶
本檔角色:不可信 GLB 信任模型與 sanitize、簽署 / 驗章、事件收件驗證 / 全網重算索引、CSP、依賴管控、反作弊索引、漏洞通報、CVE 流程。 對應模組(實作):程式架構/security.md / 程式架構/key-manager.md。 版權 / 反複製 / DMCA 見 版權.md。
1. 防禦層次¶
| 層 | 範圍 |
|---|---|
| 客戶端 sanitize | 上傳前 GLB 清洗 + 所有不可信 GLB 解析隔離(§2) |
| 簽章 / 驗章 | 所有 ledger 事件 + P2P 訊息(§3) |
| 事件收件驗證 / canonical fold | permissionless 寫入的對抗層:比賽事實驗證後由 fold 計算經濟結果、仲裁結果驗算、上傳宣告重算(§12、程式架構/ledger-admission.md §1、程式架構/moderation.md §5.6、程式架構/anti-piracy.md §5.1) |
| CSP / SRI | 防 XSS / 第三方 JS 注入(§4–§5) |
| 依賴管控 | 嚴格鎖版本 + CI 阻擋(§6) |
| Pinning 安全 Header | 自架 pinning 的 HTTP 硬化(§7) |
| 反作弊 | 賽內 + 經濟層(§12) |
| CVE 流程 | 對外通報 + 修補時程(§10) |
2. UGC Mesh Sanitize Worker¶
2.1 流程¶
玩家上傳 GLB
↓
主執行緒呼叫 Sanitize Worker (Web Worker)
↓
Worker 內:
- parse GLB
- JSON chunk > 16 MiB 在 TextDecoder / JSON.parse 前拒絕
- 主要 glTF collection、全文件 entries / depth 超限即拒絕
- 拒絕 embed external URL(避免上傳後抓外部資源)
- 拒絕惡意 extras(過大 / 過深巢狀 / 含可執行 payload)
- 拒絕無效 mesh(NaN / Inf / degenerate triangles)
- 絕對天花板:檔案大小 / triangle / AABB / texture / extras(引 canon 常數)
↓
返回 sanitized GLB 或拒收原因
2.2 限制常數¶
| 常數 | 值 |
|---|---|
SANITIZE_TIMEOUT_MS |
5000 |
SANITIZE_WORKER_VERSION |
'v1'(JSON / collection admission schema;live 後 bump 即升 protocol) |
SANITIZE_JSON_CHUNK_MAX_BYTES |
16,777,216(16 MiB;解碼前) |
SANITIZE_DOCUMENT_MAX_ENTRIES |
250,000 |
SANITIZE_DOCUMENT_MAX_DEPTH |
64 |
本表只列可由程式強制執行的 worker 限制。瀏覽器沒有可攜的 per-worker heap 硬上限;記憶體防線由解碼前輸入大小、文件/collection/幾何/extras 結構天花板,加上逾時即
terminate()組成。完整數值見 程式架構/security.md §1.2 + protocol.md §4。
2.3 嚴格隔離¶
Worker 跑在 isolated context:
- 無 DOM 存取
- 無 network 存取(不能呼叫 fetch)
- 輸入/結構天花板,逾時即終止 Worker
- 拒收後 GLB 丟棄,不寫 IPFS
2.4 不可信 GLB 解析邊界(信任模型)¶
Stage 1–3 與 sanitize 都跑在上傳者 client——對誠實 client 是品質閘,擋不住改裝 client 跳過檢核直接上鏈。網路面的防線 = 「解析隔離 + 本地拒用」:
- 所有不可信 GLB 的首次解析(上傳前清洗 / 收件驗證的相似度指紋與 PhysicsManifest 獨立重建 / 車庫預覽與進房首次載入)一律在有界 Worker 隔離與同一下載/配置上限內執行(CSP
worker-src已涵蓋 Sanitize / 指紋 / manifest admission Worker,程式架構/security.md §3)。 - 解析失敗 / 超時 / 超限 → 本地拒用該資產(fail-fast、不靜默 fallback,比照 材質表.md §11 loader 慣例);已上鏈事件不因此無效——拒用是本地行為、非共識拒收。
- 收件驗證需要完整 GLB 但內容尚未同步 → 暫緩排隊、有界重試;內容已取得後若解析、完整規則、manifest reconstruction 或 digest 比對失敗則 fail closed 拒收,不能把壞內容誤當暫時缺塊。
3. 簽章 / 驗章(Ed25519)¶
詳見 程式架構/security.md §2 + 程式架構/key-manager.md。
3.1 金鑰¶
| 參數 | 值 |
|---|---|
| 公鑰長度 | 32 bytes (ED25519_PUBLIC_KEY_BYTES) |
| 簽章長度 | 64 bytes (ED25519_SIGNATURE_BYTES) |
| 助記詞 | BIP39 12 / 24 詞 |
| PIN 加密 | 本地金鑰加密儲存(Argon2id + AES-GCM,程式架構/key-manager.md §4) |
3.2 簽章覆蓋範圍¶
| 物件 | 簽章 |
|---|---|
| Ledger event | ✅ BaseEvent.signature(必須;多簽事件以 signatures[] 取代,資料系統.md §3) |
P2P 訊息(含聊天 ChatMessage,程式架構/chat-system.md §1) |
✅ 訊息 + timestamp + nonce |
| MatchResult facts | ✅ original roster 嚴格多數完賽者多簽;事件無 payout/frontier,金額由 canonical fold 前態唯一計算(§12.1 + 程式架構/ledger-admission.md §1) |
| 回合共識錨 | ✅ canonical present roster 的嚴格多數多簽;每 signer 每回合最多一份 |
| 帳本檢查點 | ✅ 治理 signer set floor(2N/3)+1 多簽 |
| ConfigUpdateEvent | ✅ 治理 signer set floor(2N/3)+1 多簽(同帳本檢查點 signer set,流程/治理事件.md) |
| Loadout / 設定(本地) | ❌ 不需 |
3.3 防 Replay¶
P2P 訊息:
| 機制 | 值 |
|---|---|
| Timestamp tolerance | 30 秒(P2P_MESSAGE_TIMESTAMP_TOLERANCE_SEC) |
| Nonce 長度 | 16 bytes (P2P_MESSAGE_NONCE_BYTES) |
| Nonce set 硬上限 | 8,192 (P2P_NONCE_SET_MAX_ENTRIES);滿載時拒絕新訊息,不淘汰未過期 nonce |
Nonce 在 30 秒內不可重複(local set)。
3.4 SignedPayload 介面概念¶
所有 P2P 即時訊息共用 SignedPayload<T> 結構(payload / timestamp / nonce(16B) / signer / signature;timestamp 與 nonce 必在簽章涵蓋範圍內)。結構與簽章訊息(buildSignedMessage)定義以 程式架構/key-manager.md §9 為準。P2P 訊息驗章流程(本檔規範的不變式):
1. 驗 envelope 固定五欄、signer / timestamp / nonce / signature shape(nonce 必須恰 16 bytes)
2. 驗 timestamp 在 ±30s 內(否則 reject)
3. 驗 nonce 不在 local 30s nonce set(否則 reject)
4. 驗 signature 對應 signer 公鑰
5. 一般路徑全過 → nonce 加入有界 local set;容量滿 fail-closed
Gossip topic 路徑為避免有效簽章者以大量 unique nonce 在 rate limit 前灌表,順序固定為:raw bytes / topic / payload schema → signer 與簽章驗證(暫不 commit)→ per-signer/topic rate limit → nonce commit → handler。限流拒絕的 nonce 不得寫入 replay set。
Ledger 事件不走
SignedPayload——事件無nonce欄;簽章為BaseEvent.signature(多簽事件以signatures[]取代)。20 種永久事件的外層 Orbit entry 必須另帶 Admission v1workNonce:對包含事件、parents、clock、identity 與 Orbit signature 的 canonical BaseEntry 支付固定 18–22 bits PoW,並核對 canonical final bytes/CID。完全相同 final entry 由 CID 去重,同一 proof 不可跨另一份 entry 免費重用;但攻擊者可為不同 entry 重新付費,因此首播 timestamp ±30s、fold timeless 守門與 business idempotency 仍全部必要。驗證順序與 ingress 完整目錄見 程式架構/ledger-admission.md §1.1、程式架構/ledger-checkpoint.md §4.1 與 程式架構/ledger.md §6.1。
4. CSP(生產環境)¶
權威生成模型見 程式架構/security.md §3。交付 = production prerender
後逐份 HTML 生成 <meta>(主站 GitHub Pages 無法設自訂 HTTP header,部署資訊.md §2)。
script-src 固定含 'self'/'wasm-unsafe-eval',再加入該頁每個 inline script 的 SHA-256;
只有實際存在 inline handler 的頁才加入 'unsafe-hashes' 與 handler SHA-256,永不使用
'unsafe-inline'。其餘要點:worker-src 'self' blob:、connect-src 'self' wss: https:(玩家可
自選 signaling/pinning/gateway 節點)、object-src 'none'、base-uri 'self'、
form-action 'self'、upgrade-insecure-requests。meta CSP 不支援 frame-ancestors、GH Pages
無法送 X-Frame-Options → 主站無框架保護(風險接受:無 cookie/無跨源授權狀態,
clickjacking 面有限)。
5. SRI 工具¶
Subresource Integrity。本專案 runtime 原則上無第三方 script / CSS——依賴全走 npm lock + bundle(§6),且 CSP default-src 'self'(§4)本就擋外部載入。SRI 的角色:
- CI build 為 build 產物生成 SRI hash——防 hosting / CDN 層竄改產物(SRI 對同源資源同樣強制)。
- 若未來確需引入外部資源:SRI hash + CSP 白名單兩者同動(缺一不可)。
<script
src="/bundle/lib.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
工具見 程式架構/security.md §4。
6. 依賴管控¶
6.1 嚴格鎖版本¶
package-lock.json / pnpm-lock.yaml commit 進 repo;CI 比對 lock 一致。
6.2 CI 阻擋¶
| 觸發 | 阻擋 |
|---|---|
| 新增依賴 | 需 review(解釋必要性、檢查 license) |
| 升版依賴 | 自動跑安全測試 |
| Audit warning ≥ moderate | CI 阻擋 merge |
| 新依賴超過 N 個 transitive deps | 警告 |
6.3 White list¶
只允許 well-known 套件(Angular / Rapier / libp2p / OrbitDB 等核心,使用技術.md);任何「only-used-here」的小包先評估是否能不裝。
7. Pinning Server 安全 Header¶
| Header | 用途 |
|---|---|
Strict-Transport-Security |
強制 HTTPS |
X-Content-Type-Options: nosniff |
防 MIME 偽造 |
X-Frame-Options: DENY |
防 clickjacking |
Referrer-Policy: strict-origin-when-cross-origin |
跨源只送 origin、不洩完整 referer |
Permissions-Policy |
禁用不需要的 features |
8. 週邊服務容器安全基線¶
適用三顆公版 Template repo(部署資訊/open-4wd-pinning.md/部署資訊/open-4wd-signaling.md/部署資訊/open-4wd-turn.md)的自建執行映像與 k8s manifests。直接引用的上游官方 image(coturn/kubo/ipfs-cluster)不在此列——其內部硬化屬上游責任,但承載它們的 manifest 仍受本表約束。
| 項目 | 要求 |
|---|---|
| 執行身分 | 對外服務行程不得以 root 執行:Dockerfile 明示 USER;k8s securityContext 至少 runAsNonRoot: true +allowPrivilegeEscalation: false |
| 映像內容 | runtime 不得含建置/部署工具鏈(型別檢查器、linter、test runner、雲端 CLI)——雲端 CLI 尤甚,它是部署憑證的載體;僅在執行期真需轉譯時保留該轉譯器 |
| 埠與特權 | 監聽埠一律 >1024,不索取 CAP_NET_BIND_SERVICE |
| 例外 | 任一項因技術限制無法滿足(如 TURN relay 埠段需 hostNetwork),於該 repo README 寫明理由與補償措施;不得靜默略過 |
不在本基線內:resources requests/limits 與 readiness/liveness 探針。兩者的合理值隨部署規模差異極大,維持「公版範本省略、README 要求部署者於 overlay 自補」的既有作法。
發布 public image 的 repo(目前僅 pinning 推 GHCR)在首次發布前必須滿足本基線——映像一旦公開,使用者是直接 docker pull 執行,硬化責任不能下推給部署者。
9. 安全事件記錄(local-only)¶
- 客戶端以 app-scope、記憶體內 500 筆環形上限記錄安全事件(sanitize 拒收、驗章失敗、CSP violation;pin/session 可依同一介面接入)。重新整理即清空,不寫入持久儲存。
- 寫入時即依事件種類正面表列並去識別;不得保存 payload、signature、PeerId、未知欄位或 URL path/query/fragment。CSP URL 最多保留 origin/scheme。
- 不傳送到遠端,也不含自動 telemetry(保護玩家隱私)。
- 玩家可在設定頁手動匯出版本化 JSON;匯出只複製同一份已去識別 snapshot,不另收集環境資訊。
10. CVE 通報¶
10.1 Reporting¶
接受漏洞通報的 channel:
- GitHub Security Advisories(優先;私密通報)
- Email:
open4wd.service@gmail.com(主專案的安全通報信箱;DMCA 位址由各 provider 自建(DMCA 流程 §5.1),兩者不是共同信箱;PGP 金鑰上線前發布)
主 repo 根 SECURITY.md 為對外公告版(回應時限 =§10.2、90 天 disclosure=§10.3)。
10.2 Severity & Response¶
| 等級 | 回應時間 |
|---|---|
| Critical(可遠端執行 / 共識破壞) | 24 小時內 |
| High(可資料竊取 / 簽章繞過) | 7 天內 |
| Medium(DoS / 局部影響) | 30 天內 |
| Low(資訊洩漏 / 邊界) | 90 天內 |
10.3 Disclosure Window¶
- 預設 90 天 coordinated disclosure
- 嚴重漏洞可協商延長
11. 漏洞重現與 Bug Bounty 狀態¶
- Open4WD 目前不設 Bug Bounty,漏洞通報、重現協作與公開致謝均不代表金錢或其他獎勵承諾。
- 未修補漏洞的重現步驟、exploit、log 與附件必須留在 §10.1 的私密通報管道;不得以公開 issue 或 PR 提交。
- 修復完成或 coordinated disclosure 後,才可將最小化、去敏且不含 secret/可直接濫用細節的 regression test 納入公開 repo。
- 未來若要建立 Bug Bounty,必須另立 ADR 定義 scope、資格、獎勵與資金來源、安全港、重複回報、揭露及核發規則;ADR 通過前不得將 bounty 或其自動化描述成現行能力。
12. 反作弊¶
12.1 賽內反作弊¶
| 攻擊 | 防禦 |
|---|---|
| 修改 client 物理 | snapshot checksum 多簽偵測 |
| Input 偽造/畸形/equivocation | Race Sign Key 簽章與回合網域;每幀 ≤4 且 skill 唯一、Event/Hold 語意固定、必須屬於該 peer 已驗證 loadout;同 (peer, frame) 首值鎖定,觀測到相異簽章值立即 fail-closed |
| Catch-up 快照注入/DoS | requestId/source/target 一次性關聯、900 幀 horizon、內外 frame 相等;4 MiB 快照/raw packet 上限、per-peer verified queue 預算、sync 回應 120 frame cooldown;current SavedState 固定 schema/預配置武器與 Track Entity intact topology、vehicle/entity lifecycle、destruction counts、route progress、projectile clearance、weather scheduler/active descriptors 與數值範圍原子驗證 |
| Rapier collider 排序差異 | mesh fingerprint 跨 peer 比對 |
| 卡關 stall / grief(卡空 / 卡牆) | 卡空 → 自動重定位(遊戲機制.md §12);卡牆不另設偵測 = 當作無法完賽(賽程時間上限兜底、stall 無利益) |
| 抄近路 / 跳關 | Checkpoint 須依序點亮才算完賽 / 計圈;所有 peer deterministic 重算驗證 |
| Sybil 攻擊(多帳號自灌) | finisherCount ≥ 3 鑄幣閘(擋 solo / 雙人分身房)+ 完賽者群組 24h K=5 + per-player 有獎場數日限(player_prized_matches_per_day)+ 鑄幣速率封頂 |
| 結算共謀刷幣(全房合謀灌鑄) | MatchResultEvent 不含 payout/frontier;live 與 timeless fold 共用自包含 match-facts 驗證,canonical fold 依事件位置前態計算並套 incoming-inclusive 月硬頂;經濟系統.md §2.1 + 程式架構/ledger-admission.md §1 |
Snapshot checksum 偵測到 desync 而中止時寫入的
DesyncEvent不影響信譽、純供調查(desync 可能為網路 / 硬體因素,資料系統.md §3)。
詳見 賽內機制.md §5。
12.2 場地 UGC 反作弊¶
針對玩家上傳的場地 GLB 試圖規格外攻擊(巨大 AABB / 三角形爆量 / 未壓縮 / 內容空洞等)的對策,主表見 UGC機制.md §9.5。對應在 Stage 1 / 2 / 3 的拒收 / 自動調整邏輯內處理。
Stage 1–3 為上傳者 client 端檢核(誠實 client 的品質閘);對惡意 client 跳過檢核直接上鏈的網路面防線 = 載入端隔離解析 + 本地拒用(§2.4)。
12.3 網路層(eclipse / 假分叉)¶
孤立目標 peer、只餵它片面帳本或假分叉的攻擊面。既有緩解(本節僅索引):
| 機制 | 出處 |
|---|---|
| client 出貨信任根:ledger 位址+genesis signer set 內建 build(偽鏈位址不同、client 不連) | 資料系統.md §1.1 |
帳本檢查點 = 治理 signer set floor(2N/3)+1 多簽(假檢查點造不出) |
程式架構/ledger-checkpoint.md §3 |
| pinning 服務訂閱帳本檢查點(多來源交叉錨點) | 程式架構/pinning-service.md §7 |
| fork resolution 決定性收斂(同 log 集合必同結果) | 資料系統.md §7 |
| libp2p PeerScore gossip/publish/graylist 三閾值降權異常 peer | 程式架構/peer-discovery.md、network.md §10 |
13. PeerId 與身分¶
| 物件 | 由來 |
|---|---|
| 助記詞 | BIP39 12/24 詞,玩家保管 |
| Master Key | 從助記詞 derive |
| PeerId | 從 master key derive;編碼=multibase base58btc(0xed01 ‖ pubkey)(程式架構/key-manager.md §1) |
| Profile | 本機可存多組身分檔(各自助記詞/PeerId),PIN 解鎖切換 |
| Race Sign Key | 每 race ephemeral key(防 long-term key 暴露) |
PeerId 不直接綁定 IP(P2P 設計,no central tracking)。
13.1 多身分與本機備份¶
- 同一 origin 可存多 profile,但 durable activation readback 只允許一個 active PeerId;切換先停舊 P2P/房間/賽事與清明文 key/TURN/workspace。
- 身分 domain key 只從 master seed 以 HKDF-SHA256 派生,不從 PIN 派生;AES-GCM AAD 綁 PeerId 與
domain。
encryptedSeed與任何 master/private key 永不匯出。 .open4wd-backup外層 Argon2id+分塊 AES-GCM,AAD 綁 backup id、schema、index/total/length; encrypted header 綁有序 chunk CID,reorder/replace/truncate 全部 fail closed。- 匯入的 GLB 一律當不可信輸入,重新經 Sanitize Worker、Wave A 與 canonical rules;不得因來源是 自己的加密包而略過收件驗證。
14. 跨模組對接¶
| 模組 | 對接 |
|---|---|
key-manager/ |
簽章工具源頭 |
network-sync/ |
snapshot checksum + sign |
ledger/ |
event sign + 帳本檢查點 multisig + MatchResult facts admission |
economy/ |
canonical fold 結算計算 + 鑄幣資格/incoming-inclusive 硬頂 |
ugc-fork/ |
upload sign + 反複製 |
anti-piracy/ |
mesh fingerprint + 相似度比對 |
pinning-service/ |
sign 上傳請求 |
signaling-service/ |
sign 握手 |
room-runtime/ |
RFC 9807 OPAQUE 角色邀請+雙向 confirmation+線上嘗試窗與高成本 wire 限速 |
peer-discovery/ |
PeerScore + 黑名單 |
chat-system/ |
聊天簽章(SignedPayload + roomId)+ 收訊端 per-sender 限速 |
moderation/ |
檢舉 / 仲裁 |
15. 玩家安全教育¶
玩家介面以「備份碼」(各語系採既有在地化名稱)稱呼 BIP39 mnemonic;「助記詞」只保留在 程式/協議層技術文件。必要告知如下:
- 「絕對不要把備份碼給任何人」
- 「備份碼遺失或外洩 = 身分、餘額、信譽永久失控,無重置機制」(self-custody)
- 「PIN 只在本地驗證,不傳遠端」
- 「Pinning / signaling 節點選擇權在你」(不強制信任)
- 「請從官方網址取得遊戲。第三方鏡像站(開源 fork)可連上同一個網路,但在別人部署的 client 輸入助記詞 = 把金鑰安全交給該發布者——風險自負」(鏈身分與信任根見 資料系統.md §1.1)
- 「比賽時與同房玩家 P2P 直連——你的 IP 對房內玩家可見(P2P 本質、無中心中繼)」
- 「聊天訊息只在房間內 P2P 傳輸,無中心伺服器紀錄;訊息經簽章——對方可留存,檢舉時可作為可驗證據隨案上鏈公開」
呈現位置固定為:登入頁在玩家可能輸入備份碼之前顯示第三方 client 警告;設定頁「隱私」固定 列出第三方 client、P2P IP 與聊天證據三項;說明頁提供「安全與隱私」完整文章供日後回查。 這些是持續可見的必要揭露,不做每場重複勾選。IP 告知須區分實際路徑:直連會讓同房對端看見 位址,只有確實經 TURN relay 的連線不直接向對端暴露 IP,不得籠統宣稱「有設定 TURN 就隱藏」。
16. 不變式¶
sanitize 拒收的 GLB 不會上鏈
不可信 GLB 不進主執行緒解析(一律 Sanitize Worker 隔離,§2.4)
驗章失敗的 event 不會 apply
nonce 重複的訊息 reject
nonce 長度錯誤、replay set 滿載或 Gossip admission / rate limit 未過的訊息 reject,且不得提前 commit nonce
timestamp 偏差 > 30 秒的訊息 reject
MatchResult 宣稱 payout/frontier,或其 facts/外層多簽不符 → reject;金額只在 canonical fold 計算
roundAnchors 長度或非 null 證書的 match/grid/tail/roster/quorum 無效 → reject;任一 null → canonical settlement 為 not-eligible
同一 match/round 出現兩張完整有效 anchor certificate → 保存衝突證據並 consensus-invalid,不選鏈
CSP / SRI 違規 → CI 阻擋 merge
本地安全事件只留正面表列、已去識別 details,且不得自動持久化或傳送遠端