The Ultimate Guide to Bubble Security is out - 300 pages of privacy and security content

Great info and brilliantly put together @petter!
Really appreciate all of your efforts in breaking down this complex info into something accessible.
Keep up the great work!

Thank you @petter for another spectacular publication! Between this and your Performance book and other tutorial, I now have a firm grasp on DevTools.

Some follow up questions for you:

  1. You mention all info saved in all option sets is downloaded on all pages (144) – does this include notes/comments we have typed on the option sets? Same question for comments on Data Types and Fields.

  2. Your describe that Privacy Rules generally do not affect workflows (134-135) because users can still edit data without being able to view it, and you give an example of a server side workflow (Make changes to a thing). However, isn’t it true that for client side workflows (e.g. Set state) that a user cannot edit data without having access to view it – because an applied Privacy Rule would prevent Bubble from sending the data to the client’s device? In other words, if I want to hide data from a user, but want that data to be used in a workflow triggered by the user (e.g. as part of a Condition), then I need to make sure the workflow happens entirely on the server, or make the workflow a backend workflow. Right?

Thank you!

1 Like

Thanks again for the book Petter!
You saved my app from having laughably huge vulnerabilities. And you are completely right in saying that we as early-adopter developers have the responsibility to keep Bubble’s reputation high.

I have been struggling to authenticate Incoming API calls from Stripe (webhooks) and I’ve had to set the API workflow to “Can run without authentication”. I imagine that there must be a way to do this because many Bubble apps use Stripe. Do you have any tips on how I can go about securing the webhook?

Amazing book as usual! Thanks Petter!

Bubble supports Bearer Token authentication. You will need to generate an API token in your app’s settings page. After that you need to ensure Stripe adds a Bear Token Authentication field to the HTTPS Header:

Authorization: Bearer BubbleAPITokenHere

Notes:

  1. All data generated by a Bearer Token API call will have the Created By field set to Admin.
  2. The token is not scoped to a particular Workflow or Data API. It grants access to all publicly exposed Workflow and Data APIs.
  3. As such make sure your token is encrypted in transport, specifically only use HTTPS with your POST requests.
  4. Additionally make sure your token is encrypted at rest on the calling server.

In this context HTTPS provides a double role of encrypting any data being transmitted and asserting the identity of the Bubble application through trust certificates. Likewise the Bubble API Token held by the requester asserts the identity of the requester.

3 Likes

Hi @greg18

Thank you for the kind words!

1 - Notes/comments
No, notes/comments are contained within the editor files and do not transfer to the live app from what I’ve been able to find. If you perform a network-wide search in devtools (this short article describes how), you’ll find the comment if you inspect a Bubble editor tab, but not when you inspect a live app. This is true even if you reference the option set somewhere on the page. Note that I have only tested this for option sets, but I would expect that logic to apply everywhere.

2. Privacy Rules and Actions
This is a great question and I’m glad you brought it up since it’s a potential vulnerability. This will be a longer reply, so buckle up. I want to try and shed some light on two parts of it to clarify.

The first is the importance of understanding the difference between not being able to write to a Thing because of Privacy Rules (by disabling auto-bind) and not being able to write to a Thing because a Thing cannot be found (by disabling Find this in searches).

As you say, you will not be able to save changes to a database record that Bubble can’t find (because of Privacy Rules), but it’s important then to understand that it’s not the action that is stopped on a server level: the action (i.e. Make changes to a Thing) is running just fine, but since the record can’t be found (protected by Privacy Rules) there’s technically nothing to make changes to, and so the pen is there but there’s no paper to write on, so to speak.

While this in many cases can theoretically provide the protection you need, there are potential scenarios where this can give you a false sense of security. To understand this, we’ll need to define the difference between a search query and a lookup query. Performing a search means to send a call to the server with specific constraints to return a list of zero, one or more matches (such as all users with the last name Amlie). A lookup means to send a call to the server to fetch one record based on that records index key (what in Bubble is called its Unique ID). In that case you’ll only ever produce one result, and always the right one (such as the User with the Unique ID 163247823748237)

