Безопасность
Наша модель безопасности в одном предложении: на сервере нечего атаковать. Всё нижесказанное можно проверить через DevTools вашего браузера.
Архитектура
Abundera QR, это статическое одностраничное приложение, раздаваемое с Cloudflare Pages. Здесь нет сервера приложений, базы данных, учётных записей пользователей, аутентификации, API-эндпоинтов и кода бэкенда, обрабатывающего данные пользователей. Каждая операция генерации QR, кодирования, сканирования и рендеринга выполняется полностью в вашем браузере.
Модель угроз
Поскольку мы не собираем, не храним и не передаём никаких пользовательских данных, наиболее распространённые угрозы для веб-приложений, кража учётных данных, утечка базы данных, перехват сессий, серверные инъекции, к нам неприменимы. Оставшаяся поверхность атаки, это пакет статических ресурсов (HTML, CSS, JavaScript), раздаваемый с нашего источника. При проектировании мы исходим из следующего:
- Компрометация источника: злоумышленник подменяет один из наших bundled-ресурсов вредоносным. Приведённый ниже CSP ограничивает радиус поражения; токены cache-buster упрощают откат к заведомо исправной версии.
- Пользовательский ввод: QR-данные, имена vCard, пароли WiFi, строки CSV в пакетном режиме, содержимое отсканированных изображений. Каждый путь ввода считается ненадёжным при рендеринге обратно в DOM.
- Изображения от пользователя: URL фотографий vCard и загружаемые логотипы. Изображения отрисовываются на canvas и никогда не встраиваются в DOM как необработанная разметка.
- Сторонние расширения браузера: вне области ответственности. Если расширение в вашем браузере имеет разрешение на изменение каждой страницы, оно может изменить и нашу. Наши гарантии действуют при загрузке страницы в неизменённом виде.
Content Security Policy, по директивам
Действующая политика (можно проверить в заголовках ответа на любой запрос):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'wasm-unsafe-eval';
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline';
font-src 'self';
img-src 'self' data: blob: https:;
connect-src 'self' https:;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'Что разрешает каждая директива и где она идёт на компромисс:
default-src 'self', жёсткий пол. Всё, что мы явно не ослабляем, остаётся в рамках одного источника.script-src 'self' 'wasm-unsafe-eval', никаких встроенных<script>, никакогоeval().'wasm-unsafe-eval'требуется для пути компиляции WebAssembly нашей библиотеки QR-кодирования; традиционныйeval()при этом НЕ разрешается. Наша проверка перед деплоем сканирует каждую HTML-страницу на наличие встроенных скриптов и прерывает сборку при их обнаружении.style-src 'self' 'unsafe-inline', реальная уступка. Предпросмотр QR вычисляет пиксельные цвета встроенно (атрибуты стилей на отдельных модулях). Хэш-based список разрешений работал бы, но обновление любого стиля без деплоя вызывало бы сбой. Компромисс: мы принимаем чуть более слабую политику стилей; стилизация всё равно не может утекать данные (у CSS нет доступа кconnect-src).img-src 'self' data: blob: https:,data:для встроенного рендеринга QR,blob:для URL загрузки при экспорте,https:для URL фотографий vCard от пользователя. URL от пользователя никогда не выполняются, только отображаются.connect-src 'self' https:, fetch() ограничен нашим источником плюс HTTPS-запросы, инициированные пользователем (например, сканирование URL фотографии). Предотвращает эксфильтрацию через DNS / WebSocket / beacon на произвольные хосты.frame-ancestors 'none', ни один другой сайт не может встроить нас. Предотвращает clickjacking.base-uri 'self', внедрённый вредоносный тег<base>не сможет перенаправить относительные URL на источник злоумышленника.form-action 'self', любая внедрённая форма может отправлять данные только на наш источник. У нас нет форм, отправляющих данные на сервер; это дополнительная защита.
К /bio/* (ослабленный img-src для аватаров от пользователей) и /embed/* (ослабленный frame-ancestors для намеренного встраивания) применяются отдельные политики CSP. Обе задокументированы в site/_headers.
Заголовки транспорта и фреймирования
- HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, 1 год, все поддомены, соответствует требованиям HSTS preload list. Переход на HTTP отклоняется совместимыми браузерами. - X-Frame-Options: DENY, избыточен при наличии CSP
frame-ancestors, оставлен для поддержки старых браузеров. - X-Content-Type-Options: nosniff, предотвращает атаки MIME confusion.
- Referrer-Policy: strict-origin-when-cross-origin, при переходе по внешним ссылкам передаётся только источник, но не путь.
- Permissions-Policy:
camera=(self), microphone=(), geolocation=(), камера разрешена только для нашего собственного сканера; микрофон и геолокация явно запрещены даже при встраивании.
Сервис-воркер
Наш сервис-воркер (site/sw.js) кэширует только ресурсы из того же источника. Обработчик fetch явно отклоняет кросс-доменные запросы и методы, отличные от GET, логику можно прочитать на GitHub. Запись в кэш обёрнута в event.waitUntil(), поэтому она не может быть прервана в процессе навигации.
Санитизация входных данных
Каждый путь рендеринга, принимающий пользовательский ввод, обрабатывает его как ненадёжный текст:
- Предпросмотр QR-данных использует
textContent, а неinnerHTML. - Цели шаринга (буфер обмена,
navigator.share()) передают текст пользователя как строку, никогда как разметку. - SVG-экспорт формируется нашей библиотекой кодирования; пользовательский контент кодируется в base64 в
xlink:hrefэлемента<image>, а не внедряется как SVG-элементы. - Предпросмотр печати использует blob URL, а не
document.write(). - Разбор localStorage обёрнут в
try/catch, повреждённая запись возвращает чистое пустое значение по умолчанию, а не исключение, способное раскрутить живой путь кода. - URL от пользователей, отображаемые как кнопки "Открыть ссылку", ограничены схемой
http(s)://, схемыjavascript:иdata:отклоняются.
Загрузка изображений с внешних ресурсов
Когда пользователь вставляет URL вида https: в качестве фотографии vCard или логотипа, браузер загружает его с учётом CORS и списка разрешений img-src нашего CSP. Изображение отрисовывается на canvas. Оно никогда не становится живым DOM, не выполняется как код и не достигает нашего источника, запрос идёт от браузера к удалённому изображению, а результат отображается на стороне клиента. Злоумышленник, контролирующий URL удалённого изображения, может отследить факт загрузки URL (строка в логе собственного сервера), но не способен извлечь что-либо с нашей страницы.
Subresource Integrity (SRI)
Весь JavaScript и CSS, который мы поставляем, является одноисточниковым. Мы не загружаем сторонние скрипты или таблицы стилей, поэтому SRI-хэши не применимы. Если мы когда-либо загрузим сторонний ресурс, мы добавим к нему атрибут SRI integrity и задокументируем процесс обновления хэша на этой странице.
Сообщение об уязвимости
Если вы обнаружили проблему безопасности, затрагивающую Abundera QR, в нашем коде, развёртывании или используемых зависимостях, пожалуйста, сообщите об этом конфиденциально по адресу security@abundera.ai. Наша цель, провести оценку в течение 72 часов. Вы также можете связаться с нами через контактные данные в файле /.well-known/security.txt.
Программы вознаграждений пока нет
В настоящее время мы не предлагаем денежных вознаграждений, но каждый подтверждённый действительный отчёт получает упоминание в журнале изменений и нашу публичную благодарность.
Проверьте всё вышесказанное
Каждое утверждение на этой странице можно проверить через DevTools браузера, не доверяя нам на слово:
- CSP: DevTools → Network →
/→ Headers. Прочитайте заголовок ответаContent-Security-Policy. - HSTS и заголовки безопасности: то же место, все
Strict-Transport-Security,X-Frame-Optionsи другие видны там. - Нет исходящих запросов: DevTools → Network → Fetch/XHR. Сгенерируйте QR. Убедитесь, что счётчик остаётся на нуле.
- Область сервис-воркера: DevTools → Application → Service Workers. Проверьте источник скрипта и список кэшированных ресурсов.
- Нет куков: DevTools → Application → Cookies. Пусто.
- Полное руководство: раздел верификации манифеста.
Контакты
Сообщения об уязвимостях: security@abundera.ai