Автоэкранирование, безопасность и статика
Jinja по умолчанию экранирует выводимые значения — превращает опасные символы в безопасные. Это защита от XSS, одной из самых частых веб-уязвимостей.
Если пользователь введёт в имя <script>...</script>, а ты выведешь это как есть — чужой скрипт выполнится в браузере жертвы. Автоэкранирование Jinja превращает теги в безопасный текст. Отключать его — почти всегда ошибка.
XSS (межсайтовый скриптинг) — атака, при которой злоумышленник внедряет свой HTML/JS через пользовательский ввод. Jinja защищает автоматически: при выводе через {{ }} символы < > & " превращаются в HTML-сущности (< и т.д.). Браузер покажет их как текст, а не выполнит как разметку.
<!-- если comment = "<script>alert(1)</script>" -->
<p>{{ comment }}</p>
<!-- в HTML попадёт безопасный текст, скрипт не выполнится -->
Автоэкранирование включено для шаблонов .html. Если ты точно знаешь, что значение — доверенный HTML (например, очищенный санитайзером), его можно вывести как разметку фильтром |safe. Но это осознанный риск: |safe на пользовательских данных = открытая дверь для XSS.
Статические файлы (CSS, JS, картинки) кладут в папку static и ссылаются через url_for:
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<img src="{{ url_for('static', filename='logo.png') }}">
Автоэкранирование — это причина, по которой Flask-приложения защищены от XSS по умолчанию, и ты должен понимать, когда эта защита может отключиться. Опасность не в самом |safe, а в его бездумном применении к данным, пришедшим от пользователя: комментарий, имя, описание профиля — всё это потенциальный носитель чужого скрипта. Правило простое: |safe допустим только для HTML, который ты сам сгенерировал или который прошёл через санитайзер вроде bleach, вырезающий опасные теги. Второй частый источник дыр — сборка HTML строками во view с последующей отдачей: так ты обходишь автоэкранирование вручную. Доверяй шаблонизатору и не выводи сырой пользовательский HTML.
Как работает под капотом
Экранирование — это замена опасных символов на сущности. Браузер декодирует сущности при отображении, но не исполняет их как теги. Поэтому <script> показывается буквами, а не запускается.
Ввод пользователя: <b>hi</b>
│ autoescape
▼
В HTML-ответе: <b>hi</b>
│ браузер декодирует при показе
▼
На экране: <b>hi</b> (как текст, не жирный)
Смоделируем экранирование вручную — увидишь, почему оно обезвреживает скрипт.
def escape(text):
return (text
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace('"', """))
dangerous = "<script>alert('xss')</script>"
print("сырой: ", dangerous)
print("безопасный:", escape(dangerous))
# в HTML второй вариант покажется текстом, скрипт не выполнится
Запусти: теги превратились в безопасные сущности. Jinja делает это для каждого {{ }} автоматически — поэтому Flask-приложения защищены от XSS «из коробки», пока ты не злоупотребляешь |safe.
Частые ошибки
- Лепить |safe на пользовательский ввод. Это прямая дыра XSS. |safe только для доверенного/очищенного HTML.
- Собирать HTML строками во view и отдавать. Минуешь автоэкранирование — снова риск XSS.
- Хардкодить пути к статике. /static/style.css ломается при смене настроек; используй url_for('static').
Best practices
- Никогда не отключай автоэкранирование без крайней нужды.
- Пользовательский HTML перед |safe прогоняй через санитайзер (например bleach).
- Статика — всегда через url_for('static', filename=...).
Что запомнить
- Jinja экранирует вывод {{ }} автоматически, защищая от XSS.
- |safe отключает экранирование — только для доверенного/санитизированного HTML.
- Не собирай HTML строками во view, минуя автоэкранирование.
- Статику подключают через url_for('static', filename=...).
Итог: Jinja экранирует вывод автоматически, защищая от XSS; |safe — осознанное исключение для доверенного HTML; статика подключается через url_for('static'). Это закрывает блок про шаблоны — дальше формы и состояние.