В юбилейной π-версии Python реализован новый подход к обработке строк, описанный в PEP-750, и уже вызвавший неоднозначную реакцию в сети. По сути, шаблонные строковые литералы, или t-строки (
t"…") — литералы, которые выглядят как f-строки, но не вычисляются сразу же в str. Вместо этого они возвращают объект Template с раздельным доступом к статичной части и вставкам в неё. Это даёт библиотекам шанс корректно экранировать/параметризовать значения под конкретный контекст (SQL, HTML, shell и т.д.) и, якобы, тем самым снизить риск инъекций.Например,
t"Hello {name}" создаёт string.templatelib.Template, в котором доступны части строки и интерполяции, как объекты Interpolation(value, expression, conversion, format_spec). У Template нет __str__, поэтому его невозможно «случайно» напечатать как готовую строку — нужно явно вызвать обработчик (например, html(template) или sql(template), в соответствии с грамматикой принимающей стороны).На самом деле — очень здравая языковая фича, позволяющая строить эффективную обработку выходных данных за счет:
• Чёткого разделения данных и шаблона, позволяющих (при желании и умении) учитывать грамматический контекст, формат-спецификаторы/конверсии и т.п.
• Запрета «тихой» конкатенации со строками (сложение
Template + str запрещено, разрешено только Template + Template.• Явного рендера за счет отсутствия
__str__, заставляющего разработчика осознанно походить к выбору обработчиков.• Композиционности. Обработчики не обязательно должны возвращать строки, что позволяет объединять их в цепочки. Можно вкладывать шаблоны и обрабатывать по уровням контекста (например, атрибуты HTML как dict → безопасная строка атрибутов).
В psycopg, например, уже подсуетились и реализовали параметризацию SQL-запросов через t-строки в текущем dev своей библиотеки. Их обработчик можно подсмотреть в _trstrings.py.
Пример простого обработчика, санитизирующего данные в контексте HTML/Text:
from html import escape
from string.templatelib import Template, Interpolation
def html(tmpl: Template) -> str:
out = []
for part in tmpl:
if isinstance(part, Interpolation):
out.append(escape(str(part.value)))
else:
out.append(part)
return "".join(out)
evil = "<script>alert(1)</script>"
assert html(t"<p>{evil}</p>") == "<p><script>alert(1)</script></p>"
Однако, как и говорится в описании изменений, t-строки — это лишь механизм обработки строк, а не панацея от инъекций, неправильное использование которого позволит прострелить себе конечность не менее лихо, чем в случае с f-строками:
• Эффективность санитизации интерполяций — целиком зависит от правильного выбора или написания их обработчиков.
• Все выражения внутри блоков
{…} вычисляются сразу же в лексическом скоупе, интерполяцией же становится результат этого вычисления. Поэтому t"{eval(request.get['a'])}" — это всё ещё RCE, вне зависимости от обработчиков. Похожая история — и с попаданием в Template или Interpolation входных данных при создании объектов этих классов из конструкторов (вообще, стоит этого по-возможности избегать, и пользоваться предложенным синтаксическим сахаром t"…").• Конкатенация
Template + Template разрешена. Это удобно, но может породить «вирусность» шаблонов и неочевидную логику сборки, если смешивать части, ожидающие разных политик экранирования.• Формат-спецификаторы внутри
{…} вычисляются до format_spec, что может привести к потере грамматического контекста, когда придёт время обработчика.• Валидацию входных данных в соответствии с бизнес-логикой этот механизм не заменяет, и относится лишь ко второму эшелону эффективной обработки данных.
В общем, механизм годный, использовать стоит, но «думать всё равно придётся» (с) 😊
#безопасность_кода