跳到主要内容

脚本安全(Scripting Security)

Rive 文件可以包含代码:AnimaScript 与 Luau 脚本,以及 WGSL 着色器。Rive 运行时把这些代码当作不可信内容。它在沙箱中运行,只能做 Rive 允许的事,因此无法访问网络、设备上的文件,或应用的其余部分。

AnimaScript 是新脚本的推荐语言。Luau 已弃用:Luau 脚本仍会在每个 Rive 运行时上继续运行,但新的脚本功能会落到 AnimaScript。本页两者都覆盖。

常见问题(FAQs)​

问题回答
脚本能把数据送出应用吗?不能。 Rive 运行时不给脚本访问网络、文件或其他应用的能力。脚本只能通过视图模型(View Models)和事件,把数据交给你自己的代码。参见文件中的代码能做什么。
文件能让运行时发出网络请求吗?仅用于下载 Hosted 资源,且仅在默认会这样做的运行时上。脚本不能向这些请求附加数据,你也可以关闭下载。参见 Hosted 资源。
脚本能读到什么?你的应用绑定到该文件的视图模型数据、到达 Rive 视图的输入,以及当前时间。参见处理敏感数据的应用建议。
文件能打开 URL 吗?默认不能。 Apple 和 Android 运行时不会打开来自文件的 URL;Web 运行时只有在你开启后才会。参见 Open URL 事件。
文件能导航应用吗?不能。 你的代码决定如何处理事件和视图模型值。参见导航。
文件中的代码有签名吗?Luau 脚本和着色器有签名,没有有效签名时运行时会拒绝它们。AnimaScript 不需要签名,因为 WebAssembly 沙箱会检查并约束每个模块。参见签名保证什么。
签名能证明什么?代码由 Rive 的编译器生成,且之后无人改动。它不能证明代码是你的。任何人都可以从自己的 Rive 账户导出带签名的代码。参见签名不能证明什么。
签名覆盖整个文件吗?目前不是。 它只覆盖已编译的 Luau 脚本和着色器。整文件签名即将推出。
运行时能否只接受我们的文件?目前不能。 在加载前于应用内检查每个文件。参见从设计到发布之间保护文件和整文件签名。
能否关闭脚本?可以,使用不含脚本的运行时构建。没有针对单个文件的开关。参见关闭脚本。

Rive 如何看待脚本安全(How Rive approaches script security)​

  • 沙箱就是安全边界。 Rive 不依赖信任文件的作者。文件中的代码在限制其可达范围的沙箱里运行,因此即使是有害脚本也无法触达 Rive 不允许的任何东西。
  • 脚本一开始什么都没有。 Rive 有意、以它控制的 API 逐项加入能力:绘制、视图模型、输入、音频,以及脚本所属文件中的对象。脚本不能使用 Rive 没有给予的任何东西。
  • 签名证明代码从哪来,不证明它安全。 Rive 只在引擎无法自行检查代码的地方要求签名:Luau 字节码和已编译着色器。Rive 在自己的服务器上编译这些代码,因此只签署它自己生成的代码。AnimaScript 不需要签名,因为 WebAssembly 沙箱会检查并约束每个模块。
  • 违反规则的脚本会以错误停止。 你的应用继续运行。时间限制能拦住大多数运行过久的脚本调用,但不是全部;脚本仍可能占用足够的 CPU、GPU 或内存,让应用变慢或卡住。各限制覆盖范围见时间限制。
  • 由你的应用做决定。 文件不能直接对你的应用采取行动。它只能把值交给你的代码,由你的代码决定如何处理。

文件中的代码能做什么(What code in a file can do)​

Rive 文件中的一切(包括代码)都在你的应用给它的视图内工作。AnimaScript 与 Luau 脚本受到相同的限制。

文件可以:

  • 在其视图内绘制。它不能画到视图外,也不能改动应用其余 UI。
  • 播放文件中存储的声音。AnimaScript 脚本还可以生成声音。
  • 对到达其视图的输入作出反应:指针、键盘、手柄和文本输入。
  • 读取并修改你的应用绑定给它的视图模型数据。
  • 向你的应用报告事件。

脚本为这些能力加入逻辑。脚本还可以读取当前时间、向调试控制台打印消息,并用 GPU Canvas 着色器 绘制。AnimaScript 脚本还可以运行 job 和 worker;在运行时支持的地方,它们跑在后台线程上。

