Skip to content

Security Model ​

Sandbox isolation ​

Every plugin runs in an <iframe sandbox="allow-scripts">. This means:

FeatureSandbox statusWhy?
allow-scriptsEnabledYou need JavaScript to run your plugin
allow-formsDisabledPlugins cannot submit forms to arbitrary domains
allow-same-originDisabledPlugin origin is null; no cookie/localStorage access
allow-top-navigationDisabledPlugins cannot navigate the parent window
allow-popupsDisabledPlugins cannot open new windows

The result: plugins are isolated from the surrounding app and cannot directly access sensitive data.

What the SDK never exposes ​

The SDK design removes entire categories of sensitive data before the plugin receives context:

  • Session tokens — never in ctx
  • E2EE private keys — never in ctx
  • User passwords or mnemonic phrases — never in ctx
  • Channel messages — plugins cannot read them at all, neither history nor new messages
  • Viewer identity in profile context — profile plugins only see the owner
  • Other users' direct message conversations — plugins have no DM access

The fetch proxy ​

All external HTTP/HTTPS requests from plugins go through Dissent.fetch(), which proxies them through dissent-core. This provides multiple protections:

ProtectionHow it works
User IP hidingdissent-core makes the request, not the user's browser. External servers see only the server IP.
Request validationdissent-core validates the URL scheme (http/https only) and Content-Type response.
Rate limitingRequests are per-server, not per-user, preventing plugin authors from DoS attacking third parties with your users' bandwidth.
Response size capsPayloads are capped at 1MB to prevent exhausting the client.

Example: A weather plugin fetches https://api.openweather.com/?q=London. Your IP never reaches OpenWeather — only the Dissent server's IP appears in their logs.

A plugin can only be granted what it declares in declared_permissions; the node refuses anything else. A channel or sidebar plugin that declares any permission shows a consent card before first load, whatever its tier — the tier is only a label.

Join grants every declared permission; View Without Joining grants none and still loads the plugin. The card returns when a plugin starts declaring a permission the user has not granted, and a permission the plugin stops declaring stops working for everyone immediately, whatever was granted before. Admins can uninstall plugins at any time in Server Settings. Details: Permissions.

Recommendations for plugin authors ​

  • Declare only the permissions you use. Each one is shown to the user on the consent card.
  • Validate all inputs. User-supplied data (slash command args, config) can be malicious. Sanitize before rendering to HTML.
  • Use the SDK for everything. Don't try to work around the sandbox. Dissent.fetch() is the only way to make external requests.
  • Avoid inline event handlers. Use addEventListener instead of onclick attributes. This mitigates XSS if you're rendering user-supplied HTML.
  • Log errors to console. The Dissent DevTools has a fetch log for debugging external API issues. Help your users debug by logging clearly.
  • Test in the sandbox. Before shipping, disable the SDK fallback and test that your plugin fails gracefully if context is unavailable. Users might run your plugin outside Dissent during development.