subresource-integrity/sri-hashgen
GitHub: subresource-integrity/sri-hashgen
一个零依赖的 Python CLI 工具,用于在构建流程中自动生成、注入并持续校验 HTML 资源的 SRI integrity hash,防止供应链篡改。
Stars: 0 | Forks: 0
# sri-hashgen
生成并验证 Subresource Integrity hash,将 `integrity` 属性注入到 HTML 和模板中——并附带一个 CI `--check` 模式,当资源发生变动(漂移)时会导致构建失败。
一个不带 `integrity` 属性的 `
-
+
+
1 document(s): 3 added
dry run; re-run with --in-place to write these changes
```
请注意哪些内容*没有*改变:`` 被原封不动地保留了,因为浏览器不会对字体预加载强制执行完整性校验。添加 `--in-place` 参数即可执行写入。
### 整个构建目录
```
./bin/sri-hashgen inject dist/ --recursive --ext .html,.njk --root dist --in-place
```
### 在 CI 中验证
`--check` 不会重写任何内容。它会重新读取每个引用的资源,与标记中已有的 `integrity` 进行比对,如果发现任何缺失或过期的情况,就会以退出码 1 终止。
```
$ ./bin/sri-hashgen inject dist/ --recursive --root dist --check
dist/index.html
5 ok link /assets/site.css
7 ok script https://cdn.jsdelivr.net/npm/htmx.org@2.0.4/dist/htmx.min.js (cached)
8 MISMATCH script /assets/app.js
expected sha384-yPVlAIT29zzNfFmPlI8uX84OKDK9oIrsJx7G71WDaI8RyrxcJI3YzkRmdZ02PQBT, found sha384-blphUhHoGSRHydhxW45HXi+Mqul6/gMQD5QSo5vKnM45a1wxFgORisZVKnyv3yvH
1 document(s): 2 ok, 1 mismatch
$ echo $?
1
```
`--format json` 会生成同样报告的机器可读文档格式,其中包含一个 `ok` 布尔值、针对每个文件的 `findings` 数组以及一份 `summary` 计数映射表,`hash` 和 `inject --check` 命令均支持此功能。
### 作为 pipeline 过滤器
`--stdin-html` 从标准输入读取一个文档,并将重写后的文档写入到标准输出,因此它可以与任何工具组合使用:
```
cat src/index.html | ./bin/sri-hashgen inject --stdin-html --base-dir dist --root dist > dist/index.html
```
## 工作原理
### 摘要
一个 integrity 值的格式为 `-`。最容易犯的错误就是对 *hex* 摘要字符串进行 base64 编码,这会产生一个看似有效但会被所有浏览器拒绝的值。`sri-hashgen` 直接对 `hashlib.new(alg, data).digest()` 的结果进行编码,并且测试套件会针对空字符串和 `b"abc"` 的已公开测试向量(vectors)对结果进行严格校验。
当请求多种算法时,它们会按照最强优先的顺序输出(`sha512 sha384 sha256`)。这种排序对浏览器来说仅仅是装饰性的——无论位置如何,它都会选择属性中指定的最强算法——但这使得意图更加清晰易读。`--check` 完全镜像了该选择规则:如果在一个错误的 `sha384` token 旁边存在一个正确的 `sha256` token,这会被判定为**失败**,因为浏览器只会校验 `sha384` 那一个。
### 重写器
每一个 HTML 库在输出时都会对整个文档进行完整的往返处理。它会规范化引号,更改 doctype 的大小写,重新排列属性,注入 ``,并将 `{% block %}` 或 `` 转换为文本节点。如果对 Jinja、Twig、Liquid、Nunjucks 或 JSX 模板执行此操作,您将得到一份无人能够评审的 diff,以及一个无法再渲染的模板。
因此,该工具绝对不会构建解析树。它只会扫描它所关心的开标签,仅解析这些标签内的属性,并生成 *span edits*(即要替换的字节范围)。这些范围之外的所有内容都会原封不动地复制过去:缩进、CRLF 换行符、注释、无效标记、模板语法以及非 UTF-8 字节(文档在解码和重新编码时会使用 `surrogateescape`,因此游离的 Latin-1 字节会得以保留,而不会变成 U+FFFD)。
标签扫描器对于严格解析器会拒绝的异常情况具有刻意的宽容度:
- ` -->` 和 `` 块会被完全屏蔽,因此它们内部的任何内容都不会被重写。
- 未闭合的引号或缺少闭合 `>` 的标签会被严格忽略,而不会去进行猜测。
新属性会被插入到现有最后一个属性的正后方,从而保留 `>` 或 `/>` 之前的任何空白字符;对于已有的双引号包裹的 `integrity`,仅会替换其值的字节范围。
### 哪些会获得属性,哪些不会
对于 `