In the age of the Citizen Developer, enabled by such low code platforms as OutSystems, it’s still critically important to ensure that Data Security is at the core of the architectural landscape. Democratised development can empower your teams to increase efficiencies — however, well-intentioned citizen developers may not always be aware of the consequences of their actions. Having these proponents for efficiency in your business is a valuable commodity, but protecting critical information whilst protecting your team members and the business is vital.
Making the right decisions early, ensures that data such as Personally Identifiable Information (PII) is protected and their access is audited in line with compliance protocols.
But what are the risks?
In a shared development environment, without the proper guidance and controls in place, developers without suitable experience can easily expose sensitive information in ways that breach a company’s responsibility to that data. Such an example could be incorporating public entities with PII into applications without the proper authentications or protections on displaying or editing that data.
It’s not only inexperienced developers that can expose sensitive information. It can also be through generational change in the development team where knowledge is lost over time. The importance of protecting some data may not be known to new team members, and thus consuming that data into other applications could breach company or legal requirements.
Protecting what needs protecting!
In both situations described above, a well-architected environment is essential, to survive misuse of critically sensitive information and the unintentional exposure of that data. And the first step to doing this is to isolate the data.
OutSystems makes it easy to import dependencies from other modules, such as core database modules, but when we expose these entities, we also potentially expose the data within them. An entity that is exposed as public and read-only can still be consumed within another module, allowing its data to be displayed. There are no controls available to us as the custodians of that data once we’ve made the entities public.
Within that multiple developer environment, or indeed multi-generational environment, if a developer has exposed an entity as public, that developer is completely unaware of any consumers of that data without active monitoring.
So the first step in enforcing data security is ensuring that the module has no public entities — we need to be able to control data exposure to other modules.
Once we’ve marked all entities with the Public property set to “No”, it means all the entities, data, and associated structures are not available to any consumer modules. We’ve taken the first step in controlling how our data is accessed. We’ve built a Wall around our data, and now we need to build a Moat.
By removing direct access to the entities in our core module, we also need to restrict who can modify the module. It’s no use having a wall around our data if we also leave a gaping ability for any developer to come in and dismantle that Wall.
We can achieve this by using the OutSystems LifeTime console and restrict access to certain applications. In this instance, we would create a trusted Team that has permission to Change and Deploy this core module. Anyone not in that team should have a lower permission level, such as Open and Debug Applications for this core module, allowing them to add dependencies to their own modules but not modify the core module.
The Moat, the Wall — but how to access the data?
Now we’ve got two layers of protection for our data. We need to consider how to provide access through a regulated Gate. The best way is to now create Service Actions — these are exposed end-points where we control the flow of data to the consumer. And it’s where we also wrap our standard CRUD database actions.
When entities are marked as public and not set as Read Only, the standard entity actions are available to us — Create, Read, Update, Delete (CRUD). However, since we’ve not made the entities public, we need to ensure we have some way to get data out or put data in.
CRUD Wrappers are an essential method for protecting and sanitising the data as it’s coming into the database, ensuring that values like Updated On and Updated By have values on the first write or fulfilling other data requirements. We can create these wrappers in our core DB module, enforcing any business requirements on that data.
OutSystems provides many benefits through the optimisation of data queries and scaffolding of screen elements. When you use an Aggregate to build your list of data, OutSystems knows what attributes are being used on the destination screen and optimises the query to select only the data required. When building screens, you can quickly scaffold list and detail screens by dragging entities onto a UI flow. It’s super convenient and time-saving — but it also negates all our efforts to restrict access to sensitive information.
It’s always the trade-off between convenience and data security — and for Personally Identifiable Information, there should be no trade-off. There is too much at risk for users and businesses if that information is inadvertently exposed.
Beyond the standard CRUD actions, businesses usually need to query large sets of data using Aggregates or Advanced SQL Queries. Because we no longer have direct access to the entities in other modules, we need to implement customised methods to provide this data to consumers. I will explore some options in a future article. However, this will likely require a little extra effort from the administration team during development cycles.
Who’s knocking on the Gate?
At this stage, we’ve built a very secure database module with very strict pathways to access the data. But there’s always going to be a question — who is accessing my data, and should they be able to?
As mentioned, we’ve created very narrow pathways to access to our data through our defined Service Actions. We can now ensure that only the appropriate users have access to view or update that data. In your data access wrappers, and presuming that permission roles are defined at this level and not the application level, you can add checks in your Service Action wrappers to check if there is a logged-in user, and if that user has the appropriate permissions to access the data.
Incorporating role authorisation checks into these service actions will help reinforce that access to data requires permissions at the source. Without such protections and publicly exposed entities, we’d be completely reliant on the consuming application to enforce our data access policies. It could be too late by this stage, so let’s ensure we protect it at the source.
Data protection isn’t always just about if someone should be allowed to access the data. It’s also knowing who accessed it and if they modified it. This is why incorporating an Audit system into your data wrappers is essential in your data protection plan. It allows us to record who accessed what information and what changes were made. As we continually see, some data leaks are from authorised users whose own systems have been breached.
So into our Service Actions, our only way into the data, we’ve also added an automatic audit process. By adding auditing in at the data layer, we’re ensuring that changes made are audited to match our compliance regulations and not relying on our developers to implement it in every application that might consume the data.
We have our Moat, Wall, Gate and Guard on duty, keeping track of who and what goes in and out.
What isn’t being protected?
In any security discussion, it comes down to layers of protection. This article is specifically focused on how to protect our data from unintentional exposure through inexperienced or inattentive developers. What I’m not addressing here are things outside of the OutSystems walled garden.
Encryption of data, both at rest and in-flight should be considered in every situation. Protecting data access through the use of the described methods is of little value if the underlying database is vulnerable. Consideration of encrypting the database at rest would assist here, but there’s also unnecessary overhead in encrypting the entire database.
If we have specific bits of data where we want heightened protection, we could consider encrypting just those values within the database. If the underlying database were to be breached, this PII would be useless without the corresponding encryption key to decrypt it.
A key outcome is that we would no longer need to isolate the PII entities, as the data stored would be unintelligible and useless. We could then focus our entire energies on securing just the storage and retrieval of the encryption key. I would point out that whilst this might be a more efficient isolation of data, you would lose the advantage of incorporating audit and access controls at the data level.
Whatever the outcome for your particular use case, it’s important to place the security of your user’s personally identifiable information at the heart of your strategies. Next, layer on top access controls and auditing that ensure you meet your compliance and regulatory requirements. And finally, always keep in mind that shortcuts can be made in the name of expediency but may weaken your defences and expose your data.
Stay tuned for future articles in this series about data security.
About the author
Mike Samuel is a Senior OutSystems Developer at PhoenixDX and has been working in the IT industry for over 25 years. He has worked with OutSystems for 5 years as a developer and tech lead.