Secure By Default

Picture of people looking at dozens of security cameras mounted on a wall
Photo by Matthew Henry on Unsplash

Far too often I see code where security is treated more like a checkbox instead of as part of the architecture of the product. As long as my Apex classes are running with sharing and I enforce FLS/CRUD access in my SOQL queries and DML statements, my code is secure, right? Technically that’s true, but as is always the case, it really depends on the context.

Tell me if you heard this one before

Imagine a manufacturing company called “Can It Already!” that produces the cans for canned food (can you?) and they use Salesforce to manage their customer orders. The account team wants to make sure that customers are called by the same account executives in order to build a relationship. To accomodate tracking this, they have requested a few fields on the Account object.

  • The last executive to successfully call the customer.
  • When that last call took place.
  • The outcome of that call.

For the sake of this post, let’s pretend that adding these fields is the best solution (it’s not). The account team logs all of their calls as Tasks, so you figure a Task trigger is the way to go here. Below is the service class you build that gets called by the trigger:

public with sharing class AccountCallLoggingService {
  public void logCallOnAccount(Task call) {
    if (call.Status != 'Success' || !hasAccessToAccount(call)) {
      return;
    }

    accountToUpdate.LastCaller__c = call.OwnerId;
    accountToUpdate.LastCallStatus__c = call.Status;
    accountToUpdate.LastCalledOn__c = DateTime.now();

    update accountToUpdate;
  }

  private Boolean hasAccessToAccount(Task call){
    UserRecordAccess accountAccess = [
      SELECT RecordId, HasEditAccess
      FROM UserRecordAccess
      WHERE
        UserId = :UserInfo.getUserId()
        AND RecordId = :call.WhatId
    ];

    return
      Schema.sObjectType.Account.fields.LastCaller__c.isUpdateable()
      && Schema.sObjectType.Account.fields.LastCallStatus__c.isUpdateable()
      && Schema.sObjectType.Account.fields.LastCalledOn__c.isUpdateable()
      && accountAccess.HasEditAccess;
  }
}

A 10 foot wall with a wide open gate

This service will only update an Account if the running user has update access on the appropriate fields and on the Account itself. This looks pretty secure, right? The code is enforcing security checks, but in practice this might have some unintended consequences.

By enforcing FLS/CRUD and sharing access on the Account record, the account executive (i.e. the running user) must now have that corresponding access. Granting that access, however, gives the account executive those permissions not only in the context of this trigger, but also everywhere in the CRM. The business may not want an account executive to be able to go to an Account record and update those fields manually, or to even be able to update the Account record at all. So while the code checks security access, have you really made the application more secure?

To be clear, this is not necessarily wrong - the problem comes with blindly enforcing security without regard to your user’s experience and inadvertantly creating a security vulnerability. Enforcing frequent password changes may sound like a better security model, but actually makes security worse. Similarly, enforcing security checks everywhere in your code may sound more secure, but in practice your users may have to be granted more permissions just so your system is usuable.

The worst example of this I have seen this is in managed packages that are trying to pass security review and throw with sharing into every class and WITH SECURITY_ENFORCED into every query. The code might pass security review, but without a nuanced approach, the customer is forced to choose between not being able to use the functionality, or having to expand their FLS/CRUD and sharing settings to a model that accommodates the code base instead of the organization.

Running user vs System user

With this in mind, let’s revist our service code with some adjustments:

public without sharing class AccountCallLoggingService {
  public void logCallOnAccount(Task call) {
    if (call.Status != 'Success') {
      return;
    }

    accountToUpdate.LastCaller__c = call.OwnerId;
    accountToUpdate.LastCallStatus__c = call.Status;
    accountToUpdate.LastCalledOn__c = DateTime.now();

    update accountToUpdate;
  }
}

This code is running without sharing and doesn’t check the running user’s access. Generally this would set off some alarm bells, but I would argue that this is more secure. This functionality is more of a system level operation that should run the same regardless of the running user. I would rather escalate the running user’s permissions in this isolated context instead of being forced to grant that access to the user across the entire system.

The key lesson here is that enforcing security is a nuanced process that is not as simple as throwing in a few keywords into your code base. When designing your features, consider what it means for your users and customers to do so securely, in terms of usability, side effects, and of course, what it is you are truly trying to secure.

