AGOV 傳統中文翻譯:AGOV 註冊錯誤:鍵盤佈局如何破壞數位身分系統
AGOV 註冊錯誤:鍵盤佈局如何破壞數位身分系統
AGOV 註冊中的 Shift 鍵錯誤
A critical accessibility bug 在瑞士政府登入系統 AGOV 中,導致使用 AZERTY 鍵盤佈局的使用者無法註冊帳號。此問題源於一個用於驗證碼輸入的自定義 JavaScript 實作,該實作明確地封鎖了包括 Shift 鍵在內的修飾鍵(modifier keys)。由於 AZERTY 鍵盤需要透過 Shift 鍵來輸入數字,這些使用者完全無法進行註冊程序。
技術根本原因:攔截按鍵事件
AGOV 的註冊程序要求使用者在六個獨立的輸入框中輸入驗證碼。該系統並非使用標準的 HTML input 欄位,而是採用了一套複雜的 JavaScript 機制來攔截按鍵事件並將驗證碼儲存在本地變數中。
對壓縮後的 JavaScript 進行分析後發現,有一個 verificationCodeEntered 函數負責處理輸入邏輯。該函數包含一個 switch 語句,如果 S.key 為 "Shift"、"Control"、"Meta" 或 "v",則會明確地回傳(忽略)該輸入。
verificationCodeEntered(_, S) {
if ("Shift" === this.lastKeyPressed) return;
switch (S.key) {
case "Control":
case "Meta":
case "Shift":
case "v":
case "r":
return;
// ... other cases
}
S.preventDefault();
const O = Number(S.key);
isNaN(O) ? (this.getInput(_).value = "") : this.setCode(_, String(O));
}
透過呼叫 S.preventDefault() 並手動處理按鍵映射,開發人員繞過了瀏覽器的原生角色處理程序。在 AZERTY 佈局上,數字並非主要按鍵;它們是透過 Shift + [key] 來存取的。由於程式碼明確地忽略了 Shift 鍵,導致產生的字元從未被處理為數字,使得輸入框保持空白,並觸發「欄位必填」錯誤。
「先有雞還是先有蛋」的支援問題
此錯誤的影響因系統支援架構的失敗而進一步惡化。無法註冊的使用者無法回報該錯誤,因為官方支援管道是一個需要與主註冊程序進行相同電子郵件驗證程序的網頁表單。
根據 AGOV FAQ,支援是「僅透過在線上建立工單(tickets)來提供」。這造成了一種循環依賴,即阻礙註冊的錯誤同時也阻礙了使用者能夠聯繫支援團隊來回報該錯誤。
對 UI/UX 設計的更廣泛影響
此事件凸顯了現代網頁開發與無障礙設計中的幾項系統性失敗:
過度工程化簡單的輸入框
使用由自定義 JavaScript 管理的六個獨立輸入框是一種常見但脆弱的模式。開發人員之間的技術共識建議,使用單個原生瀏覽器表單欄位,並透過 CSS 樣式化為看起來像獨立的箱子,會顯著地更具韌性且更具無障礙性。
將邏輯與 DOM 解耦
由於 AGOV 系統將驗證碼儲存在 JavaScript 變數中,而不是在提交時直接從 DOM 中讀取值,這使得輸入變得對瀏覽器擴充功能和進階使用者而言是「不可見」的,而這些使用者原本可能會嘗試手動注入值到欄位中。
缺乏早期開源披露
雖然瑞士政府在法律上要求披露 AGOV 的原始碼(預計於 2026 年 12 月進行),但該系統在程式碼公開之前就已被強制要求在多項服務中使用。早期披露本可以讓社群可以識別並解決此鍵盤佈局不相容性問題,在部署前就先處理掉。
社群觀點
開發人員對於此失敗的討論強調了這些錯誤的重複性。一位貢獻者指出,類似的問題也影響了使用 Programmer Dvorak 佈局的使用者,其中數字也需要透過 Shift 鍵來輸入。
其他批評者指出自定義驗證邏輯的低效性:
"Web developer classic: use Javascript to replace the native, working, internationally supported standard inputs with a different input mechanism that doesn'://-1
"Web developer classic: use Javascript to replace the native, working, internationally supported standard inputs with a different input mechanism that doesn't work."
對於日期和電話號碼的「創意型」UI 元件(widgets)忽略了原生 HTML5 input 欄位(例如 `<input type=