文件及其脚本都不能:

  • 发起网络请求。唯一例外是下载 Hosted 资源,由运行时完成,并由你的应用控制。参见 Hosted 资源。
  • 读写设备上的文件。
  • 读取设备数据,例如位置、通讯录、照片、相机、麦克风或剪贴板。
  • 读取你尚未绑定到该文件的应用数据。
  • 触达应用已加载的其他 Rive 文件。每个文件在自己的环境中运行脚本。
  • 从文件外部加载代码,或在运行时创建新代码。
  • 导航你的应用。
  • 打开 URL,除非你开启。只有 Web 运行时可以这样做,使用已弃用的 automaticallyHandleEvents 选项。参见 Open URL 事件。

各语言如何被约束(How each language is contained)​

AnimaScriptLuau
状态新脚本的推荐语言已弃用,但现有脚本继续运行
在文件中的存储形式WebAssembly 模块Luau 字节码
Web 运行时在浏览器的 WebAssembly 引擎上运行在 Luau 虚拟机上运行,该虚拟机本身编译为 WebAssembly
Apple 和 Android 运行时尚不支持,因此文件会加载并播放,但不运行其中的 AnimaScript 脚本在 Luau 虚拟机上运行
是否签名否是
约束方式WebAssembly 沙箱Luau 沙箱,外加签名

AnimaScript​

AnimaScript 编译为 WebAssembly,这是一种为在沙箱中安全运行代码而设计的格式。Rive 编辑器或 Rive CLI 把你的脚本编译成 WebAssembly 模块,并存储在 .riv 文件中。

  • 在 Web 上,运行时把模块加载进浏览器自己的 WebAssembly 引擎,也就是浏览器用于所有 WebAssembly 的同一沙箱。
  • 在浏览器之外,Rive 用开源 WebAssembly 运行时 WAMR 运行模块,规则相同。.riv 文件只以 WebAssembly 字节码携带这些模块,从不以机器码形式携带,运行时拒绝其他形式。视平台而定,模块会以提前编译的机器码运行,或在 WAMR 解释器上运行。Apple 和 Android 运行时尚未运行 AnimaScript。参见 Where Scripts Run。

在沙箱内:

  • 模块只能调用 Rive 提供的函数。它没有其他途径触达系统。
  • 每次内存访问都留在模块自己的内存内。越界访问,或把坏指针传给 Rive API,都会让模块以错误停止。
  • 线程和音频处理器在各自的沙箱模块中运行。它们共享的任何内存都属于同一文件的脚本。

AnimaScript 目前没有签名。它的安全性来自沙箱:无论谁构建,沙箱都会检查并约束每个模块。

在 AnimaScript 源码中,原始内存访问必须放在标为 unsafe 的函数里,这样审阅者可以用文本搜索找到它。即便如此,它也只能触达模块自己的内存。参见 The Unsafe Gate。

Luau​

Rive 在沙箱化的 Luau 虚拟机中运行 Luau 脚本,并使用精简的标准库:

  • 基础函数,不含 loadstring
  • table、math、string、utf8、buffer 和 bit32
  • os,仅限 clock、date、difftime 和 time

没有 io、package 或 debug 库。内置库和全局是只读的,require 只查找同一文件中的脚本。

Luau 虚拟机假定它运行的字节码来自 Luau 编译器,这也是 Luau 建议对编译后的代码签名 的原因。Rive 运行时只在签名有效时运行 Luau 字节码。参见签名保证什么。

着色器(Shaders)​

AnimaScript 和 Luau 脚本都可以用 WGSL 编写的 GPU Canvas 着色器绘制。导出时 Rive 的服务器会编译并签署着色器,没有有效签名的着色器代码会被运行时拒绝。着色器只能使用其脚本提供给它的缓冲区和纹理。

时间限制(Time limits)​

  • AnimaScript: 每次进入脚本的调用都有时间预算,运行时中为 200 ms。原生运行时用计时器强制执行。在浏览器中,只有模块在构建时带了 fuel 检查,调用才能被打断。调试构建始终包含它们。每次发布生成的优化构建,只有在 wasmFuel: true 时才包含。没有 fuel 检查时,永不返回的脚本会卡住页面。用 dangerouslyFast 构建的文件完全没有时间预算。后台 job 和 worker 没有时间限制;它们会一直运行到完成,或被运行时停止。参见 Build Settings。
  • Luau: Rive 运行时会打断运行超过 200 ms 的 Luau 脚本调用。刻意绕过该打断的脚本可以继续运行。

把这些限制当作防呆,而不是防攻击。刻意造成伤害的文件仍可能占用足够的 CPU、GPU 或内存,让应用卡住或崩溃。

签名保证什么(What signing guarantees)​

Rive 签署已编译的 Luau 脚本和着色器。当你从 Rive 编辑器导出文件,或用 Rive CLI 发布时,Rive 的服务器会编译源码并签署结果。Rive 自己编译代码,因此只签署它生成的代码。私钥保存在 Rive 的服务器上。每个支持脚本的 Rive 运行时都携带对应公钥,并在加载文件时检查签名。未签名、或签名后又被改动的代码不会运行。

