Security foundations for Bubble apps
Privacy rules protect data read access. Backend workflow settings and workflow checks protect actions. Page logic does not do either job reliably enough to be treated as security.
Most Bubble security issues I see come from mixing those up.
Put privacy rules on sensitive data types
If a user should not be able to read a thing, protect that thing with a privacy rule.
Do not rely on page redirects, hidden groups, workflow conditions, reusable properties, group variables, or frontend searches to hide data. Those can make the page behave nicely. They are not the rule Bubble uses to decide what data the user can receive.
For a B2B app, a Note might belong to an Organisation. This privacy rule is common:
This Note's Organisation is Current User's OrganisationAdd the empty check:
This Note's Organisation is Current User's Organisation
and This Note's Organisation is not emptyIf the Note's Organisation field is empty because a workflow failed, and the logged-out user's Organisation is also empty, the first rule can accidentally match. Orphaned records should be private by default.
This is also why I would not blindly use Bubble's built-in Creator field as the ownership model for every data type. If a thing belongs to an Organisation, Team, Account, or Seller, create that field and use it in the privacy rule.
Keep backend workflows private unless they need to be public
A public backend workflow is an API endpoint.
If the workflow sends emails, deletes things, changes billing state, creates users, or updates private data, assume someone can try to call it directly.
If a backend workflow is only called by Bubble using Schedule API workflow, a database trigger, or another internal Bubble flow, keep it private.
If it has to be public, make that deliberate. Do not rely on an obscure endpoint name. Backend workflow names and app structure are easier to find than many people assume.
Use the narrowest backend workflow authentication mode
Bubble backend workflows now have these authentication modes:
None requiredAdmin onlyUser & admin
Use Admin only for internal server-side API Connector calls or controlled server/API integrations where an admin token can be used. That restricts the endpoint to admin tokens, which is much safer than accepting any logged-in user.
Use User & admin only when the endpoint genuinely needs to be called as the current Bubble user or with an admin token. Then check what that user is allowed to do inside the workflow.
Use None required for true external unauthenticated calls, like webhooks. Verify the request inside the workflow before changing data. For a Stripe webhook, that means checking the request is really from Stripe before marking an invoice as paid or granting access.
Older Bubble posts often talked about "requires authentication" as if it meant "any logged-in user or an admin token". With the current modes, Admin only is the safer default for internal API-style calls where it works.
Check permission inside the workflow
A logged-in user is not automatically allowed to run every workflow.
For example, a marketplace buyer may be allowed to read a Listing. The delete workflow still needs to check ownership.
If a delete workflow receives a Listing parameter, check the owner before deleting:
Listing's Seller is Current UserUse the real relationship for the app. It might be Project's Owner is Current User, Invoice's Organisation is Current User's Organisation, or Order's Vendor is Current User's Vendor.
Privacy rules may stop the user from passing records they cannot read. That helps. But plenty of apps allow broad read access and narrow write access, so the workflow action still needs its own check.
Secure the workflow action itself.
Do not use page state as a permission check
Treat anything sent to the browser as visible or changeable by a technical user.
That includes:
reusable element properties
group variables
custom states
hidden group data sources
visibility conditions
"not clickable" settings
redirect logic
frontend workflow conditions
client-side API Connector calls
Use these for UI and maintainability. Do not use them as the final permission check.
A reusable property called canEdit is fine for keeping the page readable. The edit workflow should still check that the current user can edit the thing.
A group variable called selectedProject is fine for UI state. The workflow that modifies the project should still check that the current user owns or can manage that project.
Redirects are not enough for admin pages
An admin page should redirect non-admin users away. The admin UI should also be hidden from non-admins.
Still add privacy rules to the admin data. Still check admin-only workflow actions on the server.
Users can inspect page structure, interrupt client-side behaviour, or call public workflows directly. The app should still refuse the request.
Security review checklist
Start with this:
Add privacy rules to every sensitive data type.
Add
is not emptychecks to privacy rules that compare ownership fields.Keep backend workflows private unless something external needs to call them.
Use
Admin onlyfor internal public backend workflows where possible.Use
None requiredonly for true unauthenticated calls, and verify the request inside the workflow.Check record ownership or role permission before update/delete actions.
Do not use hidden buttons, reusable properties, group variables, or frontend searches as permission checks.
Keep secrets out of API Connector calls.
Test as a logged-out user, a normal user, and each privileged user type.
The test I care about is simple: if a user knows the endpoint name, parameter names, and record unique ID, does Bubble still refuse the wrong request?