Просто дайте мне ввести цифры
- четверг, 16 июля 2026 г. в 00:00:09
Цифровая идентификация и её значение для веба в последние годы стали темой горячих обсуждений. Они привнесли с собой множество спорных моментов: законы о проверке возраста и их влияние на онлайн-анонимность; Википедия потенциально будет вынуждена верифицировать в Великобритании личность пользователей; привязка к официальным операционным системам iOS и Android стала обязательным форм-фактором для кошельков цифровой идентификации; кроме того, стоит упомянуть ситуацию, когда американские цифровые аккаунты были закрыты потому, что их пользователь был судьёй Международного уголовного суда.
В своей истории я расскажу о швейцарской правительственной системе идентификации AGOV. Этот развёрнутый в 2024 году сервис сегодня насчитывает 1,6 миллиона аккаунтов и становится всё более необходимым: через него можно получать пособие по безработице, предоставлять налоговую декларацию (а это обязательное действие!) и выполнять множество других операций. В кантоне Цюрих это единственная возможность подачи заявления на гражданство.
В конечном итоге, я был вынужден создать аккаунт AGOV. К сожалению, его регистрация была довольно сложной задачей, пока я не нашёл причины странного бага accessibility. Вдохновившись статьёй «Просто дайте мне выделить текст», хочу представить вашему вниманию «Просто дайте мне ввести цифры».
Первый этап процесса регистрации на AGOV — указание адреса электронной почты и ввод кода верификации для подтверждения этого адреса. У меня возникла проблема: я даже не мог ввести текст!
Контекст: форма для ввода кода верификации разделена на шесть полей (по одному на каждую цифру). Первая странность заключалась в том, что при вводе каждой цифры фокус не переключался автоматически на следующее поле. Вместо этого в текущее поле добавлялись новые цифры, которые подчёркивались красным, сообщая об ошибке.

Поначалу я решил, что это лишь несущественный баг UI и что можно просто вручную переходить в следующее поле после каждой цифры. Однако это не срабатывало, и страница всегда отображала в конце ошибку «Field required», даже если я аккуратно вводил в каждое поле цифру. Странно...

Я попробовал посмотреть в инструментах разработчика на DOM, чтобы понять, есть ли там значение формы, которое можно изменить вручную, но на первый взгляд ничего такого заметно не было. В веб-консоль не выводилось никаких сообщений об ошибке. Я попробовал использовать другой адрес почты и даже вводил фальшивый адрес1 наподобие test@example.com, но ничего не поменялось.
Я заходил на сайт через Firefox в Linux, поэтому подумал, что, может быть, именно это сочетание не поддерживается или не проходит CAPTCHA. Вероятность была низкой — сегодня я редко сталкиваюсь с веб-сайтами, поломанными конкретно для Firefox или Linux, но полностью исключать её было нельзя. И в самом деле, непосредственно перед отображением формы ввода кода верификации страница регистрации подключается eu-api.friendlycaptcha.eu, а в официальной документации AGOV в качестве поддерживаемых операционных систем указаны только Windows и macOS, хотя на другой странице о ключах безопасности говорится, что Linux и Firefox поддерживаются.
Чтобы исключить эту возможность, я попробовал шесть сочетаний браузеров и операционных систем: Firefox, Chromium и Chrome в Linux; Firefox, Safari и Chrome в macOS. Все они отображали одну и ту же ошибку!
Тут я решил, что стоит обратиться в официальную поддержку. На сайте AGOV есть много разделов помощи, но, к сожалению, особо они мне не помогли, потому что единственный способ обратиться в поддержку — это веб-форма… которая сначала отправляет код верификации на почту! Это та же самая форма, которую я не смог отправить.
И да, в FAQ чётко говорится, что эта форма — единственная возможность обращения в поддержку (выделение моё).
Поддержка участвующих сторон предоставляется вторым уровнем поддержки в рабочие часы исключительно посредством создания тикетов онлайн.
В выходных сведениях также указан физический почтовый адрес Федеральной канцелярии Швейцарии, которая вряд ли сама разрабатывала веб-сайт. Больше никакой контактной информации нет: ни электронной почты, ни телефонного номера. В принципе, можно написать комментарий для «отправки отзыва AGOV: благодарности, критики, просьб», но для этого тоже нужен аккаунт AGOV или эквивалентный. Настоящая проблема курицы и яйца!
Я заглянул в онлайн-обзоры Android-приложения AGOV Access, которое на данный момент имеет рейтинг 1,4 звёзды — худший из всех используемых мной приложений. К сожалению, в этих комментариях на Google Play Store никто не говорит о подобных проблемах. Любопытно, что там есть много комментариев о поддержке GrapheneOS (конфиденциальной и защищённой операционной системе на основе Android), а в официальном FAQ теперь даже есть пункт о ней (выделение моё)!
Приложение AGOV access не работает в GrapheneOS, потому что фреймворк, защищающий приложение AGOV access от неавторизованных манипуляций, несовместим с GrapheneOS. Федеральная канцелярия поручила поставщику приложения обеспечить совместимость.
Звучит многообещающе, но пока можно зарегистрироваться/войти только через устройство двухфакторной аутентификации, то есть или через смартфон с ванильной iOS или Android, или через ключ безопасности (хорошо, что такая возможность есть!). Разумеется, это возможно только после прохождения верификации электронной почты.
Мне было пока не срочно создавать аккаунт AGOV, поэтому я время от времени рассказывал об этой проблеме своим друзьям, но, похоже, никто из них с ней не сталкивался.
Ещё я решил поспрашивать коллег и попробовал зайти на страницу регистрации со своего рабочего ноутбука, чтобы сделать скриншот и показать им доказательства. Как ни странно, на этот раз форма заработала! Автоматическое заполнение переходило в следующее поле, и форма на самом деле определила, что я ввёл неверный код!

