Immutability and Security — From Java to Kotlin to Haskell
The state of an immutable object cannot be modified after its creation. If the object can be modified after its creation we call it a mutation of the object. In short the object is mutable.
Let’s use an example in Java.
We have list of allowedUsers
, a method to getAllowedUsers
and a method to check whether a username
canUserAccessResource
. We can think of it as a way to do some simple access control such as canUserAccessResource("Mallory")
, which would, and should, return false
. Mallory is not in the list of allowedUsers
.
The code above has a mutation related issue. Where exactly? We think about it for a moment.
Our list allowedUsers
can be modified to allow access to users which were not part of the list initially.
We create a test to make the issue visible and thus fixable.
Both tests are passing. Mallory, not in the allowedUsers
at the beginning, can access the resource after the mutation of the list. That’s bad.
Instead of learning how we can alleviate the above why don’t we use more modern languages which might bring us immutability out of the box?
Like Kotlin?:
We have no need for the getter anymore. Plus, the docs on listOf
state:
Returns a new read-only list of given elements.
Does this fix the security issue?
This time we use kotlinc REPL instead of a test
Attempts such as
authorizedUsers[2] = "Mallory"
or
authorizedUsers = listOf("Mallory")
will fail.
There is neither a set
method to provide access to the list
nor is there a way to reassign the authorizedUsers
because we are using val
instead of var
. Seems fine?
What we can do is cast it to MutableList
and then modify it
The actual object authorizedUsers
can be changed by another reference. Although the wording MutableList
might suggest using List
would be immutable it is not.
In defense of Kotlin: It’s stressed quite often how the wording read-only
is used instead of immutability
. Again, the docs of listOf
state
Returns a new read-only list of given elements.
Read-only, not immutable. Let’s leave the JVM and continue with our immutability journey in Haskell. Haskell is a pure functional language.
Using the stack Haskell REPL.
Data is immutable in Haskell. We do not mutate the existing list but build a new list from applying the function. Haskell helps us to avoid the security issue by default.
Why does all the above matter? It’s about predictability. Would a senior programmer recognise the issue in the Java example? Quite likely. Would a junior or a senior with sleep deprivation on a deadline? Maybe. Would we want to keep in mind how every other part of the codebase is able to modify the allowed users? Let’s guess the answer is no.
True. We could have fixed the issue with Java by simply copying the list in the getter. Now it would not matter whether someone attempts to mutate the returned copy. The initial allowedUsers
would not change.
Although creating the copy is something we would have to remember all the time. Mental overhead.
Would someone starting out with Kotlin realise the difference between read-only
and immutability
before stumbling upon it in the docs or when encountering a bug?
Seems like the simplest approach to getting the issues above out of the way is if our programming language is using immutability by default?