Security
What you are trusting when you install a plugin.
Installing a plugin is not like installing a browser extension with a permission prompt. There is no sandbox, and isolation is deliberately not attempted. A plugin is code you are choosing to run with full privileges, on your server and in your users' browsers.
Nothing in the SDK is a fence. A plugin that wants to reach the database file, spawn a process or call out to the internet does not have to go through any API to do it.
What a Plugin Can Do on the Server
Plugin server code runs inside the Sharkord process, as the user that started it. It can:
- read, change, or delete anything in the database, including every message and every account;
- read, write, or delete any file that user can reach on the host;
- make outbound network requests, so data can leave your machine;
- run arbitrary code, which means anything the host account can do.
What a Plugin Can Do in the Browser
Plugin client code is loaded from your server and executed on your app's own origin, in the browser of every user, whether or not they knew a plugin was installed. It has access to the DOM, to storage holding the session token, and to any request the app can make.
The admin who installs a plugin makes that decision for everyone connected to the server.
Capability Access
Commands, actions and UI slots are capabilities an admin can restrict to roles, from the plugin's permissions tab. Changing those rules needs MANAGE_PLUGIN_PERMISSIONS, which is its own permission because anyone holding it can grant those capabilities to themselves.
An unconfigured capability falls back to whatever the plugin declared with requires, and to public if it declared nothing. Two things follow:
- A plugin's declaration can only narrow, so it cannot use
requiresto hand itself more access than a user already has. - An admin can override it in either direction, including opening a capability to everyone.
That makes requires a default, not a guarantee. A plugin author who needs a hard rule checks ctx.permissions inside the handler. Hiding a UI component is presentation only: the server refuses nothing on its basis.
Plugin HTTP Routes
Plugins can register HTTP routes under /plugins/<plugin-id>/. A route is public unless the plugin passed auth or requires, because a webhook receiver has to be. When it did, the host resolves the caller and answers 401 or 403 before the handler runs.
Routes are capabilities too, so an admin can restrict one to roles, and restricting a public route forces authentication on it. The reverse does not hold: an admin widening access cannot strip auth off a route whose handler expects a caller.
Every plugin route shares one rate limit. Assume any route a plugin left public is reachable by anyone on the internet who can find the URL.
Reducing the Risk
- Run Sharkord in a container. Docker or an equivalent is the single most effective thing you can do: it puts a boundary between a plugin and the rest of the host.
- Install from sources you trust. Prefer plugins whose source you can read.
- Read the code before installing. If you cannot review it yourself, ask someone who can, or feed it to an AI assistant and ask what it does with the network and the filesystem.
- Keep plugins updated, and remove the ones you stopped using. Removing a plugin also deletes its data folder, its per-user data and its capability rules.
- Give
USE_PLUGINSdeliberately. Running plugin commands and actions is gated by it;MANAGE_PLUGINScontrols who can install them, andMANAGE_PLUGIN_PERMISSIONSwho can widen access.
About the Marketplace
Downloads from the marketplace are checked against the checksum published in the registry, so you get the file the author published. The archive is also refused if any entry would write outside the plugin's own folder, if its manifest names a different plugin than the one being installed, or if its sdkVersion does not match the server's.
That is all any of it guarantees. Every one of those checks is about the file arriving intact and in the right place, not about what the code does once it runs.
The marketplace is open to anyone. A listing is not a review, and the verified flag on an entry means the maintainers recognise the publisher, not that the code was audited. Do the same due diligence you would do for a plugin you found anywhere else.
For Plugin Authors
- Validate everything that comes from a user: action payloads, HTTP bodies, and anything read back from
ctx.userData. Command arguments are the one exception, checked against their declared types before your handler runs, and even there the check stops at the type. The contract is compile-time typing, not runtime validation. - Assume your public HTTP routes are called by strangers. Use
authorrequireswhere the route is for your own UI, and verify a shared secret or signature where it is a webhook. - Keep secrets in
secretsettings or in files underctx.dataPath, never in the client bundle.client/index.jsis served unauthenticated to every browser, so anything in it is public. Command arguments that carry secrets should be markedsensitive, which keeps the value out of the message chip. Plugin logs never carry command arguments at all; the server activity log does. - Do not rely on
requiresalone for anything that must never run for the wrong caller. Checkctx.permissionsin the handler. - Document what your plugin talks to and why. People installing it are trusting you.