Plugin And App Upgrade Trust
Scryer never installs code just because it downloaded it. Every catalog, plugin artifact, rule pack, and application upgrade arrives with a Sigstore signature bundle. Scryer verifies that bundle offline against pinned trust roots, confirms the signer is the expected GitHub repository and release workflow, and then checks the bytes against the digests and metadata the signed document promised. This page lists those checks in the order they run.
What Is Signed
Section titled “What Is Signed”| Object | Required signer | Fetched from |
|---|---|---|
| Plugin catalog redirect and catalog | scryer-media/scryer-plugins, workflow release-plugin-v3.yml | cdn.scryer.media, with a GitHub fallback |
| Official plugin artifact | The required_signer the signed catalog declares for that plugin | The catalog’s artifact URL, then its mirrors |
| Verified community plugin | The community repository the central catalog approved | That repository’s catalog |
| Manual GitHub repository plugin | The repository you entered, any workflow | That repository’s catalog |
| Rule pack | scryer-media/scryer-plugins, workflow release-plugin-v3.yml | cdn.scryer.media |
| Application upgrade manifest | scryer-media/scryer, workflow scryer.yml, at the exact release tag | The GitHub release for that tag |
Uploaded WASM files are the one unsigned path. See Uploaded WASM Files.
Sigstore Verification
Section titled “Sigstore Verification”Scryer’s release workflows sign with Sigstore’s keyless flow. GitHub Actions presents an OIDC token, Fulcio issues a short-lived certificate that records which repository and workflow ran, and Rekor logs the signature. Scryer verifies the resulting bundle without contacting any Sigstore service. Every step below must pass.
- Bundle shape. Scryer accepts a Cosign v2 blob bundle or a Sigstore v0.3 message-signature bundle, never both in one document. A bundle must carry exactly one leaf certificate and exactly one Rekor entry of kind
hashedrekordv0.0.1. The SHA-256 digest recorded in the bundle must match the downloaded bytes. - Rekor entry. The entry’s log ID must be a pinned Rekor key that was valid at the entry’s integrated time, and the signed entry timestamp must verify with that key. For v0.3 bundles Scryer also verifies the inclusion proof: the signed checkpoint, its tree size and root, and a Merkle root recomputed from the leaf.
- Binding. The digest, signature, and certificate inside the Rekor body must be byte-equal to the bundle’s signature and certificate. A genuine log entry cannot be paired with a different artifact.
- Artifact signature. The signature is verified with the public key in the leaf certificate.
- Fulcio chain. The leaf must chain to a pinned Fulcio root with the code-signing key usage, and the chain must have been valid at the Rekor integrated time and at every RFC 3161 timestamp the bundle carries.
- Certificate transparency. The leaf must embed exactly one signed certificate timestamp, signed by a pinned CT log key and no newer than the Rekor time.
- RFC 3161 timestamps. A v0.3 bundle may carry up to four. Each must bind the signature and chain to a pinned timestamp authority with the timestamping key usage.
- Signer identity. The OIDC issuer must be
https://token.actions.githubusercontent.com. The certificate’s repository must equal the required repository. When a workflow is required, a certificate URI must equalhttps://github.com/<repository>/<workflow>@refs/tags/<tag>. Only tag refs are accepted, so a build from a branch is refused even from the right repository. Application upgrades require the exact release tag.
Trust Roots
Section titled “Trust Roots”- Embedded snapshot. The binary ships a copy of Sigstore’s
trusted_root.jsonwith a provenance record naming its source and SHA-256. The digest is checked when the snapshot loads. - Refresh. At startup and every six hours Scryer fetches a fresh root through Sigstore’s TUF repository with a two-minute timeout. A failed or slow refresh keeps the current snapshot and retries in five minutes. Verification never waits on the network.
- Time windows. Every Rekor key, CT log key, Fulcio chain, and timestamp authority carries a validity window from the root. A key that was not valid at the signing time is not trusted, even if it is in the root.
Plugin Install Pipeline
Section titled “Plugin Install Pipeline”Installs, upgrades, and automatic updates from a catalog run the same pipeline. A manual GitHub repository install differs only in the signer it requires.
- Catalog. The redirect document and the catalog it points to are each signed and verified. Both are parsed with strict schemas that reject unknown fields, and decompressed under fixed size limits. A community catalog must publish exactly one plugin whose id, support tier, source repository, and signer repository all match the entry the central catalog approved.
- Download. The artifact and its signature bundle are fetched over HTTPS from the primary URL, then each mirror. Artifact fetches refuse redirects. Catalog fetches follow at most three, each re-validated as a public HTTPS target.
- Signature. The bundle is verified as above, using the catalog entry’s required signer.
- Digests and size. The compressed artifact must match the catalog’s BLAKE3 digest. It is decompressed under the declared byte count, and the result must match the catalog’s WASM digest and equal that byte count exactly.
- Descriptor. The WASM module is loaded and validated for its plugin kind. Its descriptor must report the same id, plugin type, provider type, and version as the catalog entry. The SDK contract is checked against the running host, host permissions such as allowed hosts are validated, and provider types reserved for first-party code are refused.
- Host-process capability. A notification plugin that asks to spawn host processes is refused unless its support tier is Official. Verified community and manual plugins can never carry it.
- Persist and reload. The compressed bytes, digests, and descriptor are stored. Every time Scryer loads a plugin from its database it re-verifies the WASM digest and re-runs the descriptor checks, so a tampered row fails to load.
Automatic updates take exactly the release the update badge would offer, restricted to stable releases on the installed major.minor. A failed update restores the previous installation row.
Uploaded WASM Files
Section titled “Uploaded WASM Files”Uploading a .wasm or .wasm.zst file from Settings → Plugins skips signature verification, because there is nothing to verify against. Scryer still requires the manage-system-settings permission and an explicit risk acknowledgement, caps the decompressed size, loads and validates the descriptor and SDK contract, checks host permissions, refuses the host-process capability, refuses reserved provider types, and refuses to replace a bundled plugin. The result is recorded as a manual, unverified installation.
Bundled Plugins
Section titled “Bundled Plugins”The Newznab and Torznab plugins are compiled into the binary. The build fetches them from the signed catalog and verifies them with Cosign before embedding. Their signature bundles and a provenance record listing the catalog version, artifact URLs, and BLAKE3 digests live in the source tree under crates/scryer-plugins/builtins.
Application Upgrade Pipeline
Section titled “Application Upgrade Pipeline”Upgrade on the System page runs this pipeline. See Upgrading for the operator view.
- Eligibility. Only portable installs and direct MSI installs are upgraded in-app. Docker, Homebrew, Winget, Windows service or session-zero layouts, and installs with
SCRYER_DISABLE_SELF_UPGRADEset are operator-managed. - Request. The requested tag and version must match the current update notice, and the version must be strictly newer than the running one. One upgrade runs at a time under the system maintenance lock.
- Manifest.
scryer-upgrade-manifest.jsonand its.sigstore.jsonbundle are fetched from the GitHub release for that tag, HTTPS only, under fixed size caps. The signature must come fromscryer-media/scryer, workflowscryer.yml, atrefs/tags/<tag>. - Manifest contents. The schema must be
scryer.upgrade.manifest.v1. Tag and version must equal the request, and the version must be plain semver. Each artifact URL must start withhttps://github.com/scryer-media/scryer/releases/download/<tag>/and end with its asset name. Digests are lowercase BLAKE3 hex. Artifacts are unique and sorted. Portable archives list their members with relative paths and no..or backslashes. - Download. The artifact for this OS, architecture, and channel is streamed into
application-upgrade/stagingunder the config directory after a free-space check. Neither the reported length nor the streamed bytes may exceed the manifest size. BLAKE3 is computed during the download and again from the file on disk. - Archive. A portable tar.gz must contain exactly the member paths and sizes the manifest lists, all regular files. Extraction refuses absolute paths, parent segments, and drive prefixes, and sets the executable bit only on members the manifest marks executable. An MSI has no members and is handed to Windows Installer by the upgrade helper.
- Apply and confirm. A journal is written before the binary moves, and the previous executable is kept as a backup. On the next start Scryer checks that the expected version and executable path actually booted. If not, the run is marked failed and the backup is preserved.
What Is Not Checked
Section titled “What Is Not Checked”- Provenance attestations. Scryer does not fetch or evaluate SLSA provenance at install time. Trust rests on the Sigstore certificate identity, which records the repository and workflow that built the artifact, plus the signed digests.
- Windows installer signatures. Scryer verifies the MSI’s BLAKE3 hash against the signed manifest. It does not check Authenticode itself.
- Builds without trust support. A Scryer build compiled without the plugin-trust feature refuses every catalog, plugin, and upgrade verification rather than skipping it.
Related
Section titled “Related”- Plugins covers plugin kinds, support tiers, and manual installs.
- Upgrading covers the in-app upgrade from the operator’s side.
- Extensibility defines plugins, providers, and the provider catalog.