多租户HTML输出必须禁用静态缓存并按X-Tenant-ID分片CDN缓存,响应头需设Vary: X-Tenant-ID,禁止data-*属性参与路由或API构造,租户变量须作用域隔离且不共享locals,权限逻辑由服务端返回字段驱动。

多租户 HTML 输出必须禁用静态缓存并按 X-Tenant-ID 分片
CDN 或反向代理缓存带租户标识的 HTML 页面,是多租户系统最典型的缓存污染事故。一旦 /dashboard 被缓存为租户 A 的版本,租户 B 刷新就看到 A 的菜单、logo 和数据入口。
必须在 SSR 响应头中添加 Vary: X-Tenant-ID(推荐)或 Vary: Tenant-Slug,让缓存层识别租户维度。
- 禁止对含
data-tenant-属性的 HTML 片段启用Cache-Control: public;可设为private, max-age=0, no-cache - 若使用 Edge/CDN 缓存策略,需按请求头
X-Tenant-ID做 key 分片,不能仅靠 URL 路径做缓存键 - 网关未透传
X-Tenant-ID时,SSR 应拒绝渲染,而非 fallback 到默认租户——否则会把错误租户上下文写入缓存
data-* 属性只允许用于展示层,不可参与路由或 API 构造
把 data-api-base="/api/v1/acme" 写进 <body>,再让 JS 读取拼接请求地址,等于把租户上下文控制权交到前端——攻击者改 DOM 就能调其他租户接口。
API 基础路径必须由网关注入响应头(如 X-Api-Base),JS 通过 document.querySelector('meta[name="api-base"]') 读取(该 <meta> 由 SSR 安全写入)。
立即学习“前端免费学习笔记(深入)”;
-
data-tenant-slug只能用于 CSS 主题切换、埋点打标或调试日志,不许出现在fetch()、location.href、router.push()等任何运行时逻辑中 - 模板中禁止出现
href="/t//settings"这类动态拼接——应由后端生成完整 URL 并传入tenantSettingsUrl变量 - SSR 渲染时注入
data-server-tenant-id仅作调试用途,JS 不读、不传、不依赖
EJS 模板中租户变量必须作用域隔离
EJS 的 locals 是共享对象,若在 layout.ejs 里直接写 ,后续 partial 里再声明同名变量会覆盖,导致主题类名错乱或权限判断失效。
正确做法是显式传参,避免隐式引用 locals:
- 所有租户相关变量(如
tenantLogoUrl、tenantThemeClass)必须作为参数传入子模板,而非从全局locals读取 - 租户专属过滤器必须注册为函数:
,便于统一鉴权和 fallback - 禁止在模板内写三元表达式判断租户角色:
—— 权限逻辑必须由服务端返回字段驱动
HTMLHint 规则需按租户分级配置
租户定制页面常引入第三方组件或内嵌脚本,盲目启用全部 HTMLHint 规则会导致 CI 失败。比如某租户要求保留 <font> 标签做邮件模板兼容,但 font-face-require 规则会报错。
基础规则(doctype-html5、tag-pair、id-unique)全局强制启用;租户专属规则在 .htmlhintrc 中按目录隔离:
"pages/tenant-acme/**": { "attr-no-duplication": false }- 禁止在租户模板中使用
<script>内联代码,所有 JS 必须外链并带integrity属性 - 对含
data-track-id的埋点属性,仅校验格式合法性,不检查值是否匹配当前租户——因仅用于统计,不参与逻辑分支
data-tenant-slug),整个隔离就形同虚设。



















