Сторонний скрипт это классическая сделка: не хостим сами, отдаём проверенной сети раздачи, и годами верим, что по ссылке лежит то, что положили. Сделка ломается в одну ночь, когда сеть раздачи или upstream автора оказывается скомпрометирован, и миллионы страниц одной подписью получают изменённый файл: собранный сборщиком оригинал дома, а с сети едет что-то с сюрпризом. Subresource Integrity закрывает этот сценарий тремя строчками в разметке: страница объявляет криптографический отпечаток ожидаемого файла, браузер сверяет приехавшее с ожидаемым, и чужой сюрприз не исполняется.

Статья разбирает SRI от угрозы до конвейера: модель атаки и доверие к сетям раздачи, механику атрибута integrity, генерацию отпечатков с дисциплиной пиннинга версий, требование перекрёстного атрибута для чужих ресурсов, честные границы приёма, стратегию отказов сети раздачи и автоматизацию в сборке. С командами и разметкой, которые ставятся за вечер, и с границами, о которых узнают до инцидента, а не во время него.

Атака на чужой скрипт почему доверенный источник перестаёт быть доверенным

Третья сторона в цепочке скриптов это не один субъект, а цепочка: автор библиотеки, его хранилище, сеть раздачи, кеши по пути, и у каждого звена своя поверхность компромисса. История жанра знает и взломы аккаунтов авторов пакетов, и компрометацию сетей раздачи, и злонамеренные обновления внутри популярных библиотек, и сценарий каждый раз один: страница честно подключает ссылку, ссылка честно отвечает, содержимое оказывается изменённым.

Особенность угрозы в безбилетности: страница ничего не меняла, и следующий релиз продукта не несёт злонамеренного кода. Атака живёт между релизами, в браузере каждого посетителя, и стандартные конвейеры контроля кода её не видят вовсе, потому что их код дома цел. Требуется механизм, проверяющий содержимое в момент исполнения в браузере, и это ровно жанр SRI.

Модель доверия после осознания угрозы честно пересматривается: доверие к источнику это не бинарное свойство, а договор с запасным выходом. Сеть раздачи остаётся полезной, автор библиотеки остаётся уважаемым, и дополнительная проверка отпечатка закрывает не всех врагов, а конкретного: того, кто изменит файл по пути к браузеру.

Экономика приёма приятна: три атрибута в разметке, ноль кода, проверка исполняется браузером без затрат продукта. Против стоят дисциплина версий и следствия для обновлений, и весь разбор дальше это баланс между защитой и издержками её содержания.

Механика integrity хеш ресурса и решение браузера

Атрибут integrity несёт один или несколько отпечатков ожидаемого файла: алгоритм, дефис, значение в кодировке, и браузер после загрузки ресурса вычисляет отпечаток по объявленному алгоритму и сравнивает с объявленным значением. Совпадение означает исполнение, несовпадение означает полный отказ: файл не исполняется, ошибка уезжает в консоль и в обработчики загрузки, и страница узнаёт о подмене так же, как об отсутствии сети.

<script
  src="https://cdn.example.net/lib/2.3.1/app.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6_R9QqYmV66GjGa1C0vV0T6XNqK2TzrPzYvlNqM"
  crossorigin="anonymous"
></script>

Поддерживаемые алгоритмы устоялись на семействе безопасных хешей: значения в разметке современных страниц это варианты второго и третьего поколений стандарта, и выбор алгоритма это выбор консерватизма: старшее поколение достаточно, среднее осторожнее, слабейшие отбрасываются самими браузерами. Несколько отпечатков через пробел позволяют миграцию между алгоритмами без разрыва, и слабый отбрасывается после расползания свежего.

Отказ исполнения ведёт себя как обычная ошибка загрузки: события ошибок срабатывают, фолбэки и обработчики работают штатно, и страница обязана воспринимать отказ по отпечатку как нормальный отказ сети с понятным поведением. Отличие в логировании: причина отказа видна в сообщении браузера, и мониторинг фронтенда отличает подмену от недоступности, потому что реагировать на них следует по-разному.