The challenge for Bubble developers from a security standpoint is that it’s not always clear when one method is used over the other. Privacy Rules are 100% consistent in how they function: the Find this in searches setting will completely disable the data from being found in a search. But when the Thing is referenced directly by use of its Unique ID, the query type changes to lookup and circumvents the Find this in searches rule. In most cases this is expected, but in other it can be confusing. I’ll illustrate this with three scenarios that I personally would rank as follows:

  1. completely expected
  2. not obvious but understandable
  3. surprising (but explainable)

All scenarios will discuss a User A that wants to make changes to a User B, and we’ll assume that the User type has the strictest Privacy Rules applied (can’t find in searches, can’t autobind and can’t view any fields):

Scenario 1: Direct database reference saved on Current User
In the first scenario, we’ll reference a Thing by use of a direct database connection: User B saved in a Field on User A such as Current User’s Boss. A Make changes to a Thing workflow using that field would would predictably work regardless of Privacy Rules, since this is a lookup (we’re not searching for the Thing):

The only Privacy Rule that can stop the above action from completing successfully is if the field Boss on the Current User is hidden, in which case the action will still run, but it will be trying to make changes to an empty record. But no other Privacy Rule will stop this change from being made.

Ok, everyone who knows their Privacy Rules knew this one. Onto a less obvious one:

Scenario 2: URL parameter
In this example, I’m in an app that relies on URL parameters for navigation and for fetching data. Someone has emailed me (User A) an URL that points directly to an Edit user form for User B, such as 应用宝官网-全网最新最热手机应用游戏下载

When loading the edit form, User A will not see any of User B’s information since all fields are hidden, but User B is loaded into the form - since the Unique ID is not hidden by Privacy Rules and I’m not relying on a search to find the User. Technically, this is a lookup In other words: if I make any changes to that User with an action, those changes will be saved to User B with no problems:

Setting up a URL like the above is fairly common, and I don’t see any reason to discourage it, but I wouldn’t be surprised if even experienced devs reading this are unaware that the record is technically there and can be made changes to. Many experienced devs are indeed surprised to see that the Unique ID field can’t be protected by Privacy Rules at all, since there’s rarely any reason to look for it.

The reason this works is the same as in scenario 1: since Bubble has the Unique ID of User B, it’s not performing a search behind the scenes; it’s performing a lookup, which Privacy Rules don’t protect.

Example 3: Do a search for with Unique ID as constraint
In the third example, we’ll do the same as above. We’ll assume that someone sent User A the Unique ID of User B. But this time, we’re not passing that ID through a URL parameter. Instead, we’re performing a Do a search for and making the changes to the result of that search. Since we have not enabled Find this in searches, surely the result will be empty and the action will attempt to write on an empty record:


What you’ll find if you try this is… * gasp! * that even this action will complete successfully. You’ll find the record even if it shouldn’t be searchable, and the action completes as if no Privacy Rules had been set up.

Why is that? The reason is the same as in the earlier examples: Privacy Rules adhere to what’s happening under the hood. Whether we as the app editor thinks we are performing a search is irrelevant to Privacy Rules - it only cares about how Bubble actually sends the query.

As soon as you use a Thing’s unique ID as a constraint, the query changes from search to lookup and is no longer protected by Privacy Rules - you’ll find the record with no problems and can make as many changes to it as you like.

Should it be this way? Honestly – I’m not sure. From a traditional web development perspective I’m sure this makes perfect sense, but to a Bubble developer with no knowledge on SQL queries it makes Privacy Rules appear inconsistent, since we’re obviously on the surface performing a search here.

All of these scenarios can and should be easily solved of course by placing an additional server-side condition on the action itself. In other words, you should never rely on a record being empty as a means of security: as long as the action is allowed to run, there’s a risk of some scenario where it’s not empty after all.

I’ve flagged the scenario to Bubble as a potential vulnerability, but it’s not given that they see it that way. Since it’s technically correct (and maybe impossible to change without breaking a lot of other stuff) there’s a fairly big chance that this is simply how it’s supposed to behave and they’re not going to change anything. From my perspective, the number of apps affected by this is likely quite small, but not non-existent: I am concerned that some devs are not aware of it since it’s not really communicated anywhere.

