django strust

```html django strust

django strust

第一次在项目里看到 strust 这个词,我以为是拼写错误。Django 官方文档里没有它,Stack Overflow 搜不到,GitHub 上倒是有几个零星仓库叫这个名字,点进去一看,全是半成品。后来才搞明白,strust 不是框架自带的,是有人把 str 和 trust 硬捏在一起,用来指代 Django 模板里字符串转义与信任边界那堆破事。说白了,就是 |safe、autoescape、mark_safe 这几个东西搅在一起时,你该信谁、不该信谁。

去年帮一个朋友救火。他的博客站突然在文章详情页弹出了别人的 Cookie。原因很简单:他在模板里写了 { { post.content|safe }},而 post.content 来自用户提交的 Markdown 渲染结果。渲染器没过滤 <script>,Django 又老老实实把整个字符串当 HTML 塞进页面。XSS 就这么来了。他问我:“Django 不是默认转义吗?” 对,默认是转义的。{ { post.content }} 会把 < 变成 &lt;。但一个 |safe 就把这层保护撕了。这不是 Django 的 bug,是你主动告诉它:“这个字符串我信。” 问题在于,你真的信吗?

Django 的模板引擎设计哲学很拧巴。它一边给你 autoescape 默认开启,一边又留了 safe 和 mark_safe 两个后门。官方文档写得很清楚:只有当你完全确定这个字符串不包含恶意内容时,才用 mark_safe。 但“完全确定”这四个字,在真实项目里几乎不存在。用户昵称算不算?评论内容算不算?第三方 API 返回的 HTML 片段算不算?你每加一个 |safe,就是在信任链上多挂一个未知节点。

我自己的习惯:模板里永远不写 |safe。需要渲染 HTML 的地方,全部在视图层用 bleach 或 nh3 清洗一遍,然后返回一个 SafeString。模板只负责展示,不负责判断信任。

有人会说:“我用 { % autoescape off %} 包一段,性能更好。” 性能?一个 XSS 就能让你整个站点的 session 被偷。Django 的转义开销在模板渲染里占比不到 5%,为了这点性能把安全边界拆了,不值。更隐蔽的坑是 mark_safe 用在 f-string 里。比如 mark_safe(f"<b>{ user_input}</b>"),你以为只标记了外层标签,实际上 user_input 里的 <script> 也跟着被信任了。这种写法在代码审查里一抓一个准。

那 strust 这个词到底怎么用?我后来在内部文档里给它下了个定义:字符串信任决策。每次你准备把一段字符串标记为安全之前,问三个问题——来源是谁?经过清洗了吗?如果出错,最坏后果是什么?三个答案里有一个模糊,就别用 safe。Django 提供了工具,但信任判断得你自己做。框架不会替你背锅。

最近 Django 5.0 加了 SafeString 的更多检查,比如在 format_html 里混用普通字符串会报警告。这是好事。但工具再聪明,也拦不住你亲手写 |safe。我见过一个项目,模板里 200 多个 |safe,问开发者为什么,他说:“不加的话样式会乱。” 样式乱是因为他把 HTML 结构写在了数据库字段里,而不是模板里。这是设计问题,不是转义问题。

所以 django strust 不是什么新东西。它是一直存在但没人愿意直面的老问题:你信谁?Django 把选择权交给你,你就得承担后果。下次敲下 |safe 之前,停两秒。那两秒可能救你一个周末的加班。

```