Skip to content

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.

  • A widget page can only reach umbra.local, community.umbra.local, data:, blob: and HTTPS hosts in its permissions.hosts. Everything else gets a 403 from WebResourceRequested in WebHost.InitAsync.
  • fetchJson goes through WidgetManifest.AllowsUrl: HTTPS plus a declared host.
  • Top-level navigation off the virtual hosts is cancelled and links open in the user’s browser.
  • 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 resize and zoom messages 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 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, umbractl command or MCP tool may approve: approvals is 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.

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.

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.

The control pipe uses PipeOptions.CurrentUserOnly. Never add a network listener. Control commands may check for updates but never install one.

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.

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.

Sandbox and permissions · WebView2 host · Updates