I’ll probably address this issue in an upcoming update to the book or a separate article, but want to get some clarification from Bubble first and at the very least get a confirmation that I’ve interpreted this all correctly. Everyone reading this should be aware that until we have it confirmed, my understanding could be incorrect in a minor or major way.

9 Likes

So, if some javascript guru knows the id of a database item and they know the names of your fields they can change the values of those items via the console of the browser and we can do nothing to prevent this?

If this is the case. Then we need a privacy option to determine if a user may edit a certain field.

3 Likes

This is the pressing question indeed. As of now I don’t know exactly how this is handled: but I’ll post an update as soon as I do.

Quick update (@mike_verbruggen):

I talked to Bubble about the possibility of using Javascript to “fake” a server-side Bubble action and exploit the fact that a lookup will produce a result regardless of privacy rules. As suspected, it’s not quite that easy.

The way I understand it based on these conversations is that Bubble doesn’t accept any server-side actions that it doesn’t recognise as part of the app itself. In other words, if an action exists on the page that makes changes to a database record (that could potentially be found via a lookup even when protected with Privacy Rules), there’s potentially a chance that a hacker would be able to trigger that workflow and make those changes. But - only if you as a developer left the action unprotected to begin with by not including conditions. As I touch upon in the book, constraints on server-side actions are also performed server-side (when possible), meaning that the action will be stopped on the server as long as the condition is there. The way I see it then, any vulnerability introduced as part of this logic is the fault of the developer, and not Bubble.

All that being said, this emphasises the importance of protecting both database (privacy rules) and workflows (server-side conditions) - not setting just one of them.

6 Likes

Thank you Petter,

I would argue that while this apparent lack of constraint around the Bubble unique id (kind of a UUID) seems bad, it is actually a well thought out bit of security engineering. What it boils down to is that on the Bubble architecture side the unique id is protected by the Chicken or Egg Problem. Within the Bubble architecture to discover knowledge of the existence of a unique id requires either having permission to search for a thing, or having a priori knowledge of theunique id to use in a lookup. In this manner the unique id falls within the same security envelop as API tokens: they are secretes you should never divulge, and are prohibitively expensive to guess. In this vein, my recommendation for a security practice is to only every leak unique data through slugs, which can be protected within the privacy settings.

The other way to think about it is: if an adversary has found a means of establishing the existence of one or more unique id from Bubble then they have broken a lot more security measures then just listing the unique id.

By far the greatest security vulnerability in Bubble are the community plugins. The introduction of malicious surveillance code that intercepts data posted to the actions provided in the plugin is a non-trivial possibility. To prevent this we are really reliant on a combination of Bubble reviewing the plugins, and market forces to screen out hostile code.

FWIW, here are the steps I’ve taken to secure my Stripe webhook endpoints.

And here’s a visual that might be helpful.

Everything runs smoothly, and I sleep just fine. :wink:

2 Likes

@aaronsheldon

Thanks for the thorough response – and yes, I agree with what you’re saying about the Unique ID after hearing Bubble’s response and giving it some thought.

I think understanding Privacy Rules also comes down to what your mental frame is: thinking about searches as a purely technical way to fetch data might leave you surprised that Unique ID’s will produce a result. However, thinking about searches in terms of discovering data shifts this frame, and it makes more sense that any UID that you already have knowledge of is per definition not discovered but has already been divulged. As such, you should set up your app to stop any User from discovering any data at all that they’re not supposed to have access to.

It’s easy to think of scenarios where a Unique ID might be misplaced (accessing the local device or Users sharing a link or screenshot for example), but with protected fields (privacy rules) and actions (constraints) adding additional layers of protection, I think it also makes sense to draw a line somewhere in terms of security paranoia.