Контроль целостности не заменяет шифрование канала: защищённый протокол не даёт изменить файл в пути, и SRI закрывает сценарий изменения до пути и внутри хранилищ. Совмещение шифрования канала и отпечатка содержимого складывается в перекрытие слоёв, и привычка требовать оба это гигиена, а не паранойя.

Генерация хешей и пиннинг версий дисциплина внешних зависимостей

Отпечаток вычисляется над точным содержимым файла одной командой, и генерация встроена в рабочие утилиты криптографии любой машины: алгоритм, файл, кодирование в одну строку, и значение готово для разметки.

openssl dgst -sha384 -binary app.js | openssl base64 -A

Отпечаток жёстко привязан к содержимому, и это свойство оборачивается дисциплиной версий: плавающая ссылка на самую свежую версию сети раздачи гарантирует разрыв проверки при любом обновлении библиотеки, потому что содержимое изменилось, а отпечаток остался. Ссылки обязаны быть закреплёнными за конкретной версией, обновление зависимости это обновление отпечатка, и процесс обновления встраивается в конвейер наряду с обычными зависимостями.

Хешированный контроль меняет ритм обновлений внешних библиотек: раньше обновление на плавающей ссылке происходило само и незаметно, теперь каждое обновление это решение с правкой разметки. Издержка оборачивается бонусом, знакомым по контролю зависимостей серверного кода: изменения внешнего кода перестают происходить без ведома команды, и каждая новая версия проходит глазами, а не капризом сети.

CORS и SRI почему чужие скрипты требуют перекрёстного атрибута

Проверка отпечатка для ресурсов со своего источника работает без церемоний, но для чужих по источнику скриптов и стилей браузер требует честного перекрёстного объявления: атрибут перекрёстного запроса в разметке обязателен, и его отсутствие отключает проверку целиком. Требование выглядит капризом, а объясняется моделью приватности: без перекрёстного атрибута запрос чужого ресурса это анонимный запрос, ответ кешится и раздаётся без координации с правами источника, и молчаливая проверка отпечатка открывала бы утечку информации об изменениях чужих файлов.

Перекрёстный атрибут со значением анонимного запроса координирует кеширование между запросами страницы и самим ресурсом, и проверка отпечатка работает в предсказуемом режиме. Значение атрибута одно для скриптов с отпечатками, и добавление его в разметку это строчка, которая либо есть, либо отсутствует: полумодов не бывает, и забытый атрибут это отключённая проверка, а не ослабленная.

Стилевые ссылки со своих сетей раздачи живут по тому же правилу, и целостность стилей не менее важна: изменённый файл стилей это поверхность для подмен содержимого без единой строки кода, и отпечаток на ссылке стиля закрывает жанр целиком. Шрифты, стили, скрипты: вся внешняя статика получает одинаковый контроль.

Проверка на собственной среде обязательна после включения: страница без консоли ошибок это страница с работающими отпечатками, и единственный надёжный способ убедиться в работоспособности это намеренная порча отпечатка в тестовой среде с наблюдением отказа. Тест на отказ это часть внедрения, потому что защита, которая не проверена на срабатывание, это декларация.

Границы защиты что SRI не закрывает и что закрывает CSP

Первая граница фундаментальна: отпечаток защищает содержимое файла по ссылке, и не защищает страницу, которая сама контролируется атакующим. Захват разметки страницы означает удаление атрибута целиком, и SRI это контроль содержимого, а не контроль разметки. Контроль разметки и списка разрешённых источников живёт в политике безопасности содержимого, и жанры дополняют друг друга: политика говорит какие ссылки допустимы, отпечаток говорит какое содержимое допустимо.

Вторая граница: отказ сети раздачи не лечится отпечатком, файл честен и недоступен, страница без стратегии запаса остаётся без скрипта. Граница не унижает приём, она требует второй механики из следующего раздела, и внедрение SRI без фолбэка это обмен тихой подмены на громкую пустоту.