Rive 编辑器和 Rive CLI 会在 Luau 脚本签名之前运行它们,方便你开发。运行时不会。

签名检查是开源运行时的一部分。参见 text_asset_importer.cpp 以及 file.cpp 中的 File::acceptsScript。

签名不能证明什么(What a signature does not prove)​

  • 谁制作了该文件,或其脚本是你的。 目前,无论账户或工作区,Rive 用同一把密钥签署每一次导出。任何人都可以在自己的 Rive 账户里编写 Luau 脚本,并导出带有效签名的文件,因此换成别人脚本的文件也能通过检查。沙箱限制这些脚本能做什么。签名不会。
  • 文件的其余部分未被改动。 目前签名只覆盖已编译的 Luau 脚本和着色器。

签名覆盖什么(What the signature covers)​

签名覆盖文件中 Luau 脚本和着色器的已编译代码。它不覆盖文件的其余部分,包括:

  • 画板、时间轴和状态机
  • 文本、图像、字体和音频
  • 默认视图模型值和脚本输入值
  • 事件设置,例如 Open URL 事件的地址
  • 哪些对象使用哪些脚本
  • AnimaScript 模块

如果有人改动这些部分,运行时无法察觉。如果有人在文件内编辑 Luau 脚本或着色器,签名检查会失败,运行时不会运行该文件的任何 Luau 脚本和着色器。文件的其余部分仍会播放。

整文件签名(Whole-file signing)​

整文件签名即将推出。计划覆盖整个导出的 .riv 文件,包括 AnimaScript 模块,并且运行时计划拒绝签名检查失败的文件。那时文件中任何一处改动都会阻止加载。我们也在做一种用签名判断哪个工作区导出了该文件的方式。签名届时就能证明文件从哪来。沙箱仍然是安全边界。

从设计到发布之间保护文件(Protecting files between design and release)​

目前,任何从 Rive 导出、带有 Luau 脚本或着色器的文件都带有效签名,因此签名无法把你的文件和别人的文件区分开。请像保护应用代码和其他资源一样,保护每个文件从 Rive 编辑器到用户的路径。整文件签名上线后会有帮助。即便如此,下面的检查仍用于确认文件就是你批准的那一份。

打包进应用的文件(Files bundled in your app)​

iOS 和 Android 要求覆盖应用包内每个文件的代码签名。发布后,无人能在不破坏该签名的情况下更改打包的 .riv 文件。风险更早出现:文件从设计交到工程时。为降低风险:

  • 把已批准的 .riv 文件放进版本控制,并且只通过代码审查改动它们。
  • 记录每个已批准文件的 SHA-256 哈希,并在构建流水线中校验。

应用下载的文件(Files your app downloads)​

如果应用会下载 .riv 文件,请在加载前检查每一个。

  1. 通过 HTTPS 从你控制的服务器提供文件。

  2. 把文件的 SHA-256 哈希与内置于应用的值比较,或验证用你自己的密钥做的签名。不要从存放该文件的同一服务器获取期望哈希。

  3. 只有检查通过后,才把字节交给 Rive 运行时。

Swift​

import CryptoKit
import Foundation

enum RiveFileError: Error { case integrityCheckFailed }

// SHA-256 of the approved file, as lowercase hex, built into the app.
let expectedHash = "<sha-256 of the approved file>"

// fileURL points to the downloaded file on disk.
func verifiedRiveData(from fileURL: URL) throws -> Data {
let data = try Data(contentsOf: fileURL)
let hash = SHA256.hash(data: data)
.map { String(format: "%02x", $0) }
.joined()
guard hash == expectedHash else { throw RiveFileError.integrityCheckFailed }
return data // Pass these bytes to the Rive runtime.
}

Kotlin​

import java.io.File
import java.security.MessageDigest

// SHA-256 of the approved file, as lowercase hex, built into the app.
const val EXPECTED_HASH = "<sha-256 of the approved file>"

// file is the downloaded file on disk.
fun verifiedRiveBytes(file: File): ByteArray {
val bytes = file.readBytes()
val hash = MessageDigest.getInstance("SHA-256").digest(bytes)
.joinToString("") { "%02x".format(it) }
check(hash == EXPECTED_HASH) { "Rive file failed its integrity check" }
return bytes // Pass these bytes to the Rive runtime.
}

要得到文件的哈希,在 macOS 上运行 shasum -a 256 your-file.riv,在 Linux 上运行 sha256sum your-file.riv。

网络请求、URL 与导航(Network requests, URLs, and navigation)​

