> ## Documentation Index
> Fetch the complete documentation index at: https://rive.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripting Security

> How Rive keeps the scripts and shaders in your files sandboxed, and how to ship those files safely.

Rive files can include code: [AnimaScript](https://rive.app/rasc/index.html) and Luau scripts, and WGSL shaders. Rive runtimes treat that code as untrusted. It runs in a sandbox and can only do what Rive allows. It can't reach the device's files or the rest of your app, and it reaches the network only if your app turns on [script fetch](#script-fetch).

> [!NOTE]
>
> AnimaScript is the recommended language for new scripts. Luau is deprecated: Luau scripts keep running on every Rive runtime, but new scripting features land in AnimaScript. This page covers both.

## FAQs

| Question | Answer |
| - | - |
| Can a script send data out of the app? | **Only if your app turns on [script fetch](#script-fetch).** Script fetch is off by default. While it's off, a script can pass data only to your own code, through View Models and events. Once your app turns it on, scripts can send what they read to the hosts you allow. See [What code in a file can do](#what-code-in-a-file-can-do). |
| Can a file make the runtime send network requests? | **Only for Hosted assets and script fetch.** Some runtimes download Hosted assets by default, and you can turn that off. Scripts can't add data to those downloads. Script fetch sends requests only after your app turns it on. See [Network requests, URLs, and navigation](#network-requests-urls-and-navigation). |
| Can we keep scripts off the network? | **Yes.** Script fetch is off until your app turns it on. If you turn it on, allow only the hosts your files need. See [Script fetch](#script-fetch). |
| Which runtimes support script fetch? | The web runtime (`@rive-app/webgl2-threads`) and the Rive CLI, today. Script fetch is coming soon to every Rive runtime. See [Script fetch](#script-fetch). |
| What can a script read? | The View Model data your app binds to its file, the input that reaches the Rive view, and the current time. With script fetch on, it can also read the responses from the hosts you allow. See [Recommendations for apps that handle sensitive data](#recommendations-for-apps-that-handle-sensitive-data). |
| Can a file open a URL? | **Not by default.** Apple and Android runtimes do not open URLs from a file, and the web runtime does so only if you turn it on. See [Open URL events](#open-url-events). |
| Can a file navigate the app? | **No.** Your code decides what to do with events and View Model values. See [Navigation](#navigation). |
| Is the code in a file signed? | Luau scripts and shaders are signed, and runtimes refuse them without a valid signature. AnimaScript needs no signature, because the WebAssembly sandbox checks and contains every module. See [What signing guarantees](#what-signing-guarantees). |
| What does a signature prove? | That Rive's compilers produced the code, and that nobody edited it afterward. It does not prove that the code is yours. Anyone can export signed code from their own Rive account. See [What a signature does not prove](#what-a-signature-does-not-prove). |
| Does the signature cover the whole file? | **Not today.** It covers compiled Luau scripts and shaders only. [Whole-file signing](#whole-file-signing) is coming soon. |
| Can the runtime accept only our files? | **Not today.** Check each file in your app before you load it. On runtimes that support script fetch, scripts can also load Rive files that your app never sees. See [Protecting files between design and release](#protecting-files-between-design-and-release), [Files that scripts decode](#files-that-scripts-decode), and [Whole-file signing](#whole-file-signing). |
| Can we turn off scripting? | **Yes**, by using a runtime build without scripting. There is no setting for a single file. See [Turning off scripting](#turning-off-scripting). |

## How Rive approaches script security

* **The sandbox is the safety boundary.** Rive does not depend on trusting the person who made a file. Code in a file runs inside a sandbox that limits what it can reach, so even a harmful script cannot reach anything Rive doesn't allow.
* **Scripts start with nothing.** Rive adds each ability on purpose, as an API it controls: drawing, View Models, input, audio, and the objects in the script's own file. Network requests come only through script fetch, which your app turns on. A script can't use anything Rive didn't give it.
* **Signing proves where code came from, not that it is safe.** Rive requires a signature only where the engine cannot check code itself: Luau bytecode and compiled shaders. Rive compiles that code on its own servers, so it signs only code it produced. AnimaScript needs no signature, because the WebAssembly sandbox checks and contains every module.
* **A script that breaks the rules stops with an error.** Your app keeps running. Time limits stop most script calls that run too long, but not all of them, and a script can still use enough CPU, GPU, or memory to slow or freeze your app. See [Time limits](#time-limits) for what each limit covers.
* **Your app decides.** A file cannot act on your app directly. It can only hand values to your code, and your code decides what to do with them.

## What code in a file can do

Everything in a Rive file, code included, works inside the view your app gives it. AnimaScript and Luau scripts share the same restrictions.

A file can:

* Draw inside its view. It cannot draw outside the view or change the rest of your app's UI.
* Play sounds stored in the file. AnimaScript scripts can also generate sound.
* React to the input that reaches its view: pointer, keyboard, gamepad, and text entry.
* Read and change the View Model data your app binds to it.
* Report events to your app.

Scripts add logic to these abilities. A script can also read the current time, print messages to the debug console, and draw with [GPU Canvas shaders](https://rive.app/docs/scripting/wgsl-shaders.md). AnimaScript scripts can also run jobs and workers, on background threads where the runtime supports them. If your app turns on [script fetch](#script-fetch), scripts can also send HTTPS requests to the hosts you allow and read the responses.

Neither a file nor its scripts can:

* Make network requests, unless your app turns on [script fetch](#script-fetch). Downloads of **Hosted** assets come from the runtime, and your app controls them. See [Hosted assets](#hosted-assets).
* Read or write files on the device.
* Read device data such as location, contacts, photos, camera, microphone, or clipboard.
* Read app data that you have not bound to the file.
* Reach other Rive files your app has loaded. Each file runs its scripts in its own environment.
* Load code from outside the file, or create new code while it runs. The exception is `decodeFile`, on runtimes that support script fetch. See [Files that scripts decode](#files-that-scripts-decode).
* Navigate your app.
* Open URLs, unless you turn it on. Only the web runtime can do this, using the deprecated `automaticallyHandleEvents` option. See [Open URL events](#open-url-events).

## How each language is contained

| | AnimaScript | Luau |
| - | - | - |
| Status | Recommended for new scripts | Deprecated, but existing scripts keep running |
| Stored in the file as | A WebAssembly module | Luau bytecode |
| Web runtime | Runs on the browser's WebAssembly engine | Runs on the Luau virtual machine, itself compiled to WebAssembly |
| Apple and Android runtimes | Not yet supported, so files load and play without running their AnimaScript scripts | Runs on the Luau virtual machine |
| Signed | No | Yes |
| Contained by | The WebAssembly sandbox | The Luau sandbox, plus signing |

### AnimaScript

AnimaScript compiles to WebAssembly, a format designed to run code safely inside a sandbox. The Rive Editor or the Rive CLI compiles your scripts to a WebAssembly module and stores it in the .riv file.

* **On the web**, the runtime loads the module into the browser's own WebAssembly engine, the same sandbox that browsers use for all WebAssembly.
* **Outside the browser**, Rive runs modules with [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime), an open-source WebAssembly runtime, under the same sandbox rules. A .riv file carries these modules only as WebAssembly bytecode, never as machine code, and the runtime rejects anything else. Depending on the platform, a module runs as machine code compiled ahead of time, or on WAMR's interpreter. The Apple and Android runtimes do not run AnimaScript yet. See [Where Scripts Run](https://rive.app/rasc/docs/execution-lanes.html).

Inside the sandbox:

* A module can call only the functions Rive provides. It has no other way to reach the system.
* Every memory access stays inside the module's own memory. An access outside it, or a bad pointer passed to a Rive API, stops the module with an error.
* Threads and audio processors run in sandboxed modules of their own. Any memory they share belongs to the same file's scripts.

AnimaScript is not signed today. Its safety comes from the sandbox, which checks and contains every module, whoever built it.

In AnimaScript source, raw memory access must sit in functions marked `unsafe`, so a reviewer can find it with a text search. Even there, it reaches only the module's own memory. See [The Unsafe Gate](https://rive.app/rasc/docs/unsafe.html).

### Luau

Rive runs Luau scripts in a sandboxed [Luau](https://luau.org/sandbox) virtual machine with a reduced standard library:

* Base functions, without `loadstring`
* `table`, `math`, `string`, `utf8`, `buffer`, and `bit32`
* `os`, limited to `clock`, `date`, `difftime`, and `time`

There is no `io`, `package`, or `debug` library. Built-in libraries and globals are read-only, and `require` finds only scripts in the same file.

The Luau virtual machine assumes that the bytecode it runs came from the Luau compiler, which is why [Luau recommends signing](https://luau.org/sandbox) compiled code. Rive runtimes run Luau bytecode only if its signature is valid. See [What signing guarantees](#what-signing-guarantees).

### Shaders

AnimaScript and Luau scripts can both draw with [GPU Canvas](https://rive.app/docs/scripting/wgsl-shaders.md) shaders written in WGSL. Rive's servers compile and sign shaders when you export, and runtimes refuse shader code without a valid signature. A shader works only with the buffers and textures its script gives it.

### Time limits

* **AnimaScript:** every call into a script runs under a time budget, 200 ms in the runtimes. Native runtimes enforce it with a timer. In browsers, a call can be stopped only if the module was built with fuel checks. Debug builds always include them. Optimized builds, which every publish makes, include them only with `wasmFuel: true`. Without fuel checks, a script that never returns hangs the page. Builds made with `dangerouslyFast` have no time budget at all. Background jobs and workers have no time limit; they run until they finish or the runtime stops them. See [Build Settings](https://rive.app/rasc/docs/build-settings.html).
* **Luau:** Rive runtimes interrupt a Luau script call that runs longer than 200 ms. A script written to get around the interrupt can keep running.

Treat these limits as protection against mistakes, not against attacks. A file built to cause harm can still use enough CPU, GPU, or memory to freeze or crash your app.

## What signing guarantees

Rive signs compiled Luau scripts and shaders. When you export a file from the Rive Editor, or publish one with the Rive CLI, Rive's servers compile the source and sign the result. Rive compiles the code itself, so it signs only code it produced. Rive keeps the private signing key on its servers. Every Rive runtime that supports scripting carries the matching public key and checks the signature when it loads a file. Code that is unsigned, or that was edited after signing, does not run.

The Rive Editor and the Rive CLI run Luau scripts before they are signed, so you can work on them. Runtimes do not.

> [!NOTE]
>
> The signature check is part of the open-source runtime. See [text\_asset\_importer.cpp](https://github.com/rive-app/rive-runtime/blob/main/src/importers/text_asset_importer.cpp) and `File::acceptsScript` in [file.cpp](https://github.com/rive-app/rive-runtime/blob/main/src/file.cpp).

### What a signature does not prove

* **Who made the file, or that its scripts are yours.** Today, Rive signs every export with the same key, whatever the account or Workspace. Anyone can write Luau scripts in their own Rive account and export them with a valid signature, so a swapped file with someone else's scripts passes the check. The sandbox limits what those scripts can do. The signature does not.
* **That the rest of the file is unchanged.** Today the signature covers only compiled Luau scripts and shaders.

### What the signature covers

The signature covers the compiled code of the file's Luau scripts and shaders. It does not cover the rest of the file, including:

* Artboards, Timelines, and State Machines
* Text, images, fonts, and audio
* Default View Model values and script input values
* Event settings, such as the address of an Open URL event
* Which objects use which scripts
* AnimaScript modules

If someone changes these parts, the runtime cannot detect it. If someone edits a Luau script or shader inside the file, the signature check fails, and the runtime runs none of the file's Luau scripts and shaders. The rest of the file still plays.

### Whole-file signing

Whole-file signing is coming soon. It is planned to cover the entire exported .riv file, AnimaScript modules included, and runtimes are planned to refuse a file whose signature check fails. A change anywhere in the file would then stop it from loading. We're also working on a way to use the signature to tell which Workspace exported a file. Signing would then prove where a file came from. The sandbox stays the safety boundary.

## Protecting files between design and release

Today, any file exported from Rive with Luau scripts or shaders carries a valid signature, so signing cannot tell your files apart from anyone else's. Protect the path each file takes from the Rive Editor to your users, as you do for your app's code and other assets. [Whole-file signing](#whole-file-signing) will help once it ships. Even then, the checks below confirm that a file is the exact one you approved.

### Files bundled in your app

iOS and Android require a code signature that covers every file in your app package. After release, nobody can change a bundled .riv file without breaking that signature. The risk comes earlier, when a file passes from design to engineering. To reduce it:

* Keep approved .riv files in version control, and change them only through code review.
* Record the SHA-256 hash of each approved file, and check it in your build pipeline.

### Files your app downloads

If your app downloads .riv files, check each one before you load it.

**Step 1**

Serve files over HTTPS from a server you control.

**Step 2**

Compare the file's SHA-256 hash with a value built into your app, or verify a signature made with your own key. Don't fetch the expected hash from the same server as the file.

**Step 3**

Pass the bytes to the Rive runtime only if the check passes.

```swift Swift theme={null}
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 Kotlin theme={null}
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.
}
```

To get a file's hash, run `shasum -a 256 your-file.riv` on macOS, or `sha256sum your-file.riv` on Linux.

## Network requests, URLs, and navigation

### Script fetch

Script fetch lets AnimaScript and Luau scripts call `fetch()` to send HTTPS requests and read the responses, for example to load live data or download another Rive file. It's off until your app turns it on, and nothing in a file can turn it on.

Script fetch is available today in the web runtime and the Rive CLI. It's coming soon to every Rive runtime.

| Runtime | How to turn it on |
| - | - |
| Web | Use [`@rive-app/webgl2-threads`](https://www.npmjs.com/package/@rive-app/webgl2-threads) on a [cross-origin isolated](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated) page, and call [`RuntimeLoader.setScriptFetch()`](https://rive.app/docs/runtimes/web/rive-parameters#runtimeloader). |
| Rive CLI | Pass [`--allow-net`](https://rive.app/docs/cli/reference/commands#other). It works on macOS and Windows. |
| Apple, Android, and other runtimes | Coming soon |

Once script fetch is on, the runtime checks every request against these rules:

* `https:` URLs only, to host names. No IP addresses, no `localhost`, no one-word names, and no names that end in `.local`, `.internal`, `.lan`, `.localdomain`, or `.home.arpa`.
* No requests to Rive's own servers.
* No cookies, saved credentials, or cache. Scripts can't set headers such as `Cookie`, `Host`, `Origin`, or `Referer`. A script can send its own `Authorization` header.
* Up to 8 requests at a time per file, and more wait their turn. A file with both AnimaScript and Luau scripts gets 8 for each. Request bodies can be up to 8 MiB, and responses up to 16 MiB by default. Requests time out after 30 seconds by default, and after 60 seconds at most.
* Outside the browser, requests follow redirects, and every hop passes these rules. On the web, a redirect fails the request.

The host rules look only at the name. On Windows, the Rive CLI also refuses a name that points to a local or private address when it connects directly; through a proxy, that check is up to the proxy. On macOS it doesn't check the address yet, and on the web that check is up to the browser.

> [!WARNING]
>
> Script fetch covers every file your app loads. Once it's on, any script can send what it reads to the hosts it can reach, including View Model data your app binds and text typed into its Text Inputs. Turn it on only if you trust every file your app loads, and allow only the hosts those files need.

On the web:

* `allowedHosts` lists the hosts scripts can reach. If you leave it out, scripts can reach every host. Your page's own host works only when you list it by its exact name, since `"*"` doesn't include it.
* The browser's cross-origin rules decide whether a script can read the response. A simple request still reaches the server either way, so use `allowedHosts` to limit where requests go.
* Without `proxyUrl`, requests leave from your page, so servers that trust your site's origin answer them, including your own APIs. To keep script requests apart from your site, host the [reference proxy page](https://github.com/rive-app/rive-wasm/tree/master/js/examples/script_fetch_proxy) on a separate domain and pass its address as `proxyUrl`. A proxy page you change sets its own rules for cookies, cache, and redirects.

> [!NOTE]
>
> The request rules are part of the open-source runtime. See [net\_policy.cpp](https://github.com/rive-app/rive-runtime/blob/main/src/scriptnet/net_policy.cpp).

#### Files that scripts decode

A script can turn bytes into a Rive file with `decodeFile` and show that file's artboards in the Rive view. `decodeFile` comes with every runtime build that supports script fetch, so it works even while script fetch is off. The bytes can come from a download, or from data inside the script.

A decoded file runs its scripts in a sandbox of its own, under the same rules as any other file. Its Luau scripts run only with a valid Rive signature, even in the Rive Editor and the Rive CLI. Its AnimaScript modules need no signature, as in any file. It loads only the assets stored inside it, so it never downloads Hosted assets.

Your app never sees these files, so the checks in [Files your app downloads](#files-your-app-downloads) can't cover them. To limit where downloaded files come from, limit the hosts that script fetch can reach.

### Hosted assets

In the Rive Editor, you can set an image, font, or audio asset to **Hosted**. A Hosted asset stays on Rive's CDN instead of inside the .riv file, and some runtimes download it when they load the file. The web runtime and the legacy Apple and Android runtimes download from an address stored in the file, so a changed file can point that download at any server. Scripts cannot change the address or attach data to the request.

| Runtime | Downloads Hosted assets by default | How to turn it off |
| - | - | - |
| Apple, New Runtime | No | Nothing to do |
| Apple, Legacy Runtime | Yes | Pass `loadCdn: false` to `RiveFile` or `RiveViewModel` |
| Android, new runtime | No | Nothing to do |
| Android, legacy runtime | Yes | Call `setShouldLoadCDNAssets(false)` on the `RiveAnimationView` builder, set `app:riveShouldLoadCDNAssets="false"` in XML, or create `FallbackAssetLoader` with `loadCDNAssets = false` |
| Web | Yes | Set `enableRiveAssetCDN: false` in the `Rive` options |

For other runtimes, see the Loading Assets page for your runtime.

### Open URL events

Apple and Android runtimes do not open URLs from a file. If your code listens for events, it receives [Open URL events](https://rive.app/docs/editor/events/open-url-events.md) as data and decides what to do with them. The web runtime opens these URLs only if you set the deprecated `automaticallyHandleEvents` option to `true`. It is `false` by default.

### Navigation

Rive runtimes cannot navigate your app or open screens. A file can affect your app only through the events and View Model values that your code reads. If your code navigates, opens URLs, or calls your servers based on those values, check them first, as you would any input from outside your app.

## Turning off scripting

Rive runtimes have no setting to turn off scripts for one file or for the whole app. To play Rive files without scripts, use a runtime build that leaves scripting out. These builds run no AnimaScript, Luau, or shaders. Files still load and play, but anything a script drives stops working.

| Platform | Runtime without scripting |
| - | - |
| Web | Use [`@rive-app/canvas-lite`](https://rive.app/docs/runtimes/web/canvas-vs-webgl#rive-app-canvas-lite) or `@rive-app/react-canvas-lite`. These packages also leave out text, layouts, audio, and Vector Feathering. |
| Apple | Build [rive-ios](https://github.com/rive-app/rive-ios) from source with the `--no-scripting` option. See its [contributing guide](https://github.com/rive-app/rive-ios/blob/main/CONTRIBUTING.md). |
| Android | Build [rive-android](https://github.com/rive-app/rive-android) from source with the `noScripting` Gradle property, for example `-PnoScripting`. |

## Recommendations for apps that handle sensitive data

1. **Control where files come from.** Bundle files in your app when you can, and check every downloaded file before you load it.
2. **Bind only the data a file needs.** Scripts can read every View Model property your app binds to their file. Never put passwords, tokens, or full card numbers in a View Model.
3. **Use native text fields for secrets.** Scripts can read what users type into a [Text Input](https://rive.app/docs/editor/text/text-input.md) in their file.
4. **Keep secrets out of files.** A .riv file is not encrypted. Anyone who has the file can inspect its contents, including compiled scripts and any text in them.
5. **Treat what a file sends back as untrusted input.** Check events and View Model values before you act on them. For URLs and screens, accept only values from a list you define.
6. **Turn off what you don't use.** If you don't use Hosted assets, turn off their download. Leave script fetch off unless your files need it, and allow only the hosts they need. Leave `automaticallyHandleEvents` off on the web. If your files have no scripts, use a runtime without scripting.
7. **Ask for fuel checks on the web.** If your web app plays AnimaScript files from outside your team, ask for files built with `wasmFuel: true`, so the browser can stop a runaway script. Don't ship files built with `dangerouslyFast` unless you trust their source.
8. **Keep your Rive runtime up to date** to get security fixes.

## Security resources

* **Open-source runtimes.** The Rive runtimes are open source under the MIT license, so your team can review the code your app runs: [rive-runtime](https://github.com/rive-app/rive-runtime) (the C++ core, including the signature check and both scripting engines), [rive-ios](https://github.com/rive-app/rive-ios), [rive-android](https://github.com/rive-app/rive-android), and [rive-wasm](https://github.com/rive-app/rive-wasm).
* **AnimaScript.** [Where Scripts Run](https://rive.app/rasc/docs/execution-lanes.html) explains how AnimaScript runs on each platform and how its time budgets work.
* **SOC 2 Type II.** Part of the [Enterprise plan](https://rive.app/enterprise).
* **Reporting a vulnerability.** Contact us privately through the [support form](https://forms.rive.app/support?utm_medium=support_page). Please don't report security issues in the community forum or in GitHub issues.
