Дежурный вставляет одноразовую ссылку в канал Slack или в рабочий чат Telegram. Сначала вылезает карточка с названием сайта. Через десять минут получатель открывает адрес — и видит «Этот секрет уже уничтожен». Оба уверены, что открытый текст никто не читал. Первый GET, который дошёл до хоста, часто был не пальцем коллеги. Это мессенджер запросил страницу, чтобы нарисовать превью.
В предыдущей заметке разобрали, почему ключ одноразовой ссылки ставят после #: там вопрос, видит ли HTTP-хост с шифротекстом сам ключ. Здесь другой: пока никто не кликнул, может ли превью потратить единственное чтение. Ключ по-прежнему после #. Создать и открыть можно без аккаунта. У одноразовой ссылки MyPassGen число чтений по умолчанию 1, максимум 10. Ниже нет обхода кнопок на странице создания — только то, что видно во вкладке «Сеть», в адресной строке и в окне чата.
Сначала разделите два факта
Сожжёт ли превью шифротекст — и останется ли в истории чата полный адрес — разные вопросы. Превью, которое берёт только HTML и не запускает скрипты страницы, обычно не читает ключ после # и не доходит до запроса шифротекста. В канале при этом лежит целая строка s.html?id=…#…. Кто скопировал — тот откроет. Превью не сожгло секрет — это ещё не значит, что ссылка перестала быть допуском.
Пишут «уничтожена», вы не кликали
Обычная схема одноразовой ссылки такая: первый успешный забор шифротекста — и сервер его стирает. При лимите чтений 1 эта одна выборка и есть последняя копия. Между экраном «создано» у отправителя и состоянием «уничтожена» у получателя могут вклиниться визиты, которые никто не кликал: превью канала, сканер корпоративной почты, шлюз, который переписывает URL и затем сам его зондирует.
Slack это описывает в продуктовой документации. В Unfurling links in messages сказано: когда пользователь вставляет ссылку в сообщение, Slack запрашивает страницу и прикрепляет превью. Официальная страница robots называет бота, который делает этот запрос, Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots), и пишет: он берёт как можно меньше страницы (через HTTP Range), чтобы вытащить oEmbed, Twitter Card и Open Graph; если теги указывают на картинку, видео или звук, файл забирают отдельно — для проверки. В Slack Robots ещё сказано: ответ на один и тот же URL кэшируется по сервису примерно 30 минут; robots.txt они не соблюдают — это запрос от имени пользователя, который уже вставил адрес, а не обход всего сайта поисковиком.
Значит, карточка в канале доказывает только одно: какая-то машина, которая не является получателем, уже отправила хотя бы один HTTP-запрос на вставленный адрес. Она не доказывает, что открытый текст прочитали. Она не доказывает, что шифротекст засчитали как чтение. Чтобы понять, сожжено ли, нужно увидеть, какой слой этот запрос задел.
Какой кусок адреса уходит в превью
Полная ссылка, которую вы копируете, выглядит как …/s.html?id={id}#{key}. id после вопросительного знака входит в строку HTTP-запроса. Ключ после решётки протокол оставляет клиенту. Это RFC 3986 §3.5 и RFC 9110 §7.1: целевой URI не включает фрагмент, потому что этот кусок обрабатывает клиент. Бот превью делает обычный HTTP GET. Законная строка запроса — GET /ru/s.html?id=…. Хвост после решётки до машины с шифротекстом не доезжает.
Поэтому превью «только HTML» обычно не расшифровывает секрет. Страница чтения должна сначала прочитать location.hash скриптом браузера, затем запросить шифротекст у сервера и уже на этом устройстве раскрыть AES-256-GCM. У краулера превью нет ключа из фрагмента — только номер. Даже если он сохранит весь HTML s.html, внутри нет пароля. Страница чтения — посадочная с noindex. В исходном HTML стоит общий заголовок «Одноразовая ссылка · MyPassGen». Открытый текст появляется на странице только после того, как скрипт отработал.
Мессенджер хранит другой объект. Пользователь вставил всю строку, включая # и ключ. Поиск по каналу, синхронизация сообщений, открытие той же переписки с нового телефона снова отдают полный допуск. Ключ после # убирает его из GET превью и из типичного журнала сервера. Он не убирает полный URL из истории группы. Для более чувствительной передачи номер и ключ можно разнести по двум каналам — об этом ниже.
Три захвата, считает только один
RFC 9110 определяет GET как безопасный метод: отправка не должна давать разрушительный побочный эффект. Забрать страницу только чтобы нарисовать карточку, по этой семантике не должно стирать шифротекст. На практике одноразовый инструмент, который считает «первый GET документа» уже чтением, даст боту превью сжечь секрет раньше получателя. Разница не в названии мессенджера. Разница в том, к какому запросу привязан счётчик.
Разложите захваты по тому, что они умеют выполнить. Первый тип берёт начальный HTML, читает <title> и Open Graph и не запускает скрипты страницы. Официальное описание Link Expanding у Slack относится сюда: вытащить метатеги, затем при необходимости забрать медиа, на которые теги указывают. В докладе Apple Ensuring Beautiful Rich Links сказано: при сборке rich link JavaScript не выполняется, поэтому Open Graph должен лежать в исходнике страницы. Встраивания в каналах Discord обычно устроены так же: Discordbot/2.0 читает начальный HTML, а не поднимает полный движок браузера. У Telegram для карточки ссылки сервер тоже запрашивает страницу и собирает превью из метатегов; часто виден User-Agent TelegramBot (like TwitterBot). Это наблюдение в логах и у вебхуков, не страница robots уровня Slack — поэтому ниже решающим остаётся результат тестовой ссылки, а не чужой User-Agent.
Второй тип запускает скрипты страницы, но в запросе по-прежнему нет фрагмента. Сюда садятся headless-браузеры и часть корпоративных песочниц. Они могут выполнить JavaScript страницы чтения и всё равно не прочитать location.hash. На этом сайте нет ключа — нет забора шифротекста. Страница должна остановиться на «Ссылка неполная». Счётчик чтений не должен сдвинуться.
Третий тип запускает скрипт и загружает полный URL — включая решётку — в настоящую страничную среду. Отдельные средства защиты конечной точки, которые открывают «ссылку, по которой вы сейчас кликнете» в локальном WebView, или превью, которое полностью грузит страницу на устройстве отправителя, могут дойти до «забрать шифротекст и увеличить счётчик». Протокол этот класс не закрывает. Его можно только воспроизвести тестовой ссылкой на том же канале.
Slack, Telegram, WeChat и почта
Slack проверить проще всего. Официальная страница robots публикует User-Agent. Этой же строкой можно запросить страницу чтения и увидеть: ответ — статичный HTML, и сдвинулся ли потом счётчик. Slack ещё документирует глобальный кэш примерно на 30 минут: один и тот же URL, вставленный за короткое время в несколько каналов, не каждый раз снова бьёт в ваш origin. Заголовок сайта на карточке значит только то, что прочитали метатеги или <title>. Стандартный заголовок русской страницы чтения — «Одноразовая ссылка · MyPassGen». Тестовой фразы там нет.
Telegram для русскоязычных команд обычно важнее WeChat. У него нет такой же публичной страницы robots, как у Slack, поэтому возможности внутреннего краулера нельзя читать как договор. Что видно сразу: после вставки адреса появляется ли карточка с заголовком; после клика получателя страница чтения показывает исходник, «Ссылка неполная» или «Этот секрет уже уничтожен». Карточки в Telegram собирают на стороне сервера из Open Graph и заголовка, а не тем, что на телефоне получателя сначала прогоняют скрипт страницы чтения. Instant View — отдельная история: это шаблон статьи внутри клиента, не обычное превью ссылки; посадочная s.html с noindex для Instant View не предназначена. Кэш превью у Telegram живучий: уже отправленное сообщение карточку само не обновляет; служебный @WebpageBot пересобирает превью для новых отправок, не для старых. Без публичной спецификации верьте результату своей тестовой ссылки. «Карточка появилась» — не «шифротекст забрали».
WeChat публичной страницы robots тоже не даёт. Если ссылку всё же нужно отдать китайским коллегам в WeChat, правило то же: смотрите карточку и повторное открытие полной ссылки, не чужое описание бота. Типичная сборка карточки в таких мессенджерах — сервер забирает HTML-заголовок и краткий текст, а не прогон скрипта на телефоне получателя. WhatsApp и карточки ВКонтакте проверяйте тем же тестом: нет публичного контракта — решает тестовая ссылка.
Корпоративная почта — четвёртый путь, и в российских офисах он часто Outlook. Microsoft в обзоре Safe Links пишет: входящую почту сканируют, URL могут переписать; клик проверяют ещё раз. Переписанные адреса несут префикс вроде safelinks.protection.outlook.com. И скан при доставке, и редирект при клике могут снова сделать GET на цель. Сверьте два места: обёрнут ли уже видимый текст, и остался ли в адресной строке после клика кусок после #. Если ключ потерялся при переписывании или редиректе, страница чтения должна сказать «Ссылка неполная», а не расшифровать секрет. Если полный URL — включая решётку — попал в песочницу, которая выполняет скрипт, чтение может засчитаться. Не считайте, что каждый шлюз «смотрит только HTML». Ссылки в каналах Microsoft Teams могут идти тем же путём; политика зависит от тенанта.
Какие запросы делает страница чтения
Когда открывается страница чтения MyPassGen, скрипт делает два шага подряд. Сначала по номеру спрашивает статус: секрет ещё здесь, уже уничтожен или истёк, — и этот запрос счётчик не увеличивает. Без ключа после # на этом всё: страница пишет «Ссылка неполная». С ключом затем уходит GET шифротекста. Именно он увеличивает read_count. Когда число достигает заданного лимита (по умолчанию 1, максимум 10), сервер стирает шифротекст. Следующее открытие — «уничтожена». Срок следует TTL, который выбрали при создании: 1 час, 24 часа, 7 дней — или только по числу чтений, без TTL.
Значит, превью, которое только делает GET s.html?id=…, и сканер, который бьёт только в статус, это одно чтение не тратят. Тратит запрос шифротекста от «скрипта страницы чтения, у которого ключ уже есть». При создании браузер шифрует на этом устройстве AES-256-GCM, IV 12 байт, открытый текст не больше 32 КБ. Сервер временно держит только шифротекст. Ключ в строку HTTP-запроса не входит. Как сверить алгоритм и то, что исходник не ушёл, — в заметке, как на месте проверить, что шифрование в браузере не отправило открытый текст.
Отдельной кнопки «Показать» на странице чтения нет. Если ключ на месте, открытие вкладки забирает шифротекст. Это не противоречит «краулеры превью берут только HTML» — до забора они обычно не доходят. Противоречие сидит в третьем классе: полный URL попал в среду, которая выполняет скрипт. Тогда это выглядит как человек, открывший вкладку, и ссылка с лимитом 1 потрачена. По оформлению карточки класс не узнать. Узнать можно по статусу — как в следующем разделе.
Превью не сожгло — в переписке всё ещё допуск
Полная ссылка в канале Slack, в чате Telegram, в WeChat или в почтовой ветке — всё ещё допуск: один шаг до открытого текста. Кто найдёт это сообщение поиском, тот откроет, пока ничего не уничтожено. Закрытое окно инкогнито не забирает полный URL, который уже лежит в закладке или в «Загрузках». См. после закрытия окна инкогнито пароль остаётся в загрузках, закладках и буфере обмена.
Что остаётся после превью
Одна и та же тестовая ссылка, число чтений 1: после превью отправитель и получатель видят не одни и те же объекты. Таблица говорит о страницах, которые можно открыть, — не о слоганах продукта.
| Что произошло | Что обычно видно в чате | Полная ссылка после этого должна показать |
|---|---|---|
| Только HTML-мета, без скрипта | Карточка с заголовком, без тестовой фразы | Всё ещё расшифровывает тестовую фразу |
Скрипт отработал, но в запросе не было # |
Карточка или пустое превью; на сервере — запрос статуса | Всё ещё расшифровывает; визит без ключа — «Ссылка неполная» |
| Полный URL попал в среду, которая выполняет скрипт | Карточка или отчёт сканера; чтение уже потрачено | «Уничтожена» или истекла, открытого текста нет |
| Почтовый шлюз переписал URL и потерял фрагмент на редиректе | В теле обёртка вроде safelinks… |
«Ссылка неполная»; шифротекст обычно ещё на месте |
Четвёртую строку не смешивайте с третьей. Если ключ потерялся, получатель секрет не откроет, а отправитель всё ещё откроет исходный полный URL — счётчик не потрачен, у получателя просто обрезанный адрес. Если ключ на месте, а страница уже «уничтожена», превью или сканер прочитали первыми. В первом случае достаточно дослать кусок после решётки. Во втором нужна новая ссылка. Резервной копии открытого текста на сервере нет, почты поддержки, которая вернёт сожжённое, тоже нет.
Сверка на месте
Эти шаги не опираются на обещание бренда. Берите тестовую фразу, которой нельзя войти ни в один живой аккаунт, например orange-lake-7. Не тренируйтесь на текущем мастер-пароле, на боевом ключе и на настоящей одноразовой ссылке.
- Откройте одноразовую ссылку, вставьте тестовую фразу, срок — 24 часа, число чтений оставьте 1, создайте ссылку. Запишите полный адрес и убедитесь, что форма
s.html?id=…#…. Аккаунт не нужен. - Скопируйте только часть до решётки. Из терминала запросите этот URL с User-Agent Slack, например
curl -A "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)" "https://mypassgen.com/ru/s.html?id=…". Затем той же частью — сcurl -A "TelegramBot (like TwitterBot)" "https://mypassgen.com/ru/s.html?id=…". Ответ должен быть HTML страницы чтения. В заголовке или теле не должно быть тестовой фразы. Это доказывает только «забор документа не видит открытый текст». Живой чат это не заменяет. - Откройте тот же адрес «только id, без ключа» в браузере. Должно быть «Ссылка неполная», не исходник. Откройте инструменты разработчика, вкладку «Сеть», включите «Сохранять журнал»: должен быть запрос статуса, не должно быть последующего успешного забора шифротекста.
- Сразу откройте полную ссылку (с
#) в другой вкладке. Должна расшифроваться тестовая фраза. Если предыдущий шаг уже сжёг её, реализация или промежуточное устройство сочли визит без ключа чтением — остановитесь. Живой секрет не отправляйте. - Создайте ещё одну тестовую ссылку с числом чтений 1. Вставьте полный URL в канал Slack, которым вы управляете, в «Избранное» Telegram или в закрытый тестовый чат. Если работаете с WeChat — в «Передачу файлов» или тестовую группу. Дождитесь карточки превью (или убедитесь, что её нет). Страницу чтения не открывайте.
- Когда превью появилось, откройте ту же полную ссылку в браузере на компьютере. Если всё ещё расшифровывает — превью на этом канале из первых двух классов. Если уже уничтожена — где-то в середине отработал третий класс, либо одно из ваших устройств заранее загрузило страницу с ключом. Чтобы передать снова, создайте новую. После чтения перезапишите буфер, как в заметке, кто ещё читает буфер обмена после копирования пароля. Адрес с
#в закладки не кладите.
Для рабочей почты добавьте полшага: отправьте тестовую ссылку себе на корпоративный ящик, посмотрите, не обернули ли тело в адрес вида Safe Links, затем — остался ли ключ в адресной строке после клика. Ключ пропал — номер шли почтой, ключ — голосом. Сожгли — поднимите число чтений или смените канал. MyPassGen не классифицирует чужой шлюз за вас. Верьте тем окнам, которые только что открыли.
Если всё же слать в канал с карточками
Лимит 1 подходит под схему «у человека полный адрес, он открывает сразу, потом ссылка пропадает». Для канала, большой группы или рассылки, которая рисует карточки, сначала прогоните предыдущий раздел на тестовой ссылке. Убедитесь, что превью не считает, и только потом шлите настоящий секрет. Если тест уже сгорел, не ждите, что большее число чтений сделает превью «безопасным». Лимит 2 оставляет ещё одно человеческое открытие. Сканер, который каждый раз выполняет скрипт, всё равно может исчерпать счётчик.
Надёжнее разнести по двум каналам. В одном сообщении только s.html?id=…. Превью тогда получит номер и общий заголовок — максимум. Во втором — звонок, лично или другой аккаунт мессенджера — только кусок после #. По половине не расшифровать. Это приём использования, не настройка продукта по умолчанию. Страница создания по-прежнему выдаёт одну полную ссылку — так удобнее отдать один на один.
Пароль собирайте на этом устройстве. Не пишите его в группу, чтобы потом удалить сообщение. Генератор MyPassGen в случайном режиме даёт 6–128 символов, по умолчанию 16, ниже 8 предупреждает о слабости. Открывается без аккаунта. Результат как рабочие данные не уходит. Пакет сертификатов или выгрузка больше 32 КБ на текстовую одноразовую ссылку не помещаются. Берите шифрование файлов: потоковый AES-256-GCM в браузере, один файл до 5 ГБ, на выходе .lock / .enc, пароль — отдельно. Облако держит только шифротекст. См. перед тем как бросить файл в облако: кто получит открытый текст.
Когда вы сверили «полная ссылка после превью всё ещё расшифровывает» и «в переписке остался полный допуск», на вопрос статьи уже можно ответить. Превью, которое берёт только HTML, обычно не сжигает одноразовую ссылку этого сайта раньше вас. Захват, который выполняет скрипт и несёт #, сжигает. Сама карточка класс не называет. Называют страница статуса и второе открытие.
Частые вопросы
В канале Slack уже висит карточка. Ссылка мертва?
Не обязательно. Документация Slack пишет, что Link Expanding вытаскивает метатеги и через Range берёт как можно меньше страницы. Карточка доказывает только то, что HTML страницы чтения забрали. Откройте полную ссылку ещё раз: всё ещё расшифровывает — счётчик цел; уничтожена — создайте новую. Не обновляйте один и тот же URL в надежде, что секрет вернётся.
У Telegram и WeChat нет страницы robots. Какой фразе верить?
Верьте результату этой тестовой ссылки. Карточка с заголовком значит только то, что какая-то программа прочитала заголовок или краткий текст. До клика получателя откройте полную ссылку сами: тестовая фраза на месте — превью на этом канале шифротекст не забирало; уже уничтожена — разнесите URL или отправьте один на один.
Если поставить 2 или 3 чтения, превью проиграет?
Это только лишние слоты забора. Превью от этого не становится безопасным методом. Сканер, который каждый раз выполняет скрипт с ключом, всё равно потратит счётчик. Сначала тестовая ссылка с 1 чтением покажет, из первых ли двух классов превью. Если канал нестабильный и другого нет — поднимайте число и принимайте, что открыть может ещё один человек.
Нужен ли аккаунт, чтобы создать и открыть? Считается ли бот превью читателем?
Регистрация не нужна. И создание, и чтение открыты посетителю. Бот превью, который берёт только HTML, — не чтение. Если он отправил GET шифротекста, сервер не отличит его от человека, и счётчик всё равно упадёт. Страница чтения открыта получателю. Входа нет, почты поддержки, которая вернёт уничтоженное, тоже нет.