Files
LiveWall/docs/plugins/security.md
T
2026-11-04 14:29:22 -06:00

123 lines
6.6 KiB
Markdown

# Security model
LiveWall is a wallpaper app that can run third-party code. This page states plainly what that means,
what the app does about it, and what it cannot do.
## The blunt version
A plugin is a .NET assembly loaded **into LiveWall's own process**, with LiveWall's rights. There is
no sandbox, no separate account, no permission-checking kernel layer. A plugin can:
* read and write any file your user account can reach (documents, browser profiles, keys);
* open network connections and upload whatever it found;
* change or delete your LiveWall settings and wallpapers;
* register global hotkeys and log keystrokes of its own (as any process can);
* crash LiveWall (only the plugin's own failures are contained — see below).
The permission list is therefore **informed consent, not a security boundary**. LiveWall enforces it
where enforcement makes sense (it will not register an overlay without `DesktopOverlay`), and the
warning dialog shows exactly what a plugin asked for, but a hostile plugin that lies in its manifest
is only limited by your user account.
Install plugins the way you install any other program: from a source you trust, ideally with a
reviewable repository.
## What LiveWall actually does
### The warning before anything runs
Two gates, both modal, both requiring an explicit acknowledgement:
1. **The plugin system itself.** Turning on *Enable the plugin system* shows a full-screen-format
warning: third-party code, unverified plugins may cause harm to your computer, nothing is
sandboxed, only proceed with sources you trust. The checkbox must be ticked before *Enable plugins*
becomes clickable. `ConsentGiven` is stored, so this is asked once per installation.
2. **Every single plugin.** Enabling a plugin shows a card with name, author, version, id, folder,
its **SHA-256 hash**, a verified/unverified verdict, and the full permission list expanded into
human sentences. Again the acknowledgement must be ticked first.
The same warning banner stays visible at the top of the **Plugins** tab permanently — the user is
never more than one click from the reminder.
### Off by default
`Plugins.Enabled` is `false` in a fresh configuration, and `AllowUnverified` is `false` as well. With
both off, nothing in `%APPDATA%\LiveWall\plugins` is loaded at all; the folder is only scanned so the
list can be shown.
### Hash-pinned trust
* Every plugin's entry assembly is hashed (SHA-256) at load time.
* When the user accepts a warning, that exact hash is stored in
`%APPDATA%\LiveWall\plugin-trust.json` as `{ "<plugin id>": "<sha256>" }`, and mirrored into
`settings.json` (`Plugins.Plugins[] { Enabled, ApprovedHash }`).
* If the DLL changes — an update, but also the first sign of tampering — the hash no longer matches,
the plugin is **unverified** again, and the warning reappears. There is no way to enable a
mismatched plugin silently: the *Enable* button path always shows the dialog.
* **Forget trust** (in the row, visible for verified plugins) removes the stored hash; the next
enable asks again.
* A manifest may also carry a `checksum`; when present it must match the assembly or the plugin is
rejected outright (`ChecksumMismatch`), which lets a publisher pin the exact build they shipped.
### Failing safely
* `Initialize`, every command, page build, filter pass, renderer call, hotkey action and event
handler is wrapped: exceptions are logged with the plugin id and the offending feature stops being
used. The plugin shows as **Failed** with the message.
* A plugin that fails to load is never retried in a loop.
* A crashing native plugin can still take the process down (as can any in-process code). LiveWall
logs each step of loading so the culprit is identifiable afterwards.
* LiveWall never executes a plugin it has not been explicitly told to enable — there is no
auto-load from a zip, download, or command line.
### Isolation of the file system
* Plugin data goes to `%APPDATA%\LiveWall\plugin-data\<id>\` (`IPluginStorage`). Folder names are
sanitised from the plugin id, so `../../Windows` cannot be used to escape.
* The plugin folder itself is `%APPDATA%\LiveWall\plugins\<folder>\`. Nothing else in the app's own
data folder is exposed through the SDK except `Storage.PathFor`, which is confined to the plugin's
data folder.
### Assembly loading
Plugins are loaded into a **collectible `AssemblyLoadContext`** that has `LiveWall.Sdk` shared with
the host. This means:
* two plugins can ship different versions of the same helper library without colliding;
* unloading actually releases the assembly, so a fixed plugin can be retried after **Rescan**;
* a plugin cannot accidentally shadow `LiveWall.Sdk` and hand back the wrong interface type.
## What LiveWall does *not* do
* It does not sandbox, virtualise or restrict the plugin's file/network/process access.
* It does not scan plugins for malware, and does not transmit hashes anywhere.
* It does not "verify" against a public signature: **verified** on this machine means *"this exact
file is the one you approved"*, not *"this file is trustworthy"*.
* It does not protect against a plugin that persists (auto-start entry, scheduled task) — check
`shell:startup`, `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` and Task Scheduler if you
distrust something you ran.
* It cannot undo what a plugin already did to your files. Back up first if you are experimenting.
## Advice for users
1. Turn the plugin system on only when you have a plugin you actually want.
2. Read the permission list. `FileSystem` + `Network` together is "it can upload my files" — that had
better be a plugin you trust.
3. Prefer plugins whose source you can read. A DLL alone is a black box.
4. If the hash of a plugin you have been using changes after an update, that is expected — but it is
also exactly what tampering looks like, so treat the new warning as real.
5. To be sure a plugin is gone: **Disable** it, delete its folder, and delete
`%APPDATA%\LiveWall\plugin-data\<id>`.
## Advice for developers
* Declare **all** permissions you use, and nothing else. Over-declaring trains users to click through.
* Never call `SetWallpaper` in a loop; it reloads media.
* Don't write outside `context.Storage.DataFolder`. If you need a user-chosen location, ask for it
explicitly and remember it.
* Long work belongs on the thread pool; the UI thread stalls the wallpaper and your own overlay.
* Log through `context.Log`, not to your own file, so everything is in one place for support.
* Treat the returned `IDisposable`s and `ShutdownToken` seriously: unload happens while the user is
watching, and a plugin that leaves a timer running keeps the load context alive.