Security model
Umbra runs code written by other people on your desktop, so its security rules are part of the design, not an afterthought. These are the invariants. Never weaken one without an issue and review. To report a vulnerability, read SECURITY.md.
The widget sandbox
Section titled “The widget sandbox”- A widget page can only reach
umbra.local,community.umbra.local,data:,blob:and HTTPS hosts in itspermissions.hosts. Everything else gets a 403 fromWebResourceRequestedinWebHost.InitAsync. fetchJsongoes throughWidgetManifest.AllowsUrl: HTTPS plus a declared host.- Top-level navigation off the virtual hosts is cancelled and links open in the user’s browser.
Input and focus
Section titled “Input and focus”- Widget windows never activate themselves. They get keyboard focus only right after a physical mouse press on them (
WidgetWindow.TakeKeyboardFocus), so a widget can’t pull focus while you type elsewhere. - A widget can’t resize or zoom itself. The page’s
resizeandzoommessages are honoured only in edit mode, for an unlocked widget, with a physical mouse press (resize) or Ctrl held (zoom) over that window.
Community and agent-written files: relative forward-slash paths only, no .., allowed extensions only, 5 MB total, id equals folder. Everything is staged in a temp folder and validated before Commit (Community.cs). Built-in widgets (the generic <Content> mechanism, none shipped today) can’t be removed or shadowed by community or agent ids.
The one exception: commands
Section titled “The one exception: commands”The one exception to “a widget cannot run code” is permissions.commands (WidgetCommands.cs, CommandService.cs, CommandRunner.cs):
- A widget runs only a command it declared: exact executable plus an argv array, no shell. Parameters are
${name}whole-argument placeholders, validated and passed as single arguments, never a free command line. - Every command needs the user’s approval, stored per widget id and command hash in
approvals.json. A changed command is asked again. - Only a host-drawn prompt (after a physical click on the widget, rate-limited) or the Settings button approves. No control command,
umbractlcommand or MCP tool may approve:approvalsis read-only. - Runs are as the user, in the widget’s data directory, with a scrubbed environment, a timeout (max 120 s) and a 1 MB output cap, and never while the widget is disabled. Built-in widgets get no automatic trust.
See Running local commands for the rules a widget author sees.
Updates never grant access silently
Section titled “Updates never grant access silently”Community.PrepareAsync diffs the downloaded manifest against the installed one (PermissionDiff: hosts, system, media, notifications, commands), and PendingInstall.Require refuses until the user accepts. Settings asks with a host-drawn box (SettingsWindow.ConfirmNewAccess), umbractl install needs --accept-permissions, and the MCP server strips that argument. Any new install path (for example “Update all”) must go through PrepareAsync/InstallAsync with acceptPermissions false by default.
Themes are data
Section titled “Themes are data”Token names come from Theme.Allowed; values must match the color and length patterns (mirrored in scripts/catalog.mjs and schemas/theme.schema.json), and catalog tokens are re-validated before Settings draws them. A theme can’t contain code, url() or any other way to load something.
Automation
Section titled “Automation”The control pipe uses PipeOptions.CurrentUserOnly. Never add a network listener. Control commands may check for updates but never install one.
Self-update
Section titled “Self-update”Updates.cs runs in the host only. The installer must be an HTTPS download from github.com/AmineChr54/umbra/releases/download/ and named UmbraSetup-<release version>.exe; it’s verified before it runs.
Privacy
Section titled “Privacy”No telemetry and no account. The network traffic Umbra itself makes is the daily GitHub release check, the catalog when Browse is open, and requests widgets make to hosts they declared. Backups never include command approvals, and diagnostics contain no option values, proxy address or widget data.
