Plugin Security and Privacy
Your plugin works with real people’s data. This page lists the rules, and the reason for each. Broadcast enforces many of them for you, and you must follow the rest.
What Broadcast does for you
- Your code runs in its own process. It cannot reach the database, files or server settings.
- Each call to Broadcast is checked: the plugin, the version, the channel and the permission.
- Your code sees only the channel it runs in. It cannot ask for another one.
- The network gate lets your code reach only declared hosts. It refuses private and internal addresses.
- Secrets are stored encrypted, and a page never sees them.
- A plugin page has no access to the admin’s session.
- The core adds the unsubscribe link and checks consent after all plugin hooks. No plugin can change that.
- With no
subscriberspermission, a plugin gets no personal data in events. - No plugin can write a tag or custom field that holds a topic, because a topic is consent.
- A plugin event reaches a webhook only if an admin turns it on. Broadcast builds that webhook, with no email address.
- Email addresses are hidden in logs.
- An admin reviews each version. Only an admin can deploy.
What you must do
Ask for the least
Ask only for what the feature needs. Use read if you do not write. Use an exact host, such as api.example.com, and not *.example.com.
A short list is also good for you, because the admin reads it before deciding.
Keep secrets secret
- Mark API keys, tokens and webhook URLs
"secret": true. - Read them with
await ctx.settings.secret("name"), in the call that needs them. - Never log them, never store them, and never return them from a hook.
- Send a secret only to the service it is for.
Do not put a key in your code. An admin reads the code, and the code is stored in plain text.
Treat personal data with care
Emails, names and custom fields are personal data.
- Read only the fields you need.
- Store personal data with
ctx.store.forSubscriber(id). It is deleted with the person. - If you keep it anywhere else, add the Subscriber Deleted Hook and delete it there.
- Never write personal data to the log. Log the subscriber ID.
- Send it only to the hosts the manifest declares, and only for the reason the admin expects.
If you send data to another service, say so in your plugin’s description. An admin may need to name that service in a privacy notice.
Never trust input
Input is anything that comes from outside your code:
- The
valueof a public action. It is not signed. - The
bodyof an API endpoint. - The
inputofpageDataand of an AI tool. signup_refon an event, and anything incustom_data.- Everything a remote service answers.
Check the type and the length, and compare it to a list of allowed values when you can.
const ALLOWED = ["good", "bad"]; if (!ALLOWED.includes(value)) return { title: "Sorry", message: "We do not know that answer." };
Be careful with a key built from input. new Map() and Object.hasOwn are safer than a plain object lookup, because __proto__ is a valid string.
Write safe pages
- Put text in the page with
textContent, neverinnerHTML. - Do not ask for a Broadcast password.
- Do not make a page that looks like a Broadcast sign-in page.
Be a good guest
- Handle repeats, so that a retry does no harm.
- Use bulk calls for many items.
- Do not loop forever. A slow call is stopped, and the failures count.
Permissions change trust
Each new permission asks for more trust. When a new version asks for other permissions or hooks, the admin sees the new ones marked New.
Changes to your events, rules and settings should only add things. If you rename a setting, its saved value is lost. If you rename a rule, saved segments break. Use a new name and keep the old one.
If you find a problem
If your plugin has a security bug, tell the people who run it, and release a fixed version as soon as you can. An admin can turn off a plugin in a channel at once, or stop all plugins with safe mode. See safe mode.