Третья граница: отпечаток фиксирует файл, но не его поведение в контексте страницы: честная версия библиотеки с уязвимостью остаётся уязвимой, и контроль версий зависимостей никуда не уезжает. SRI это замок на соответствие, а не аудит качества, и гигиена обновлений сохраняет собственную ценность.

Политика безопасности содержимого добавляет к отпечаткам собственные механизмы: нонсы и хеши встроенных скриптов контролируют происхождение кода в самой разметке, белые списки ограничивают источники, и связка политики с отпечатками закрывает оба этажа: и откуда приехал код, и что именно приехало. Зрелая страница носит оба слоя, и их сочетание это норма, а не избыточность.

Отказ CDN и стратегия запасных путей страница обязана жить

Отказ чужого ресурса по любым причинам, компрометация с блокировкой отпечатка, авария сети, пропажа файла, обязан переживаться страницей, и стратегия запаса встраивается на уровне разметки: резервные пути объявляются рядом с основным, браузер перебирает их по порядку при отказе, и каждый резерв несёт собственный отпечаток.

<script
  src="https://cdn.example.net/lib/2.3.1/app.js"
  integrity="sha384-PrimaryHash"
  crossorigin="anonymous"
></script>
<script>
  if (!window.AppLoaded) {
    document.write(
      '<script src="/vendor/2.3.1/app.js"><\/script>'
    );
  }
</script>

Локальный резерв с собственным хостингом это вершина стратегии: основной путь чужой ради скорости, запасной свой ради выживания, и отпечатки обоих хранятся в одном месте сборки. Проверка выживания обязательна: страница после отказа основного и успеха запасного обязана поднять мониторингом флаг, потому что хроническая жизнь на резерве это хроническая проблема, а не норма.

Встраивание в конвейер генерация атрибутов при сборке и проверка релиза

Ручное ведение отпечатков в шаблонах обречено: значения длинные, версии меняются, копипаст ошибается, и зрелый конвейер генерирует разметку автоматически. Сборка знает версии внешних зависимостей, вычисляет отпечатки над файлами пакетов и подставляет атрибуты в шаблоны, и изменение версии тянет за собой пересчёт без человеческого участия.

Манифест внешних скриптов становится единым источником: путь, версия, отпечаток, резерв, и разметка страниц это производная манифеста. Ревью изменения внешней библиотеки это дифф манифеста: версия, отпечаток, источник изменений, и осознанность обновлений возвращается в продукт через обычный процесс ревью.

Проверка релиза завершает конвейер: собранная страница прогоняется на совпадение атрибутов манифесту, отсутствие отпечатка на внешнем скрипте это ошибка сборки, и правило закрепляется проверкой в конвейере. Новая внешняя зависимость без манифеста просто не доезжает до продакшена, и гигиена становится свойством процесса, а не памятью разработчиков.

Порядок внедрения SRI свёрнут в короткую лестницу:

  1. провести инвентаризацию внешних скриптов и стилей с их сетями раздачи;
  2. закрепить ссылки за версиями и завести манифест внешних зависимостей;
  3. генерировать отпечатки при сборке и подставлять атрибуты автоматически;
  4. добавить перекрёстный атрибут на все чужие ресурсы без исключений;
  5. прогнать тест порчи отпечатка и убедиться в отказе исполнения;
  6. встроить резервы с собственными отпечатками и мониторингом их доли;
  7. проверять сборку на совпадение манифесту перед каждым релизом.

Целостность внешних ресурсов это тот редкий случай, когда безопасность добавляется тремя атрибутами и одним конвейером, и цена атрибутов несоизмерима с ценой одного исполнённого чужого сюрприза в браузере клиента. Страница, которая знает отпечатки всех своих внешних гостей, перестаёт доверять вслепую и продолжает доверять с проверкой, и в этом различии живёт вся разница между наивной и взрослой зависимостью от чужого кода. А проверенный отказ в тесте превращает три атрибута из декларации в работающий механизм защиты, и именно так зреет доверие к каждому слою.