Tauri 2 · wraps the official updater plugin

Ship in-app updates from private GitHub repositories.

The official updater returns 404 on private repos. This crate embeds an Authorization: Bearer token at build time and presets it on the official tauri-plugin-updater — on both the manifest request and the installer download. No fork. No extra commands. Your frontend keeps the official JS API, unchanged.

CI status crates.io version official updater npm version MIT license

Install both — on the Rust side this crate and a direct tauri-plugin-updater dependency (Tauri's ACL permission collection only sees direct dependencies); the frontend keeps the official @tauri-apps/plugin-updater, unchanged:

# 1 · Rust — src-tauri/Cargo.toml (both required)
[dependencies]
tauri-updater-private = "0.1"
tauri-plugin-updater  = "2"
# 2 · npm — frontend (the official package)
npm i @tauri-apps/plugin-updater

One header is all it takes

The official updater fetches a latest.json manifest and the installer asset over plain HTTPS. On a private repository both requests are rejected with 404 unless an authenticated token is attached. Nothing about the update flow needs to change — only the transport.

  1. Your app

    check() · downloadAndInstall()
    official JS API, zero changes

  2. tauri-updater-private

    Authorization: Bearer •••
    Accept: application/octet-stream

  3. GitHub

    manifest via raw.githubusercontent.com
    asset via api.github.com

  4. minisign verify

    pubkey pinned in tauri.conf.json
    independent of transport auth

Update authenticity is unaffected: installers are still verified against the minisign pubkey before anything is installed. Transport auth and update signing are separate concerns — kept separate.

Three steps to private updates

1

Point the updater at GitHub

tauri.conf.json — the manifest lives on your default branch.

{
  "plugins": {
    "updater": {
      "pubkey": "<minisign public key>",
      "endpoints": [
        "https://raw.githubusercontent.com/{owner}/{repo}/main/latest.json"
      ]
    }
  }
}
2

Register the plugin via this crate

Token is embedded at build time from UPDATER_GH_TOKEN. Grant updater:default in your capability file.

// src-tauri/src/lib.rs
.builder(|b| b.plugin(tauri_updater_private::updater_builder()?.build()))
3

Keep the official frontend API

Headers are applied automatically — pass no options.

import { check } from '@tauri-apps/plugin-updater';
const update = await check();        // Authorization header applied automatically
await update?.downloadAndInstall();  // ...also on the asset download

In your release workflow, export UPDATER_GH_TOKEN (a long-lived fine-grained PAT — the Actions-default GITHUB_TOKEN expires when the job ends and cannot be embedded):

- uses: tauri-apps/tauri-action@v0
  env:
    TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.UPDATER_SIGNING_KEY }}
    UPDATER_GH_TOKEN: ${{ secrets.UPDATER_GH_TOKEN }}

A topology that survives contact with GitHub

Private-repo endpoints have sharp edges. This crate encodes the combination verified end-to-end against a real private-repo Tauri 2 app on macOS (check → download → install → relaunch, v0.1.0 → v0.1.1), signature verification and version gating included.

Security posture, stated plainly

A stolen token can't ship malware

Installers are verified against the minisign pubkey pinned in tauri.conf.json before any install step. A leaked token can read your repo — it cannot push an update to your users.

Least-privilege by construction

Fine-grained PAT scoped to the target repository only, permission Contents: Read-only, with an expiry. Application code never handles the token — it's embedded at build time from a CI secret.

Honest about extraction, easy to rotate

The embedded token can be pulled from a distributed binary with strings. That trade-off is documented, not hidden: rotate by replacing the secret and rebuilding. A dedicated updates-only repo is documented as a future hardening path.

Add private-repo updates in three lines of Cargo.toml.

Both required — the wrapper plus a direct official-plugin dependency; frontend keeps the official npm package:

# 1 · Rust — src-tauri/Cargo.toml
[dependencies]
tauri-updater-private = "0.1"
tauri-plugin-updater  = "2"
# 2 · npm — official
npm i @tauri-apps/plugin-updater