So-Called Advent Calendar Day 24 - Take a Break

In the spirit of the holiday season, this is a series of short blog posts covering random things I have learned while doing Salesforce development, one for each day of Advent.

lighted christmas tree

I didn’t actually think I would make it to the last day of the advent. I hardly post once a month so trying to write a blog post every day of the advent seemed a little overambitious (though I guess I can’t really speak to the quality of these posts).

I’ll be taking the next few days off to get away from work and this holiday season I hope you get an opportunity to take a rest, too.

Merry Christmas and happy holidays, wherever you are.

So-Called Advent Calendar Day 23 - Incentivize a Safe Work Place

In the spirit of the holiday season, this is a series of short blog posts covering random things I have learned while doing Salesforce development, one for each day of Advent.

crosswalk signal
When your team does not feel safe to proceed, it will not last long

A few days ago I watched a video a colleague shared with our team called “The four components of high performing teams.” The speaker presents the four components along with some stories from her professional experiences to add some context. Here is the description of the the four components from the video:

  • Mastery - The skills & knowledge needed to do a great job, and a clear path to get to the next level.
  • Autonomy - The space to figure out their own solution to a problem & how they want to work
  • Purpose - A clear sense of direction, and the knowledge of how what they’re working on fits into the big picture & helps their team succeed.
  • Safety - A team that is afraid won’t take risks or experiment, a team that is afraid of finger-pointing won’t learn from mistakes.

Below is a link to the video. It is about 30 minutes long, but I think it is worth the watch, especially if you are building a new team:

The four components of high performing teams | Lisa van Gelder

The component the spoke to me the most was the concept of safety. I never really considered software development to be unsafe other than the risk of not getting enough sun and exercise. But even though I have been pretty much always physically safe in a job, I can’t say the same for my psychological safety.

I have had moments where I have broken down and cried in a stairwell. I have had days where I stopped in front of the entrance and stared at the building for 10 minutes, trying convince myself to walk in. I still recoil when I see a Slack notification on my phone - I don’t even use Slack at work anymore, but I just have this negative Pavlovian response to those notifications now.

These are feelings that I should never have about work, especially in the truly low stakes environments that I have worked in. No one is going to die if I deploy next week instead of today. It will be ok if the website is down for an hour. What’s the worst that will happen if a lead can’t convert on a Saturday?

But I have felt that overwhelming pressure before, that looming cloud that sets you into a panic to just do something to get it over with. Maybe these are a little extreme, and to be fair I recognize that these reactions are also things I need to work on personally. But I guarantee you have felt unsafe before too.

Have you ever blown out an estimate before, like doubling your estimated scope just to give you some padding? Have you ever delayed responding to a message or an email even if you have the time to answer just because “If I answer too quickly, I’ll set the precedent that I am always immediately available?” How about creating fake meetings on your calendar because you can’t trust that the time won’t be taken away from you at the last minute? Or maybe you answered emails and messages during your days off because something might happen if don’t?

All of these are signals of an unsafe environment. You may not be in an overtly toxic workplace, but that feeling of unease can really poison things. When you expect punishment for missing a deadline or for being incorrect on something, you will start to change your behavior to keep it from happening again.

The whole idea of “under promising, over delivering” is based on this premise of withholding information in order to protect yourself. For example, I could be 80% certain I could deliver something in a month, but the last time I gave that estimate, it was sold as it will 100% be delivered in a month, regardless of an unforeseen consequences. So instead of rushing to deliver at the last minute, maybe this time I double my estimate just to be safe.

To me, that sounds like an antagonistic relationship instead of a collaborative one. An environment that promotes safety, however, would allow for this dialog to happen. As a team, we should decide together to add some padding to the scope in order to account for uncertainty. We should be able to discuss and reflect not only on our victories, but also our mistakes and incorrect assumptions, so that we can be estimate more accurately in the future.

Most importantly, this has to come from the top, from a leadership level. So if there is anyone in management or executive leadership reading this, I encourage you to incentivize safety above all. Your reports should be comfortable to tell you that something went wrong, that something will take a longer than you hoped, so that you can make more tactical decisions base on all the information available. Because when you disregard that and insist that it needs to be done anyway, you disregard reality and end up delivering a solution that no one is happy about. And the longer that lasts, the more likely your team will decide they are better being somewhere else.