На этот раз она возвратила ошибку «Code entered is incorrect».
Но, конечно же, в вопросах общения с государством я не хочу полагаться на выданный работодателем компьютер. Даже если мне удастся зарегистрироваться с рабочего ноутбука, всегда остаётся риск того, что я не смогу войти с личной машины.
К сожалению, ни у одного из коллег не возникала моя ошибка… Я попробовал и на компьютере друга с теми же тремя браузерами в macOS, и все они приняли код верификации!
Неужели я такой неудачник, что мне единственному в мире принадлежат те два компьютера, на которых веб-сайт ломается?
На этом простые мысли у меня кончились, поэтому я решил исследовать веб-страницу. Как ни странно, она загружает небольшое количество ресурсов, однако основной файл JavaScript имеет размер 2,4 МБ и состоит из более чем ста тысяч строк частично обфусцированного/минимизированного кода. Выглядело всё это довольно пугающе, ведь на первый взгляд казалось, что некоторые части обфусцированы сильно.
... var Pc = o(3980), eh = o(4572), wa = o(9974), El = o(2326), Tp = o(6450), th = o(1203), Ld = o(9326); function Yl(...M) { const m = (0, Ld.ms)(M); return m ? (0, th.F)(Yl(...M), (0, Tp.I)(m)) : (0, wa.N)((I, _) => { (0, eh.P)([I, ...(0, El.K)(M)])(_); }); } ...
Я решил подождать ещё немного на случай, если кому-то в голову придёт хорошая идея.
Примечание
Согласно FAQ, исходный код системы AGOV для соответствия юридическим требованиям будет опубликован в конце 2026 года.
«Параграф первый Статьи 9 Федерального закона об использовании электронных средств для выполнения служебных обязанностей гласит:
"Подпадающие под действие этого закона федеральные органы власти обязаны раскрывать исходный код ПО, которое они разрабатывают или разрабатывали для выполнения своих задач, если только права сторонних лиц или безопасность не предотвращают и не ограничивают такое раскрытие".
Согласно этому требованию, исходный код AGOV будет опубликован в декабре 2026 года. Это версия AGOV, представляющая собой достижение целей проекта AGOV, а именно AGOV, включая AGOV e-ID Verifier. Последующие версии будут описаны в примечаниях www.agov.ch/rn, подготовлены к публикации после выпуска и опубликованы».
Возможность изучения исходного кода и отправки баг-репортов напрямую разработчикам помогло бы в моём расследовании, но, к моему сожалению, в AGOV уже есть обязательная система логина в некоторые сервисы ещё до публикации исходного кода!
Позже я вернулся к исходникам; к счастью, поискав строку «field required», я нашёл подсказки по этой логике.
get errorMessage() { return this.translateService.instant( this.emailControl.errors?.required ? "GENERAL.text.fieldRequired" : this.emailControl.errors?.maxlength?.actualLength > ht.Z.EMAIL_MAX_LENGTH ? "GENERAL.text.inputTooLong" : "GENERAL.text.emailAddressError", ); }
Продолжив искать «required», я нашёл эту функцию верификации почты, которая устанавливает код статуса равным «REQUIRED».
onEmailVerification() { var _ = this; return (0, ve.A)(function* () { ((_.showResendSuccessfulBanner = !1), _.verificationCode ? _.verificationCode.length === _.verificationCodeLength ? (_.statusCode.set(""), (_.isLoading = !0), yield (0, Qt.s)( _.registrationService.emailVerification(_.verificationCode, _.userEmail), ) .then((S) => { "VALID" === S.status ? _.onEmailVerificationSuccess() : ((_.showCodeValidation = !0), _.statusCode.set(S.status ?? "WRONG")); }) .catch(() => { (_.statusCode.set("WRONG"), (_.showCodeValidation = !0)); }), (_.isLoading = !1)) : (_.statusCode.set("WRONG"), (_.showCodeValidation = !0)) : (_.statusCode.set("REQUIRED"), (_.showCodeValidation = !0)), _.changeDetectorRef.detectChanges()); })(); }
В конце концов, я нашёл следующую функцию, выполняющую обратный вызов верификации электронной почты.
И тут баг стал для меня очевиден! А вы его нашли?
verificationCodeEntered(_, S) { if ("Shift" === this.lastKeyPressed) return; switch (S.key) { case "Control": case "Meta": case "Shift": case "v": case "r": return; case "Enter": return (S.preventDefault(), void this.emailVerificationCallback()); case "Backspace": return (this.setCode(_, "", "backward"), void S.preventDefault()); case "ArrowLeft": case "ArrowDown": return void this.goBackward(_); case "ArrowUp": case "ArrowRight": case "Tab": return void (_ !== this.codeLength - 1 && (this.goForward(_), S.preventDefault())); } S.preventDefault(); const O = Number(S.key); isNaN(O) ? (this.getInput(_).value = "") : this.setCode(_, String(O)); }
Похоже, эта функция перехватывает нажатия на клавиши и собирает код верификации в локальную переменную JavaScript. То есть мы не можем изменить ввод в DOM после ввода: придётся искать переменную JavaScript и инъецировать его туда!
Самое важное здесь то, что отклоняется нажатие клавиш-модификаторов.
А моя проблема в том... что я пользуюсь стандартной французской клавиатурной раскладкой AZERTY, в которой цифры можно вводить только с удерживаемой клавишей Shift. Иными словами, удерживание Shift блокирует ввод цифр.

