
本文由 Consensys Diligence 與 MSQ 專案合作撰寫,並獲得 DFinity Foundation 及 MetaMask Snaps DAO 的支持。

Consensys Diligence 是 Consensys Software Inc. 旗下的審計部門,以其在 Solidity、Cairo 及各類 ZK 函式庫審計方面無與倫比的專業能力著稱。Diligence 團隊正引領 MetaMask Snaps 的審計與安全標準制定,並與 MetaMask 團隊密切合作,確保錢包功能的安全性。此外,Diligence 團隊也積極開拓安全工具開發,以及在最嚴苛的生產環境中驗證的前沿持續模糊測試解決方案。
MSQ 專案近期已在 MetaMask Snaps Store 上架。MSQ 是一款 Snap,讓使用者能與 Internet Computer 網路互動、管理基於 IC 的代幣、在基於 IC 的 dapp 中安全授權,以及支付數位商品與服務。本質上,它是一個整合於 MetaMask 的加密貨幣錢包,專為 Internet Computer 設計,具備獨特的授權與支付功能。
Diligence 與 MSQ 團隊共同確立了本文涵蓋的五大核心領域——文件、軟體設計、程式碼、供應鏈,以及修復——列出 MSQ 團隊在開發過程中採用的實踐方法,並附上 Diligence 團隊對這些實踐之安全效益的詳細說明,為任何正在開發 MetaMask Wallet Snap 或其他可能接受安全審計之關鍵系統的開發者提供實用參考。讓我們開始吧!
文件能將你腦中的概念轉化為他人可見的內容。分層文件透過逐步引導讀者進入情境,幫助他們以你預期的方式理解這些概念。顯然,文件越清晰,就能為你的專案提供越廣闊的視角,讓他人更容易理解你的意圖。
我們以讀者通常存取的反向順序定義文件層次,每一層提供更高層次的抽象。如此一來,從全局視角出發的讀者將能逐步深入細節。
關於你軟體的最終資訊來源是程式碼本身,它是文件的基礎層。雖然程式碼包含所有必要資訊,但處理這些資訊並不容易。程式碼越整潔,就越容易從中解析出高層次的抽象概念。但在實際系統中,這些抽象概念往往隱藏在系統元件的複雜互動、數週的技術債,以及快速而粗糙的解決方案之後。
因此,首先你要保護閱讀程式碼的人,避免他們被系統中某些複雜部分所誤導。最好的方法是降低閱讀程式碼所需的認知負擔。一些可行策略包括:
讓程式碼易於導覽,方便在 IDE 中輕鬆瀏覽。
使用詳細的命名來闡明程式碼元素的目的與功能。
採用強型別語言來定義清晰的資料格式。
MSQ Snap 與 dapp 前端均以 TypeScript 開發,有效防止了許多潛在錯誤,相較於純 JavaScript 的種種陷阱優勢明顯。同樣地,我們的後端採用 Rust,以其強健的型別系統著稱。此外,MSQ Snap 匯出了一系列可由第三方應用程式執行的方法。然而,我們不使用字串來識別這些方法,而是使用常數。我們有一個如下的常數物件:
在程式碼中,這個常數物件可作為存取正確方法的指引,讀者也能透過物件中的識別碼了解該方法的許多資訊。例如,方法 SNAP_METHODS.protected.identity.login 只能由 MSQ 的配套 dapp 執行,並允許使用者登入。這種方式提升了程式碼的清晰度,也簡化了重構工作,因為方法名稱的變更可集中管理,而非分散在多處逐一更新。
為了比對方法名稱,我們使用帶有如下分支的大型 switch 表達式:
如你所見,閱讀時幾乎不可能產生誤解——每個命名都精確無誤,毫無模糊空間。
Diligence 評語 時間是關鍵因素。開發者有數週時間來理解程式碼庫,而安全工程師往往只有數天來識別風險。這一限制凸顯了降低程式碼認知負擔的重要性——這對開發效率與安全性都大有裨益。
從 MSQ 的程式碼庫中我們可以學到:清晰、易於導覽、具有描述性命名與強型別(如 TypeScript)的程式碼,能減少誤解——而誤解正是漏洞的常見來源。
但請記住,TypeScript 的型別標註僅在編譯時進行檢查!對於在執行時處理或解析的任何資料(RPC、JSON 等),還需要額外的型別強制執行、資料格式與邊界檢查!為此,MSQ 優雅地採用了 zod——一個以 TypeScript 為優先、具備靜態型別推斷的 schema 驗證函式庫,我們建議各團隊跟進這種做法。
MSQ 在程式碼意圖的表達上拿捏得恰到好處,透過適量的注釋讓意圖清晰可見。這種清晰度有助於安全研究人員快速掌握程式碼庫的「主軸」與核心概念。透過撰寫易於理解的程式碼,開發者能大幅提升快速安全審查的效果。
我們認為注釋的目的並非覆蓋 100% 的程式碼。關鍵在於為複雜或寫得不夠清晰的部分加上注釋。正規表達式、密碼學,以及字串處理等領域尤其需要清晰的注釋。
例如,在 MSQ 中,我們有一個函式用於跳脫字串中可能出現的 Markdown 控制字元。其主體只有一行程式碼,但其中包含正規表達式比對,因此我們在注釋中描述其功能:
如果你不排斥在工作流程中使用 AI,ChatGPT 非常擅長快速為此類工具函式生成良好的注釋(只需仔細核對其輸出即可)。
Diligence 評語 身為安全審查人員,我們將函式注釋視為穿越陌生程式碼的路線圖。請記住,我們審查程式碼的時間有限,當注釋與實作不符時,不僅令人困惑——更是一個警示訊號。
這往往揭示函式或其描述偏離了規格,暗示潛在漏洞的存在。也許某個函式跳過了承諾的檢查,或以意外方式處理資料,這都可能導致嚴重後果。
因此,請保持注釋簡潔且準確,就像 MSQ 在其程式碼庫中所做的那樣。行內注釋不只是備忘錄;它們是引導我們穿越程式碼的燈塔,而任何不一致之處都是某處可能出問題的線索。
在確保程式碼及其複雜性都有完善文件記錄之後,下一步是概述系統架構。這包括詳細說明元件之間的互動,以及你的軟體所採用的協定、演算法和資料結構。這一層主要使用人類可讀的格式,例如文字和圖表,而非程式碼。
MSQ 的系統文件包含架構概覽和整合指南。這些文件內容豐富,包含文字描述、範例和圖表,清楚說明不同系統部分如何互動。
序列圖對於視覺化元件互動非常有價值。我們推薦使用 diagrams.net(draw.io),這是一款免費工具,已在各種專案中證明其多功能性。
Diligence 評語 與詳述函式的行內注釋不同,系統文件提供了一萬英尺的高空視角,呈現應用程式的完整全貌。這種視角對於發現跨元件問題或使用者流程漏洞至關重要,而這些問題在程式碼層面往往難以察覺。
把安全審查想像成一場《終極動員令》遊戲。你從一張大部分被遮蔽的地圖開始。隨著探索推進,系統文件就像偵察單位,逐步揭開完整架構——權限、Snap 與 dApp 的互動、使用者互動。這種逐步展開的全局視角讓審查人員能追蹤使用者旅程,就像追蹤遊戲單位的移動,從而發現可能繞過安全機制的路徑。
此外,完整的系統文件應包含與安全相關的備註,理想情況下還應包含威脅建模演練的輸出結果,清楚概述主要元件、互動、風險、參與者和典型使用者流程。這有助於審查人員聚焦於高優先級區域,並發現與規格的偏差。
對開發者而言,良好的系統文件不只是文書作業;它是一張共享地圖,引導攻防策略,將安全審查從盲目探測轉化為有策略的行動。
這一層涵蓋各種材料,例如文章、登陸頁面、部落格文章、示範影片等。
這些資訊大多關乎業務層面,但在回答「是什麼?」這個問題上發揮了重要作用,而前幾層則以不同程度的細節回答了「如何做?」的問題。
新接觸者最可能透過這類材料認識你的專案,因此盡量讓這一層保持簡單且視覺化是個好主意。此外,這類材料通常是唯一能展示軟體實際運作的內容——使用者如何透過你的軟體從狀態 A 到達狀態 B。透過親眼看到軟體的運作,人們能了解你軟體的核心理念,並對其底層運作方式做出合理推測。
例如,MSQ 的登陸頁面主視覺以大字寫著:「USE THE INTERNET COMPUTER WITH METAMASK」。讀完這句簡單的句子,讀者可能會猜想:「啊,所以這是一個可以儲存 Internet Computer 區塊鏈代幣的錢包 Snap。它的運作方式應該和其他區塊鏈的 Snap 類似,只是使用了 IC 開發生態系的技術」——這個猜測完全正確。
Diligence 評語 我們在審查 MSQ 及眾多其他區塊鏈專案的經驗中,始終印證了一點:清晰的高層次文件不只是有益的——對於使用者採用和安全性而言,它至關重要。在審查 MSQ 的 Snap 時,他們的多層次文件方式讓我們感到驚喜。他們開發者的影片導覽讓我們迅速上手。即使在查看程式碼之前,我們就能快速理解 Snap 的功能。這份高層次概覽展示了使用者介面,並附有對預期行為和典型使用者流程的說明。這類視覺輔助工具極具價值,讓安全專家能更快掌握應用程式中編碼的預期「快樂路徑」,並尋找往往隱藏在這些路徑之外的漏洞。這與 Snap 和程式碼的現場導覽相結合,通常是安全審查的理想開端。
這部分對你專案的安全性而言可能是最重要的。你的程式碼可以完全沒有錯誤,你的基礎設施可以用價值數百萬美元的各種措施加以保護。但如果你的設計存在某些根本性缺陷,可被惡意行為者利用,那麼所有這些努力都將毫無意義。
雖然具體範例因使用情境而異,我們提供幾個通用建議。
在 MSQ 專案之初,我們建立了與願景相符的核心安全原則:
MSQ 團隊一無所知:我們不收集任何使用者敏感資訊,包括金鑰材料、授權工作階段資料或帳戶詳情。
密碼學材料絕不離開 Snap:受保護的使用者 MetaMask 熵值僅在 Snap 內部處理,按需生成,使用後立即丟棄。
MSQ 具有確定性:使用者無需任何額外知識,即可透過根助記詞確定性地恢復所有資金、身份和金鑰對。
MSQ 以隱私為優先:我們的努力將使用者隱私置於一切之上。
這些原則是針對 MSQ 的特定需求量身制定的。然而,MetaMask 團隊也為 Snap 開發者提供了一套安全準則,MSQ 遵循這些準則,可在此處查閱。
例如,有一個最小權限原則,基本上意味著你的 Snap 向使用者請求的權限越少,其安全性就越高。MSQ 只請求 4 項權限:
endowment:rpc
snap_dialog
snap_manageState
snap_getEntropy
我們不需要 endowment: network access,因為 MSQ 只負責簽署交易,而非將其傳送至 IC——整合的 dapp 負責後者。
你應該在你的 Snap 中做同樣的事——採用 MetaMask 的預設準則,以你的特定規則加以擴充,並嚴格遵循。
Diligence 評語 我們告訴客戶:「安全不是你添加的功能;它是你圍繞其設計的原則。」MSQ 在專案之初就建立核心安全原則,正是我們所期望看到的。太多專案將安全視為事後補救,在核心架構確定後才附加安全措施。這種補丁式做法不可避免地留下程式碼層面的修復無法真正解決的根本性缺陷。
在 Consensys Diligence,我們提供安全專家服務、工具,並倡導終端使用者的權益。這包括確保使用者的金鑰材料安全地保存在錢包信任邊界內,絕不會無意間暴露在此範圍之外。我們也專注於保護使用者隱私,理想情況下甚至防止元資料或旁路資訊洩漏給第三方。此外,我們推動 Snap 僅請求功能所需的最小權限——即最小權限原則。
MSQ 的積極態度與這些原則相符。透過從一開始就將安全置於優先地位,他們正在建立一個更堅實的基礎——一個本質上對漏洞更具抵抗力的基礎。
這更像是一個元準則——它是任何軟體都應實施的原則:從線上遊戲到銀行系統皆然。
MSQ 中最值得注意的例子是我們對身份管理的做法。你可能知道,大多數錢包會為使用者提供一組身份(地址、帳戶等)。在使用者同意的情況下,任何 dapp 都可以存取這組身份,例如用於簽署某些交易。
這對 dapp 之間的整合非常有用,因為任何 dapp 都可以代表使用者呼叫其他 dapp 的智能合約方法,並以此方式相互擴展功能。例如,加密支付平台可以呼叫 Uniswap 的智能合約,自動將買家的代幣兌換成符合賣家對特定商品或服務付款預期的代幣。
但這對安全有害,因為詐騙網站可能誘騙人們簽署惡意交易,清空他們的錢包。
與此相對,MSQ 採用了「身份範疇化技術」——每個 dapp(每個不同的 URL 來源)都有其自己獨立的使用者身份集合可以互動,無法與其他 dapp 的使用者身份互動。這不僅解決了詐騙網站的問題,也大大有助於隱私保護,因為 dapp 無法再追蹤使用者,也無法看到他們與哪些智能合約(在 IC 中稱為 canister)互動。這種方法受到 Internet Identity 和 WebAuthn 規範的啟發,大幅降低了安全風險。
例如,如果一個知名 dapp 遭到入侵,傳統錢包將在所有 dapp 上都面臨風險。相比之下,MSQ 的身份範疇化將威脅限制在被入侵的 dapp 範圍內,防止更廣泛的損害。
然而,這種方法限制了 dapp 之間的直接整合,因為使用者在每個 dapp 上都有不同的身份。MSQ 為特定情境提供解決方案,例如允許在雙方同意的情況下在 dapp 之間共享身份,這對於域名變更等情況非常有用。
此外,MSQ 確保 dapp 可以存取的資料嚴格與相關身份綁定,進一步保護使用者資訊,並在不同應用程式之間維護隱私。
Diligence 評語 限制 dApp 直接整合的取捨是經過深思熟慮的。在我們的風險評估中,我們經常討論「安全預算」——承認完美的安全性可能妨礙功能性。MSQ 明智地運用了這筆預算。透過限制跨 dApp 互動,他們大幅降低了最嚴重的風險之一(大規模身份濫用),同時保留了核心錢包功能。
在討論文件時,我們已稍微談及程式碼。在此,我們想強調程式碼的另外幾個面向,這些面向更多與技術相關,而非可讀性。
這裡的「健全」是指程式碼行為可預測,能處理所有預期和非預期的輸入。例如,當你的 Snap 預期接收 hex 格式時,若收到 base64 字串應如何反應?或者若方法以非預期的順序被呼叫,或呼叫了未定義的 RPC 方法時又該如何?
處理這些不確定性需要:
清晰的錯誤處理: 確保程式碼中的每個分支和情況都經過檢查並得到適當處理。在 MSQ 的 Snap 中,我們在 switch-case 語句中使用 TypeScript 的窮舉檢查來捕捉任何意外的方法呼叫,如下所示:
這個設置能捕捉使用未知 RPC 方法識別碼的情況。
輸入清理: 根據預期的型別和格式驗證輸入資料可能很複雜,但對安全性至關重要。我們在 TypeScript 中使用 zod 函式庫來實現這一點,將 TypeScript 的型別系統擴展至執行時期檢查。Zod 允許詳細的 schema 定義,確保只有有效資料才會被處理。
例如,我們的方法 SNAP_METHODS.protected.identity.login 使用 zod schema 來嚴格驗證輸入:
這裡,ZOrigin 和 ZIdentityId 不只是普通的字串或數字,而是經過特別驗證的子型別,確保輸入不僅在型別上正確,在情境上也適當。
透過盡可能精確地定義 zod schema,你可以預先解決許多潛在問題。審計人員建議在 MSQ 中強制執行嚴格驗證,實施後識別並修正了多個安全風險。
Diligence 評語 這不是我們第一次聽到客戶表示,根據我們針對特定攻擊情境的建議實施更嚴格的驗證後,揭露了其他幾個安全風險。這與我們的經驗完全吻合,這也是為什麼我們始終倡導嚴格的輸入驗證以縮小攻擊面。在許多安全審查中,我們從仔細審查輸入驗證開始。收緊這一環節往往會暴露潛伏的問題——就像骨牌效應,修復一個漏洞會揭露另一個。這清楚地表明專案認真對待我們的反饋,並持續強化其系統。隨著攻擊面的縮小,攻擊者成功利用弱點的難度也逐步提高。
在 MSQ,我們認為有責任為 dapp 開發者提供一個直觀的函式庫,促進與我們 Snap 的正確且高效互動。這不僅讓 MSQ,也讓第三方開發者能夠維護整潔且健全的程式碼庫。
這樣的函式庫有幾個好處:
簡化 API 複雜性: 我們將 Snap 的複雜性抽象為使用者友善的 API。例如,MSQ 的客戶端函式庫使用 CBOR 進行資料編碼,繞過 JSON-RPC 的限制,省去開發者手動編解碼資料的麻煩。
確保安全且直接的互動: 某些 Snap 功能僅限於我們的配套 dapp 使用。為此,我們管理重新導向和資料傳輸流程,透過 postMessage 確保無縫流轉。這種內部處理保證第三方開發者透過清晰、直接的 API 與我們的 Snap 互動。
增強語義清晰度: 整潔的 API 不只是工具;它是一種溝通媒介,讓程式碼更易讀易懂,擺脫複雜編解碼流程和額外操作層的雜亂。
透過優先考慮這些面向,MSQ 致力於創建一個讓我們的 Snap 和整合 dapp 都能高效且安全運作的生態系統。
Diligence 評語 MSQ 透過其函式庫整合第三方程式碼的做法,展現了在維護安全性的同時簡化開發的承諾。雖然在核心層面,安全性必須在 Snap 環境中嚴格執行,但面向 dapp 的函式庫可以提供一種便捷的方式與 Snap 通訊。然而,始終需要注意的是,dapp 開發者需要確保與 Snap 的安全互動,以及在 dapp 環境中使用時的資料安全編碼。
近期的 XZ 後門事件再次強調了防止惡意程式碼進入依賴項的重要性。在 MSQ,我們使用 pnpm 來管理依賴項,但實際上你偏好哪種套件管理器並無差別。我們在 MSQ 採取了兩項措施來防範此類攻擊向量:
使用 pnpm install --save 通常會安裝套件的最新版本,遵循 semver 規範,允許自動更新至不破壞相容性的較新版本。這在 package.json 中以插入符號(^)表示,代表可接受的版本範圍:
"zod": "^3.22.4" // Installs version 3.22.4 or newer up to 4.0.0然而,為了防範惡意更新的風險,將依賴項鎖定至特定版本更為安全:
從你的 package.json 中移除插入符號(^)以固定每個依賴項版本。刪除現有的鎖定檔案和 node_modules,然後執行 pnpm install 以嚴格遵循已定義的版本。
對未來的安裝使用 --save-exact 旗標(pnpm install --save-exact )以避免自動更新。
信任是這個決策中的一個因素。例如,在 MSQ,我們認為 @metamask 和 @dfinity 依賴項命名空間是安全的,並允許在其 semver 範圍內更新。然而,在這樣做時,請注意傳遞依賴項可能帶來的潛在漏洞。
Diligence 評語 將專案切換至精確依賴項版本已防止了無數問題:
它阻止了靜默更新重新引入已修補的漏洞。
它防止了「版本偏差」——即不同模組看到不同函式庫行為的情況。
它在事後調查中至關重要,讓我們能夠重現確切的環境。每次更新都可能是惡意的,無論是哪個命名空間、哪個套件。透過審查使用的套件,並僅綁定至已知安全的版本以防止意外更新,可進一步縮小供應鏈攻擊的攻擊面。信任特定供應商是可以的,只要在使用前手動驗證新版本不包含惡意程式碼。請注意,被許多專案廣泛使用的知名套件尤其是惡意行為者的主要目標,過去的案例表明,任何此類核心函式庫都可能遭受複雜的供應鏈攻擊(參見 XZ 後門攻擊)。
為確保團隊一致性並維護可重現的建置,請將鎖定檔案提交至版本控制系統。在 MSQ,我們使用 pnpm workspaces 和 turborepo,需要一個單一的根鎖定檔案 pnpm-lock.yaml。然而,你的設置可能需要維護多個鎖定檔案;所有這些都應納入版本控制,以確保環境一致性。
Diligence 評語
這個做法有時被視為僅是開發便利性的考量而遭到忽視,但實際上它是維護安全且穩定程式碼庫的關鍵組成部分。我們多年來審計智能合約、區塊鏈元件,以及現在的 MetaMask Snaps 的經驗反覆表明,可重現的建置不只是錦上添花,而是基本的安全要求。提交鎖定檔案是縮小供應鏈攻擊面的拼圖之一。當 npm install 不再是一場抽獎,具體的版本和校驗碼在儲存庫中留下審計軌跡,建置更具確定性,「在我的機器上可以運行」的爭論也將成為過去式。
雖然這與我們的 Snap 沒有直接關係,但了解如何有效處理審計發現至關重要。
一旦審計人員團隊回報發現,你應立即開始著手修復。修復的規模和複雜性顯然取決於發現的內容,但有一個建議可以讓修復更小且更乾淨——在可能的情況下,修復根本原因,而非症狀。
例如,在 MSQ,審計人員發現可以在我們的 MetaMask 同意訊息中注入 Markdown 控制字元。我們沒有逐一修復每條同意訊息,在程式碼的不同部分進行多處更改,而是簡單地重新定義了 text() 和 heading() 元素(來自 @metamask/snaps-sdk),使其先將內容通過 escapeMarkdown() 函式處理,然後再渲染。這讓我們只需在所有包含同意訊息的檔案中將 import 表達式替換為我們的自訂元素,而無需逐一修復每個 text() 和 heading() 的呼叫,這是一個更乾淨的修復方案。
有效的修復不僅改善了特定問題,也預先防範了未被發現的漏洞。
Diligence 評語 我們經常看到專案採取打地鼠式的安全方法,逐一修補每個出現的問題,而不考慮底層的漏洞類別。透過解決問題的根本原因,而非猶豫不決地全面消除漏洞類別,MSQ 展現了他們將安全視為整體性學科,而非一系列補丁的理解。作為安全專家,我們只能建議客戶不要倉促修復,而是花足夠的時間將安全發現轉化為對專案安全性產生正面影響的事件。
以上是我們對成功通過 MetaMask Snaps 安全審計的深入見解。遵循這些準則幫助 MSQ 團隊獲得了 Diligence 團隊的正面報告。我們希望分享 MSQ 的經驗能協助你做好安全審計準備,確保過程全面且有效。
Consensys Diligence 已協助區塊鏈生態系中的各類專案確保其協定已準備好上線,並致力於保護使用者。我們很樂意在客製化審計中為你的 Snap 檢查這些及更多潛在漏洞。立即聯繫我們,取得報價!
AI 翻譯。可能包含錯誤。請務必核實資訊。