Appearance
Security Model
Sandbox isolation
Every plugin runs in an <iframe sandbox="allow-scripts">. This means:
| Feature | Sandbox status | Why? |
|---|---|---|
allow-scripts | Enabled | You need JavaScript to run your plugin |
allow-forms | Disabled | Plugins cannot submit forms to arbitrary domains |
allow-same-origin | Disabled | Plugin origin is null; no cookie/localStorage access |
allow-top-navigation | Disabled | Plugins cannot navigate the parent window |
allow-popups | Disabled | Plugins 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:
| Protection | How it works |
|---|---|
| User IP hiding | dissent-core makes the request, not the user's browser. External servers see only the server IP. |
| Request validation | dissent-core validates the URL scheme (http/https only) and Content-Type response. |
| Rate limiting | Requests are per-server, not per-user, preventing plugin authors from DoS attacking third parties with your users' bandwidth. |
| Response size caps | Payloads 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.
Consent model
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
addEventListenerinstead ofonclickattributes. 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.