Оказалось, что AZERTY — одна из немногих клавиатурных раскладок в мире, в которых цифры вводятся с зажатой клавишей Shift. На работе я пользуюсь английской раскладкой (QWERTY) и не один из тех, кого я спрашивал, не был франкоязычным, поэтому среди всех протестированных компьютеров проблема затронула только два моих «поломанных» ноутбука.

Смена раскладки на программном уровне в моей операционной системе и в самом деле устранила проблему; и наоборот: включение французской раскладки на рабочих устройствах вызывало баг.
Здесь можно задаться вопросом: как так получилось, что швейцарское приложение не работает с французской клавиатурой, ведь французский — один из официальных языков Швейцарии? Дело в том, что в Швейцарии стандартная французская раскладка относится к семейству QWERTZ (распространённому в Центральной Европе), которое позволяет эффективно вводить текст и на французском, и на немецком. Разумеется, в этой раскладке для ввода цифр не нужно удерживать Shift.

Тем не менее, согласно данным Федеральной службы статистики Швейцарии, в стране примерно 171 тысяча жителей французской национальности, не говоря уже об иностранных работниках, которым тоже может понадобиться взаимодействовать с швейцарским правительством. То есть, с точки зрения статистики, меня удивило то, что этот баг всё ещё не заметили и не устранили!
Сменив раскладку клавиатуры, я наконец-то смог валидировать свою почту и зарегистрироваться с ключом безопасности.
Самое странное в том, что мою настоящую личность на протяжении этого процесса никак не верифицировали! Я просто ввёл имя, номер телефона и дату рождения в форму, но они никак не проверялись. Единственное, что валидировала система AGOV — это (1) существование адреса электронной почты и что (2) второй фактор — это ключ безопасности из разрешённой модели или приложение AGOV Access, запущенное на разрешённом устройстве Android или iOS.
Это заставило меня засомневаться в модели угроз и в полезности системы AGOV в целом. AGOV защищена от захвата аккаунта при помощи удалённого фишинга, и это очень важно с точки зрения конфиденциальности! Однако ничто не мешает кому-то другому создать аккаунт и ввести в него мои имя и другие данные.
После более подробного изучения, выяснилось, что для доступа к сервисам, считающимся конфиденциальными, требуется валидация личности. То есть я просто пока не обращался к конфиденциальным сервисам.
Я сообщил о баге в форме службы поддержки, но пока не получил никакого ответа.
Думаю, этот инцидент заслуживает небольшого постмортема, чтобы избежать подобных проблем в будущем.
В чём заключались ошибки?
Создание сложного UI с шестью полями ввода и сложной JavaScript-логикой — ненадёжный способ разработки UI. Просто используйте нативное поле формы браузера и декорируйте его при помощи CSS.
Полное отделение логики формы от DOM вместо чтения значений из DOM при отправке формы. Продвинутый пользователь (или браузерное расширение) даже не сможет отредактировать DOM постфактум, потому что JavaScript-логика уже перехватила нажатия клавиш.
Только один канал поддержки. Для обеспечения надёжности рекомендуется применять второй канал (электронная почта, телефон и так далее).
Использование одной логики для основного продукта и для канала поддержки. Связав их, разработчики теряют баг-репорты от пользователей, которые не могут войти. Именно по этой причине многие Интернет-провайдеры хостят свою страницу статуса на отдельном домене.
Функции поддержки спрятаны за верификацией почты. Возможно, так разработчики будут получать меньше спама, но и меньше отчётов от реальных пользователей.
Отсутствие публикации исходного кода до развёртывания кода и его применения системами. Юридические требования раскрытия исходного кода нужны не просто для галочки, они действительно полезны для устранения проблем, обмена баг-репортами и поиска обходных решений пользователями.
Что было сделано правильно?
Соответствующая JavaScript-логика не была обфусцирована. В конечном итоге, для нахождения бага достаточно было просто поискать нужные строки. Не потребовался ни сложный декомпилятор, ни дорогие LLM. Мне бы понадобилось гораздо больше усилий для анализа логики, если бы она была обфусцирована или скомпилирована в WebAssembly.
Соответствующий код распространялся в открытом вебе, что позволяет скачать его (пусть даже и в минифицированном виде) и исследовать для поиска проблем. Мы воспринимаем это, как нечто само собой разумеющееся, но нам было бы сложно даже получить доступ к коду, если бы он распространялся только для нескольких одобренных огороженных операционных систем.
В чём мне повезло?
Наличие под рукой нескольких ноутбуков с разными раскладками позволило сразу исключить множество гипотез. Маловероятно было, что проблема в браузере или сети. Тем не менее, мне понадобилось довольно много времени на сбор фактов, потому что я методически делал скриншоты каждого сочетания браузера и операционной системы, чтобы убедиться, что ошибка не на моей стороне.
Согласно RFC 6761, домен example.com зарезервирован.