moonsri

    MoonBit Subresource Integrity toolkit with HTML audits, trusted baselines, and release-to-release HTML reference diffs

    sri
    integrity
    sha2
    subresource-integrity
    Download zip
    Version
    0.5.1
    License
    Apache-2.0
    Last updated
    57 minutes ago
    Downloads
    36

    #MoonSRI

    CI 版本 0.5.1 Apache-2.0

    MoonSRI 是一个用 MoonBit 编写的 Subresource Integrity(SRI) 元数据生成与校验工具。它对本地文件的原始字节计算 SHA-256、SHA-384 或 SHA-512 摘要,输出可用于 HTML integrity 属性的字符串,也能检查已有元数据是否与文件相符。

    目前提供 MoonBit 库和命令行工具。适合在构建脚本、静态站点发布流程中为固定的 JavaScript / CSS 文件生成哈希,或在发布前检查文件是否被意外改动。

    #快速开始

    安装 MoonBit 工具链,在仓库根目录运行:

    moon test --target wasm moon run cmd/main --target wasm -- generate examples/hello.js

    示例输出(examples/hello.js 的结尾包含一个换行符):

    sha384-uY+OtEjszwPe7jcYKI1KfAGMrLSMeFfhJc/DWf9ccFpWjjw1tKO9St/U0k2N8ONW

    将该值写入 HTML 时,需确保部署的文件字节与计算时一致:

    <script src="/hello.js" integrity="sha384-uY+OtEjszwPe7jcYKI1KfAGMrLSMeFfhJc/DWf9ccFpWjjw1tKO9St/U0k2N8ONW"></script>

    验证文件:

    moon run cmd/main --target wasm -- check examples/hello.js 'sha384-uY+OtEjszwPe7jcYKI1KfAGMrLSMeFfhJc/DWf9ccFpWjjw1tKO9St/U0k2N8ONW'

    输出为 match,退出码为 0。摘要不符时输出 mismatch、退出码为 1。元数据中没有任何受支持算法或发生用法、文件读取错误时,退出码为 2。

    generate 默认使用 SHA-384,也可以显式指定 sha256、sha384 或 sha512:

    moon run cmd/main --target wasm -- generate examples/hello.js sha512

    也可以用逗号分隔算法,例如 sha256,sha512。输出会按指定顺序排列,多个摘要之间用空格分隔,可以一起填入同一个 integrity 属性。

    在 Windows PowerShell 中,把示例里的单引号保留即可。也可用 --target js 运行,需有 Node.js。

    #生成 HTML 标签

    tag 从本地文件计算摘要,并生成可复制到页面的 script 或 link 标签。第三个参数是浏览器加载资源时使用的 URL,可以是相对路径或 HTTP(S) 地址。标签默认包含 crossorigin="anonymous"。跨源资源的服务器需要允许页面通过 CORS 读取响应。参见 W3C SRI 规范。

    moon run cmd/main --target wasm -- tag script examples/hello.js '/assets/hello.js?v=1&lang=zh' sha384 moon run cmd/main --target wasm -- tag stylesheet examples/style.css /assets/style.css

    运行 python -m http.server 8765 --bind 127.0.0.1 后打开 浏览器示例。页面会显示匹配摘要的脚本成功执行,另一条故意填错的摘要会触发加载错误。控制台可以看到浏览器给出的 SRI 错误。

    #审计部署后的 HTML

    audit-html 读取部署目录中的 UTF-8 HTML,检查静态 script[src] 与未禁用的 link[rel=stylesheet][href] 是否带有 integrity,并用本地资源字节校验摘要。可以一次检查多个页面,也可以设置最低算法强度。

    moon run cmd/main --target wasm -- audit-html --root dist --json --min-algorithm sha384 index.html docs/index.html

    页面参数是相对于 --root 的文件路径。相对资源 URL 默认从页面目录解析,以 / 开头的 URL 从部署根目录解析。支持 UTF-8 百分号编码,校验时只使用路径部分。

    页面可用本地 base href 指定资源的解析目录。/assets/ 从部署根目录定位,../assets/ 从页面目录定位。末尾有 / 时使用该目录,文件形式的地址则使用其上级目录,例如 /assets/index.html 对应 /assets/。远程基准地址会报告 unsupported_base。

    JSON 输出是按输入顺序排列的页面数组。每页包含 page、status 和 resources,资源条目记录 kind、url、status,并在可定位到本地文件时提供 path。绝对 HTTP(S) 地址和以 // 开头的地址标为 external,跳过校验。

    同一次审计中,解析到同一本地路径且 integrity 相同的引用共享校验结果。每个引用仍出现在所属页面的报告中。

    退出码 0 表示没有审计失败项,1 表示本地资源缺少摘要、摘要不匹配或低于算法要求,2 表示读取失败、没有受支持算法、URL 无法解析为本地文件或页面含不支持的 base href。多个页面中以最高退出码为准。审计范围是静态 HTML 引用,URL 与文件的映射以传入的部署目录为准。

    可直接审计仓库中的浏览器示例。示例故意为一条脚本引用填了错误摘要,预期返回 1。

    moon run cmd/main --target wasm -- audit-html --root examples --json browser.html

    #对照可信发布清单

    在审查通过的构建产物上生成一份基线清单,并将它与之后待部署的产物分开保管。加入 --baseline 后,audit-html 会核对页面声明、基线清单与部署文件三者。即使有人同时更改了文件和页面里的摘要,只要基线保持可信,审计也会报告变化。

    moon run cmd/main --target wasm -- manifest --root reviewed-dist assets/app.js assets/site.css > approved-integrity.json moon run cmd/main --target wasm -- audit-html --root dist --baseline approved-integrity.json --json index.html docs/index.html

    报告保留资源原有的 status,并为本地引用增加 baseline.declaration 和 baseline.content。声明可能为 match、drift 或 untracked,内容可能为 match、mismatch、read_error、below_minimum 或 not_checked。比较声明时只看最强算法及去重后的摘要集合,所以摘要顺序和重复项不影响结果。未登记的本地引用和任何基线偏差都会让审计失败。审计范围以本次传入页面的静态本地引用为准,基线文件的真实性由发布流程保证。

    #比较两次发布的 HTML 引用

    在部署前比较两次构建的同名页面。即使两次 audit-html 都通过,diff-html 仍能报告删除的脚本、新增的样式表、有效 SRI 声明变化和共同引用在 DOM 中的相对顺序变化。

    moon run cmd/main --target wasm -- diff-html --before-root previous-dist --after-root dist --json index.html docs/index.html

    本地引用按类型、解析后的文件路径及原样保留的查询参数和片段匹配。外部 HTTP(S) 引用按去除首尾空白后的 URL 文本匹配。同一键的重复引用按出现顺序配对。有效 SRI 摘要的重排、重复项和弱于最强算法的摘要变化不会产生差异。

    JSON 按输入页面顺序输出 added、removed、integrity_changed 和 order_changed。条目的 position 从 1 开始,顺序变化时还提供 before_order 和 after_order。退出码 0 表示无变化,1 表示有变化,2 表示参数或任一页面输入有误。页面需在两份构建里都存在。此命令比较静态 HTML 声明和 DOM 顺序,资源字节交给 audit-html 校验,脚本运行时的执行顺序由浏览器决定。

    #批量生成与校验

    在项目根目录列出要发布的资源,生成一份 JSON 清单:

    moon run cmd/main --target wasm -- manifest examples/hello.js examples/style.css > integrity.json moon run cmd/main --target wasm -- check-manifest integrity.json

    在 Windows PowerShell 中,可以用 | Set-Content -Encoding utf8 integrity.json 替换第一条命令末尾的 > integrity.json。

    清单是一个 UTF-8 JSON 对象,键是文件路径,值是 SRI 字符串。相对路径以运行命令时的工作目录为准。路径含空格时请加引号。默认算法是 SHA-384,可以在文件列表前加 --algorithm sha512 或 --algorithm sha256,sha512。文件名与选项同名时,在文件列表前加 --。

    check-manifest 会报告各文件的结果。退出码 0 表示全部匹配,1 表示至少有一份摘要不匹配,2 表示清单无效、文件无法读取或元数据没有受支持的算法。若同时出现摘要不匹配和读取等错误,优先返回 2。请使用可信来源的清单,因为工具会按其中的路径读取本地文件。

    脚本可以用 --json 获取结构化结果:

    moon run cmd/main --target wasm -- check-manifest --json integrity.json

    标准输出是一个 JSON 对象,键为清单中的文件路径,值为 match、mismatch、read_error 或 no_supported_metadata。清单有效时,即使有文件校验失败也会输出完整报告,退出码规则与默认模式相同。参数错误或清单本身无法读取、解析时,诊断写入标准错误。

    生成和校验都支持 --root。例如从 dist 生成清单,在发布目录 staging 校验同一份清单:

    moon run cmd/main --target wasm -- manifest --root dist assets/app.js > integrity.json moon run cmd/main --target wasm -- check-manifest --root staging integrity.json

    清单键仍是 assets/app.js。使用 --root 时,清单命令与 HTML 审计共用可移植路径规则。文件名可以包含中文和中间空格,路径必须是以 / 分隔的相对路径,路径段不能以点或空格结尾,也不能包含控制字符或使用 Windows 保留的文件名。指定的根目录用于拼接路径,操作系统仍会按正常规则解析符号链接。

    #从文件列表生成清单

    构建系统可以把资源路径写入 UTF-8 文件,每行一个路径。路径里的空格会保留,空行会跳过,支持 LF、CRLF 和 UTF-8 BOM。--from 可以与 --root 和命令行上的额外路径同时使用。

    moon run cmd/main --target wasm -- manifest --root dist --from assets.txt > integrity.json

    #比较发布清单

    moon run cmd/main --target wasm -- diff-manifest previous.json integrity.json

    按路径列出新增、删除和摘要变化的资源。比较直接使用清单中保存的字符串,例如调整算法顺序也会显示为变化。两个清单相同则返回 0,有变化返回 1,清单读取或解析失败返回 2。

    #要求最低算法强度

    发布流程可以用 --min-algorithm 要求最强的已识别算法达到指定强度。例如下面的校验会拒绝只有 SHA-256 的条目,即使其摘要正确:

    moon run cmd/main --target wasm -- check-manifest --json --min-algorithm sha384 integrity.json

    单文件用法为 check <file> <integrity-metadata> --min-algorithm sha384。低于要求时,批量 JSON 报告中的状态是 below_minimum,退出码为 1。如果同时出现读取错误或缺少受支持算法,仍优先返回 2。

    #MoonBit 库

    在已有的 MoonBit 项目中,从 Mooncakes 安装指定版本:

    moon add yiwnagmolu/moonsri@0.5.1

    在调用方的 moon.pkg 中加入包导入。如果调用方是新的命令行包,可以使用以下配置:

    import { "yiwnagmolu/moonsri" @sri, } pkgtype(kind: "executable")

    在该包的 main.mbt 中运行最小示例:

    ///|
    fn main {
    let data = b"abc"
    let metadata = @sri.generate(data[:], @sri.Sha256)
    println(metadata)
    }

    如果入口位于 cmd/main,运行 moon run --target wasm cmd/main,会输出以 sha256- 开头的摘要。

    根包公开以下接口:

    • generate(data : BytesView, algorithm : Algorithm) -> String:计算一个 SRI 元数据字符串。
    • parse_metadata(input : String) -> Array[Metadata]:解析含多个哈希表达式的字符串,忽略未知算法。
    • canonical_metadata(input : String) -> String?:规范化最强算法的摘要集合,供不同来源的 SRI 声明做等价比较。
    • verify_bytes(data : BytesView, input : String) -> Verdict:仅检查元数据中最强的已识别算法;同一强度的多个摘要只要有一个匹配即可。
    • verify_bytes_at_least(data, input, minimum) -> Result[Verdict, Algorithm]:对最强算法低于要求的元数据返回 Err(实际算法),其余情况返回普通校验结果。
    • verify_with_token(input, token_for, minimum=...):需要校验摘要时,按最强算法向回调请求由 generate 生成的完整 token。调用方可在多份声明间复用同一资源的摘要。

    Algorithm 包含 Sha256、Sha384、Sha512;Verdict 包含 Match、Mismatch、NoSupportedMetadata。

    如果应用分块读取资源,可以用 Hasher::new(algorithm) 创建增量计算器,按顺序调用 update(chunk),最后用 metadata() 取得 SRI 字符串。中途读取摘要不会改变计算状态,调用 reset() 可用同一种算法开始计算下一个资源。

    子包 yiwnagmolu/moonsri/manifest 提供 parse 和 encode。它负责检查 JSON 清单结构,返回路径到 SRI 字符串的映射,适合接入构建工具或自行决定如何读取资源。

    子包 yiwnagmolu/moonsri/audit 的 scan(html) 返回 HTML 中的脚本和样式表引用、对应的 integrity 和首个生效的 base href。它依赖 moonbit-community/html 解析 HTML,可以在 MoonBit 构建工具里独立调用。

    #范围与行为

    • 依据 W3C SRI 规范处理算法优先级、多个摘要和保留的 ?options 部分;目前没有已定义的 SRI 选项。
    • 对已识别算法的摘要进行区分大小写的字符串比较。较强算法的摘要即使无效或不匹配,也不会退回较弱算法。
    • 作为离线检查工具,NoSupportedMetadata 单独报告,不把空元数据默认为通过。浏览器规范在这种情况下返回通过;这个区别仅影响 MoonSRI 的离线检查结果。
    • 标签生成和摘要校验以本地文件为输入。浏览器负责加载 URL、验证响应并执行页面的跨源策略。
    • 结果取决于精确字节;换行符或编码变化都会改变哈希。

    #开发

    moon check --target wasm moon test --target wasm moon test --target js moon info moon fmt

    测试包含 SHA-2 已知向量、最强算法选择、同等强度多摘要、未知算法及空元数据。CI 在 WebAssembly 和 JavaScript 目标运行测试及命令行示例。

    本项目使用 Apache-2.0 许可证。

    Algorithm

    pub(all) enum Algorithm {
    Sha256
    Sha384
    Sha512
    } derive(Eq, Hash)

    SRI 1.0 algorithms supported by this library.

    Algorithm::equal

    fn Algorithm::equal(Algorithm, Algorithm) -> Bool

    Algorithm::hash

    fn Algorithm::hash(self : Algorithm) -> Int

    Algorithm::hash_combine

    fn Algorithm::hash_combine(Algorithm, Hasher) -> Unit

    Algorithm::not_equal

    fn Algorithm::not_equal(x : Algorithm, y : Algorithm) -> Bool

    Hasher

    type Hasher

    An incremental SRI hasher for callers that receive a resource in chunks.

    Hasher::metadata

    fn Hasher::metadata(self : Hasher) -> String

    Return the token for bytes seen so far without changing the current state.

    Hasher::new

    fn Hasher::new(algorithm : Algorithm) -> Hasher

    Hasher::reset

    fn Hasher::reset(self : Hasher) -> Unit

    Start hashing a new resource with the same algorithm.

    Hasher::update

    fn Hasher::update(self : Hasher, data : BytesView) -> Unit

    Append bytes in resource order.

    Metadata

    pub(all) struct Metadata {
    algorithm : Algorithm
    digest : String
    }

    A token with a recognized SRI hash algorithm.

    Verdict

    pub(all) enum Verdict {
    Match
    Mismatch
    NoSupportedMetadata
    }

    An offline checker should not silently accept an empty or unknown-only list.

    canonical_metadata

    fn canonical_metadata(input : String) -> String?

    Normalize the effective digest set to its strongest recognized algorithm. Digest case is preserved; duplicate digests are removed and the rest sorted.

    generate

    fn generate(data : BytesView, algorithm : Algorithm) -> String

    Generate one SRI metadata token for exact resource bytes.

    parse_metadata

    fn parse_metadata(input : String) -> Array[Metadata]

    Parse SRI tokens using the algorithm in SRI 1.0, ignoring unknown algorithms.

    verify_bytes

    fn verify_bytes(data : BytesView, input : String) -> Verdict

    Multiple digests of that strength are alternatives, as in SRI 1.0.

    verify_bytes_at_least

    fn verify_bytes_at_least(data : BytesView, input : String, minimum : Algorithm) -> Result[Verdict, Algorithm]

    Verify with a minimum algorithm strength. Err contains the strongest advertised algorithm when it falls below the minimum.

    verify_with_token

    fn verify_with_token(input : String, token_for : (Algorithm) -> String raise?, minimum? : Algorithm?) -> Result[Verdict, Algorithm] raise?

    Verify against a token computed for the selected algorithm and resource, such as the output of generate. The provider runs only when hashing is required, so callers can reuse a digest across different declarations.