安全响应头让支持它们的浏览器执行具体规则,价值来自这些规则改变了什么行为,而不是检查清单有多长。即使是静态站点,也涉及文档加载、脚本或样式、页面嵌入、导航和传输等边界。应根据真实页面及交付路径选择策略,同时验证预期限制和合法功能。不能把响应里出现一串熟悉字段,当成已经证明页面完整、安全且不会破坏正常集成。

内容安全策略可以限制资源加载与脚本执行,嵌入祖先指令规定哪些祖先能够包含受保护文档,引用来源策略控制适用请求携带多少源地址信息,权限策略则约束选定的浏览器功能。CSP 的 frame-ancestors 必须通过 HTTP 响应头交付;写在 meta 元素里不会建立这个嵌入策略。这些控制相互关联,但没有哪一个只凭字段存在,就能建立经过认证的业务权限。适合某类响应的规则,放到另一类响应上可能毫无意义,也可能妨碍页面原本需要的行为。

理解每个响应头负责的边界

严格传输安全策略告诉浏览器,对适用的主机使用 HTTPS。浏览器从有效的 HTTPS 响应学习该策略,普通 HTTP 响应不能建立它。包含子域名的设置还会影响当前应用之外的主机,因此这些主机需要支持预期的安全传输方式。这个响应头不会签发证书,也不会修复已经过期的证书;在依赖浏览器长期记住的策略之前,传输配置本身必须已经可靠。

跨源开启者策略影响浏览上下文和开启者隔离,跨源资源策略则限制某些跨源资源加载。把二者统称为隔离所有窗口的通用开关,会混淆各自约定。CORS 响应暴露又是另一套机制。修改相关策略时,应验证依赖弹出窗口、嵌入资源或跨源访问的实际集成,而不是把能找到的所有响应头复制到同一条规则中,再期待它们自动组合成正确结果。

坏掉的样子

nosniff 让浏览器处理资源时尊重声明的内容类型。对于脚本和样式表目标,如果响应的 MIME 类型不适合该目标,浏览器会拒绝它。因此,一个把 JavaScript 当成纯文本交付的部署,可能在启用该响应头后停止工作。应修复实际交付的字节、类型和状态,不能只删除限制,直到现象消失。策略暴露了交付错误,并不说明策略本身就是错误的。

另一种常见问题,是脚本或样式路径实际返回了 HTML 错误页。正确策略可能让这个原本存在的路由错误更容易被发现。应检查经过重定向、缓存和路由之后真正收到的响应,包括状态与内容类型。只验证安全头是否存在,无法知道收到的是不是目标资源;首页请求成功,也不能证明错误响应和静态文件都遵循了各自的交付约定。

快速验证

保存代码为 nosniff.py,执行 python3 nosniff.py –serve。先打开打印出的地址并加上 ?mode=correct,再分别测试 ?mode=strict 和 ?mode=loose。此前记录使用 Python 3.14.4 与 Chrome 152;本轮没有重跑浏览器实验。临时回环服务在三种情况下返回完全相同的经典脚本字节,只改变内容类型及 nosniff 响应头。页面报告脚本是否执行、加载是否失败,结束后按控制键加 C 关闭服务。

import json
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Event, Thread
from urllib.parse import parse_qs, urlsplit
from urllib.request import urlopen


class Handler(BaseHTTPRequestHandler):
    def log_message(self, *args):
        pass

    def do_GET(self):
        url = urlsplit(self.path)
        mode = parse_qs(url.query).get("mode", ["correct"])[0]
        assert mode in {"correct", "strict", "loose"}
        headers = {"Cache-Control": "no-store"}
        if url.path == "/payload.js":
            body = b"window.payloadExecuted = true;"
            headers["Content-Type"] = "application/javascript" if mode == "correct" else "text/plain"
            if mode != "loose":
                headers["X-Content-Type-Options"] = "nosniff"
        else:
            body = ("""<!doctype html><meta charset="utf-8"><title>MIME policy lab</title>
<h1>Script MIME policy</h1><pre id="result">Running...</pre><script>
const mode=""" + json.dumps(mode) + """;
const script=document.createElement('script');
script.src='/payload.js?mode='+mode;
function done(failed) {
  window.labResult={mode,executed:window.payloadExecuted===true,loadFailed:failed};
  document.getElementById('result').textContent=JSON.stringify(window.labResult,null,2);
}
script.onload=()=>done(false);script.onerror=()=>done(true);document.head.append(script);
</script>""").encode()
            headers["Content-Type"] = "text/html; charset=utf-8"
        self.send_response(200)
        self.send_header("Content-Length", str(len(body)))
        for key, value in headers.items():
            self.send_header(key, value)
        self.end_headers()
        self.wfile.write(body)


server = HTTPServer(("127.0.0.1", 0), Handler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
origin = f"http://127.0.0.1:{server.server_port}"
try:
    for mode in ("correct", "strict", "loose"):
        with urlopen(origin + "/payload.js?mode=" + mode, timeout=5) as response:
            assert response.read() == b"window.payloadExecuted = true;"
            assert response.headers["Content-Type"] == ("application/javascript" if mode == "correct" else "text/plain")
            assert response.headers.get("X-Content-Type-Options") == (None if mode == "loose" else "nosniff")
    print(json.dumps({"app_url": origin, "wire_mime_checks": "PASS"}), flush=True)
    if "--serve" in sys.argv:
        Event().wait()
except KeyboardInterrupt:
    pass
finally:
    server.shutdown()
    thread.join(timeout=5)
    server.server_close()

正确模式使用有效的 JavaScript MIME 类型并携带 nosniff,脚本应正常执行。严格模式把同样的字节标为纯文本,同时保留 nosniff,因此执行标志为假,加载错误标志为真。宽松模式去掉纯文本响应上的 nosniff,此前被测浏览器执行了该经典脚本。线上响应探针还分别检查了字节内容确实一致,以及相关响应字段按预期变化,避免把不同代码的执行结果误当成类型策略差异。

这组浏览器对照只验证一个具体的 MIME 边界,没有测试严格传输策略的持久化、所有资源类型、开启者隔离或其他浏览器,也不意味着任何环境中删除 nosniff 都能保证执行。对于实际站点,应把有针对性的拒绝案例与真实页面、资源的功能检查一起保留。核对最终服务层真正交付的响应头,并把每个预期字段值与它准备保留或禁止的浏览器行为对应起来,才能形成可解释、可复核的验证证据。

延伸阅读

来源