Content-Security-Policy
(CSP) - это HTTP заголовок, который улучшает безопасность веб-приложений за счет запрета небезопасных действий, таких как загрузка и отправка данных на произвольные домены, использование eval
, inline-скриптов и т.д. В этой статье будет сделан фокус на директиве style-src
и ее использование вместе с CSS-in-JS библиотекой emotion.
Кратко о CSP и style-src
Content-Security-Policy
заголовок должен быть выставлен в ответе вместе с загружаемой веб-страницей (например, index.html
). Это выглядит следующим образом:
Content-Security-Policy: style-src 'self'
style-src
- это директива, которая отвечает за то, какие стили можно загружать и применять на странице. Возможные значения:
'none'
- все стили запрещены'self'
- разрешены файлы стилей, которые загружаются с того же домена, что и основной документ (страница)<url>
, напримерhttps://example.com
- разрешены файлы стилей с этого домена, также допускаются wildcard (*) на месте под-домена и порта'<hash-algorithm>-<base64-value>'
, например'sha256-ozBpjL6dxO8fsS4u6fwG1dFDACYvpNxYeBA6tzR+FY8='
- разрешены файлы стилей и inline-стили (тег<style>
), у которых хеш совпадает с указанным значением'nonce-<value>'
, например'nonce-abc'
- разрешаются inline-стили, у которых атрибутnonce
совпадает с указанным (в примере -abc
)'unsafe-hashes'
- разрешает inline-стили, указанные в атрибутеstyle
строкой, например<div style="color:red;"></div>
, при этом хеш значения атрибута должен совпадать с хешом, указанным в'<hash-algorithm>-<base64-value>'
'unsafe-inline'
- разрешает все inline-стили, созданные через тег<style>
'unsafe-eval'
- разрешает добавление/изменение CSS declarations, которые приводят к парсингу строки, например, с помощью CSSStyleDeclaration.cssText
Директива может принимать несколько значений через пробел. В этом случае это трактуется как логическое "или" - при удовлетворении хотя бы одному значению стили разрешаются.
CSP и emotion
emotion
добавляет style
элементы динамически и в последних версиях не может извлекать все стили в отдельный файл во время сборки приложения. Это означает, что для того, чтобы можно было использовать emotion
вместе с style-src
, есть следующие опции:
'unsafe-inline'
- самая простая опция из всех. Не требует какой-либо настройки со стороныemotion
. При этом мы снижаем безопасность нашего приложения, поэтому это решение можно использовать только как временное.'nonce-<value>'
- можно разрешить inline-стили, созданныеemotion
. Для этого нужно задатьnonce
при созданииcache
.
При использовании @emotion/react
или @emotion/styled
это можно сделать следующим образом:
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
export function App() {
const cache = createCache({
key: 'my-app',
nonce: getNonceValue(),
});
return (
<CacheProvider cache={cache}>
{/* children */}
</CacheProvider>
);
}
Если используется @emotion/css
напрямую, то потребуется создать свой экземпляр emotion
:
import createEmotion from '@emotion/css/create-instance';
export const {
flush,
hydrate,
cx,
merge,
getRegisteredStyles,
injectGlobal,
keyframes,
css,
sheet,
cache
} = createEmotion({
key: 'my-app',
nonce: getNonceValue(),
});
При использовании createEmotion
потребуется поменять все места, где раньше импортировался @emotion/css
на этот модуль:
// import { css } from "@emotion/css";
import { css } from "./emotion";
Передача nonce на фронтенд
Т.к. значение CSP заголовка недоступно коду, исполняемому на клиенте, то значение нужно дополнительно передать другим образом. Один из вариантов - это создание inline-скрипта со значением, которое выставляется на бекенде:
<script id="nonce" type="application/json">
"abc"
</script>
На фронтенде это можно использовать таким образом:
function getNonceValue() {
const nonceElement = document.getElementById("nonce");
return JSON.parse(nonceElement.textContent);
}
Обратите внимание на type="application/json"
- таким образом браузер не считает это исполняемым кодом, и особое значение для script-src
не требуется.