Интеграция Google Tag Manager (GTM) с Content Security Policy
- понедельник, 31 марта 2025 г. в 00:00:10
Примечание: Пост предназначен для веб-аналитиков и специалистов, работающих с GTM, поэтому не несет в себе подробных инструкций по имплементации на стороне разработки.
Соответствие политикам Content Security Policy (CSP) при использовании Google Tag Manager (GTM) на сайте может быть сопряжено с рядом сложностей.
CSP блокирует загрузку внешних ресурсов, выполнение inline-скриптов и стилизации, что может вызвать сбои в работе GTM: некорректную загрузку тегов, проблемы с передачей данных или отсутствие панели отладки.
Ниже рассмотрены основные проблемы в работе GTM и возможные способы их решения. Некоторые из них решаются совместно с отделом разработки и безопасности, а некоторые решаются на стороне самого GTM.
GTM использует inline-скрипты и inline-стили, но CSP может их блокировать. Это может повлиять как на работу тегов, так и на корректное отображение самого интерфейса режима превью GTM.
Важно отметить, что проблема с блокировкой стилей затрагивает не только визуальное отображение интерфейса превью GTM. Многие рекламные пиксели вставляют на страницу <noscript>-блоки или скрипты, которые используют inline-стили. При жёсткой CSP такие элементы также могут быть заблокированы, что повлияет на корректную работу пикселя и сбор данных.
Решение:
Необходимо обновить политику CSP на стороне разработки, разрешив выполнение inline-кода с использованием безопасных механизмов (учитывая особенности GTM Preview, в котором не все методы применимы).
Рекомендуется добавить nonce к доверенному загрузчику скриптов (например, gtm.js или тегу, вставляющему его). В сочетании с директивой strict-dynamic это позволяет:
Разрешить выполнение как основного скрипта, так и всех дочерних скриптов, загружаемых по цепочке
Косвенно разрешить выполнение inline-скриптов GTM Preview, если они добавляются доверенными скриптами
Директива strict-dynamic включает наследование разрешений всем скриптам по цепочке. Это означает, что если исходный доверенный скрипт (например, a.js) динамически загружает b.js, а тот, в свою очередь, — c.js, то при наличии strict-dynamic выполнение будет разрешено для всех трёх. В отсутствие этой директивы будет выполнен только изначально доверенный скрипт (a.js), а последующие — заблокированы политикой безопасности.
Добавить style-src 'unsafe-inline' только если нет иной возможности, либо аналогичный механизм с nonce для <style>.
CSP может ограничивать загрузку внешних скриптов, изображений и iframe, используемых в GTM. Это приводит к тому, что аналитические теги не срабатывают, рекламные пиксели не отправляют события, а данные о действиях пользователя теряются.
Пример ошибки, которая может появиться в консоли браузера при попытке отправки аналитических данных из GTM:
Такие ошибки означают, что браузер заблокировал отправку данных, потому что политика CSP запрещает подключение к этому ресурсу.
Решение:
Команда разработки должна обновить политику CSP, добавив разрешения (в connect-src, img-src, frame-src и других директивах) на загрузку ресурсов со ВСЕХ доменов GTM и всех подключённых сервисов.
Ошибка, связанная с eval(), возникает, когда GTM пытается выполнить строку как JavaScript-код.
Пример функции eval():
eval("console.log('Hello, world!')");
Этот код выполнит строку как JavaScript, выводя "Hello, world!" в консоль. Однако, из-за своей опасности (возможность выполнения непроверенного кода), многие браузеры и политики безопасности (включая CSP) ограничивают или блокируют использование eval().
Данная ошибка со стороны gtm.js возникает при использовании Custom JavaScript переменных, которые используют eval() для динамического выполнения кода. Однако из-за ограничений CSP, использование eval() может быть заблокировано, что приведёт к сбоям в работе GTM.
Как решить проблему с eval() в GTM
Для решения этой проблемы есть два пути:
1) Использование Custom HTML тега в GTM:
При необходимости парсинга данных из структуры DOM рекомендуется использовать Custom HTML теги в GTM. Это позволяет избежать использования eval() в кастомных переменных.
Рекомендации:
Минимизировать обращения к DOM через JavaScript
Например, при загрузке страницы сохраните в отдельную переменную весь document, и получайте данные уже из полученной переменной, вместо многократных обращений ко всему DOM.
После сбора данных отправлять их в dataLayer и использовать тип переменной dataLayer Variable вместо Custom JavaScript.
2) Шаблон для вычислений:
Для расчёта некоторых переменных с более сложной логикой можно создать отдельный Custom Template и вынести в него все функции вычислений. Такой подход позволяет централизовать обработку данных внутри GTM и не нарушать политику CSP.
Пример шаблона:
const copyFromDataLayer = require('copyFromDataLayer');
const variableName = data.variable;
const variables = {
'variable1': function() { return 'value1'; },
'variable2': function() { return 'value2'; }
};
if (Object.keys(variables).indexOf(variableName) > -1) {
return variablesvariableName;
} else {
return undefined;
}
Доступ к функциям, localStorage и cookies внутри Custom Template управляется через раздел Permissions, что позволяет ограничить работу с потенциально небезопасными источниками и предотвратить ошибки, связанные с использованием eval().
В дальнейшем вместо Custom JavaScript переменных рекомендуется использовать переменную на основе созданного шаблона, передавая в неё имя необходимой функции. Такой подход снижает количество кастомного кода в конфигурации GTM и повышает уровень безопасности.
В некоторых случаях имеет смысл использовать хеши внешних скриптов, загружаемых через GTM — особенно если речь идёт о малознакомых партнёрах, CPA-сетках или временных интеграциях. Хеширование позволяет браузеру убедиться, что содержимое скрипта не изменилось, и тем самым снизить потенциальные риски.
Процесс хеширования обычно выглядит так:
Получение хеша содержимого скрипта
Добавление хеша в CSP, чтобы браузер мог безопасно разрешить их выполнение.
Отслеживание актуальности: при изменении скрипта или добавлении новых партнёров хеши нужно обновлять.
Этот подход не всегда удобен, особенно для крупных партнёров, где скрипты обновляются автоматически и хеш быстро устаревает. В таких случаях возможно использование whitelisting доверенных доменов в script-src, однако при наличии strict-dynamic такие списки по умолчанию игнорируются. Существуют способы обойти это ограничение, включая конфигурации с двумя заголовками Content-Security-Policy, но подобные решения нестабильны, зависят от поведения конкретного браузера и требуют осторожности при внедрении.