I don’t really see the UID lookup as a Bubble vulnerability: my bigger concern is the degree to which Bubble devs are aware of these mechanics. I suspect many are unaware that a Thing identified by Unique ID in the Page Thing URL or a URL parameter will load (like my examples in the earlier post) and can be written to by an unprotected action. Technically there’s nothing wrong, but educationally that may still be a hole that hasn’t been properly filled. Bubble makes most things related to web development easy for beginners, but from what I’m seeing in coaching sessions they still haven’t quite finished the puzzle when it comes to communicating security best practices and the consequences for those outweigh those related to performance, design and other topics.

As for plugins, yes, that’s a chapter on its own. I’m not a plugin developer myself, so I honestly have limited to offer in that discussion but I would very much welcome a lot more strict vetting process from Bubble’s side to ensure security, version testing, etc.

2 Likes

I have added a new section in the book today that highlights the difference between a search and lookup discussed in this thread and how it affects security. The update is FREE for all buyers of the book.

The new section is available on page 151, and can be downloaded from your Gumroad library page: www.gumroad.com/library

For those who haven’t made the purchase, the book is available here: The Ultimate Guide To Bubble Security - How To Create Secure And Privacy-focused Apps In Bubble.io - Amlie Solutions

5 Likes

I have a doubt you want to clear up with you regarding server vs client side conditions.

I’m saving a database thing on a custom state.
Would the following condition make a workflow safe?
“Group A’s Event’s Admins contains current user”

I am not sure whether this condition is checked client-side or sever side, as it falls somewhere in the middle of the examples you give in the book (“element is visible” and “Current user’s administrator is yes”)

Thanks for your time and thanks again for your book.

1 Like

Thank you for your help! I’ve implemented the check for IP address and the signature timestamp freshness. I already feel safer.
I am not sure how you managed to authenticate stripe though. If I uncheck run without authentication, Stripe’s webhook just says that it is unauthorized. Any tips/suggestions on how to authenticate Stripe?

1 Like

@petter Congrats Petter! Have been waiting for this since our last chat. Just purchased and can’t wait to read it!

1 Like

@petter Just wanted to say I’m 144 pages in and cannot stop reading. Not only is it comprehensive with a wealth of context, but with such a delightful prose. :muscle:t2:

1 Like

@nico.dicagno

If I try to understand your set up here, you have a GROUP A which contains a CUSTOM STATE (list of Users) that may or may not contain the CURRENT USER?

I would not expect that to run server-side. Since the list is not stored in the database but relies on a variable stored on the device, it can be manipulated. So no, this doesn’t look like a secure way to set it up. If you store the list of Users in the database instead, Bubble will be able to confirm it on a server level.

Let me know if I misinterpreted your scenario.

@ada
Thank you! I look forward to hearing what you think, and to our next chat :slight_smile:

@philledille
Thank you so much Peter, that makes my day :slight_smile:

Thank you Petter!
My scenario is slightly different, but thanks for clearing up that scenario as well!

I have a GROUP A, which contains a Data Thing EVENT, whose field ADMINS (list of users) may or may not contain CURRENT USER.
A similar conditional would be GROUP A, containing a Data Thing ACTOR, whose field ADMIN (Yes/No) is Yes.

These seem like conditions which can be checked server-side, however the Database Things are stored on a custom state. Can a motivated malicious actor actually change what Thing is stored on the element’s custom state in order to make the condition pass?

You have ingrained in my head that client-side is no man’s land, so i don’t trust custom states anymore either.

Thanks

Hmm, that’s hard to say - technically they might be able to replace the ID of the Event in that custom state and successfully check a different Event for the list of Admins. But it would require that they actually know the ID of an Event that lets them pass the condition in the first place and it’s also perfectly possible that Bubble has safeguards in place for scenarios like this.

We’re moving into territory now where I’d caution against being too paranoid about potential security issues. If we’re talking about particularly sensitive data/workflows here I would consider reaching out to Bubble to ask, and perhaps moving it to the server altogether to be safe (i.e. Current User’s Current Event’s Admins). But the scenario you describe of exploiting the Unique ID of a different Thing may be within the realm of possible but unlikely.

I definitely recognise the urge to map out any potential security issue in my head, and I of course can’t really advice against that - but it’s important to find the right balance so as to not overcomplicate things.