脚本(Scripts)​

没有任何 Rive 运行时向脚本提供网络访问。

Hosted 资源(Hosted assets)​

在 Rive 编辑器中,你可以把图像、字体或音频资源设为 Hosted。Hosted 资源留在 Rive 的 CDN 上,而不是写进 .riv 文件;部分运行时在加载文件时会下载它。Web 运行时以及旧版 Apple 和 Android 运行时,会从文件中存储的地址下载,因此被改动的文件可以把下载指向任意服务器。脚本不能更改该地址,也不能向请求附加数据。

运行时默认是否下载 Hosted 资源如何关闭
Apple,新运行时(New Runtime)否无需操作
Apple,旧版运行时(Legacy Runtime)是向 RiveFile 或 RiveViewModel 传入 loadCdn: false
Android,新运行时否无需操作
Android,旧版运行时是在 RiveAnimationView builder 上调用 setShouldLoadCDNAssets(false),在 XML 中设置 app:riveShouldLoadCDNAssets="false",或创建 FallbackAssetLoader 时设 loadCDNAssets = false
Web是在 Rive 选项中设置 enableRiveAssetCDN: false

其他运行时请参阅对应运行时的「加载资源」页面。

Open URL 事件(Open URL events)​

Apple 和 Android 运行时不会打开来自文件的 URL。如果你的代码在监听事件,它会把 Open URL 事件 作为数据接收,并由你决定如何处理。Web 运行时只有在把已弃用的 automaticallyHandleEvents 选项设为 true 时才会打开这些 URL。默认值为 false。

Rive 运行时不能导航你的应用或打开屏幕。文件只能通过你的代码读取的事件和视图模型值来影响应用。如果你的代码根据这些值导航、打开 URL 或调用你的服务器,请先检查它们,就像对待来自应用外部的任何输入一样。

关闭脚本(Turning off scripting)​

Rive 运行时没有针对单个文件或整个应用关闭脚本的设置。若要在不运行脚本的情况下播放 Rive 文件,请使用去掉脚本的运行时构建。这些构建不运行 AnimaScript、Luau 或着色器。文件仍会加载并播放,但由脚本驱动的任何内容都会停止工作。

平台不含脚本的运行时
Web使用 @rive-app/canvas-lite 或 @rive-app/react-canvas-lite。这些包还会去掉文本、布局、音频和矢量羽化。
Apple从源码构建 rive-ios,并加上 --no-scripting 选项。参见其 contributing 指南。
Android从源码构建 rive-android,并加上 noScripting Gradle 属性,例如 -PnoScripting。

处理敏感数据的应用建议(Recommendations for apps that handle sensitive data)​

  1. 控制文件来源。 能打包进应用就打包,并在加载前检查每一个下载的文件。
  2. 只绑定文件需要的数据。 脚本可以读取你的应用绑定到其文件的每一个视图模型属性。永远不要把密码、令牌或完整卡号放进视图模型。
  3. 密钥用原生文本框。 脚本可以读取用户在其文件的文本输入(Text Input) 中键入的内容。
  4. 不要把秘密放进文件。 .riv 文件未加密。拿到文件的人可以检查其内容,包括已编译脚本以及其中的任何文本。
  5. 把文件回传的内容当作不可信输入。 在根据事件和视图模型值采取行动之前先检查它们。对于 URL 和屏幕,只接受你定义的列表中的值。
  6. 关掉你不用的能力。 如果不用 Hosted 资源,就关闭其下载。在 Web 上保持 automaticallyHandleEvents 关闭。如果文件没有脚本,使用不含脚本的运行时。
  7. 在 Web 上要求 fuel 检查。 如果你的 Web 应用播放来自团队外部的 AnimaScript 文件,请要求用 wasmFuel: true 构建的文件,以便浏览器能停下失控脚本。除非你信任来源,否则不要交付用 dangerouslyFast 构建的文件。
  8. 保持 Rive 运行时为最新,以获得安全修复。

安全资源(Security resources)​

  • 开源运行时。 Rive 运行时以 MIT 许可证开源,你的团队可以审阅应用实际运行的代码:rive-runtime(C++ 核心,包括签名检查和两套脚本引擎)、rive-ios、rive-android 和 rive-wasm。
  • AnimaScript。 Where Scripts Run 说明 AnimaScript 在各平台如何运行,以及时间预算如何工作。
  • SOC 2 Type II。 Enterprise 计划 的一部分。
  • 报告漏洞。 请通过支持表单私下联系我们。请不要在社区论坛或 GitHub issues 中报告安全问题。
看完还有疑问?进群交流下!
与众多 Rive 创作者、开发者一起交流探讨与答疑解惑。
加入交流群