By default, JavaScript allows you to mutate global objects. We've seen how when you call lockdown()
from the ses library that it makes all of JavaScript's shared global objects immutable.
This is great, but the rest of our JavaScript code that we write can still be tampered with.
Say we have this counter object that holds some state and has methods for incrementing and decrementing that state.
1const makeCounter = () => {
2 let count = 0;
3 return {
4 count,
5 incr() {
6 this.count += 1
7 return this.count;
8 },
9 decr() {
10 this.count -= 1
11 return this.count;
12 },
13 };
14};
15
16const myCounter = makeCounter();
17myCounter.incr();
18myCounter.incr();
19myCounter.incr();
20myCounter.decr();
21console.log(myCounter); // { count: 2, incr: {}, decr: {} }
22
Someone can come in and mutate our object.
1const myCounter = makeCounter();
2myCounter.incr();
3myCounter.incr();
4myCounter.count = 'hehehehe';
5myCounter.incr();
6myCounter.decr();
7console.log(myCounter); // { count: NaN, incr: {}, decr: {} }
This completely breaks our program. The first thing we can do to prevent this type of tampering is to make count private
.
1const makeCounter = () => {
2 let count = 0;
3 return {
4 incr() {
5 this.count += 1
6 return this.count;
7 },
8 decr() {
9 this.count -= 1
10 return this.count;
11 },
12 };
13};
14
15const myCounter = makeCounter();
16myCounter.incr();
17myCounter.incr();
18myCounter.incr();
19myCounter.count = 'hehehehe';
20myCounter.decr();
21const lastValue = myCounter.incr();
22console.log({lastValue}); // { lasValue: 3 }
This prevents us from directly accessing count
and mutating it but we can still mutate our methods.
1const myCounter = makeCounter();
2myCounter.incr();
3myCounter.incr();
4myCounter.incr();
5myCounter.incr = () => {
6 console.log('I have hijacked your increment. There is nothing you can do.');
7};
8myCounter.decr();
9const lastValue = myCounter.incr();
10console.log({lastValue}); // { lasValue: undefined }
These methods need to be accessible to people because that's the API we want to expose so we can't just make them private.
We can use the harden
function from the ses library to make our methods tamper-proof.
1const makeCounter = () => {
2 let count = 0;
3 return harden({
4 incr() {
5 this.count += 1
6 return this.count;
7 },
8 decr() {
9 this.count -= 1
10 return this.count;
11 },
12 });
13};
This locks down the object and prevents anyone from mutating it. Now if you try to reassign incr
you'll get an error telling you TypeError: Cannot assign to read only property 'incr' of object '[object Object]'
.
Transcript
In this file we have the sess library being imported and What we're going to do here is just invoke the lockdown Function it gives us access to now by calling lockdown We are introducing a few different changes to our program
One of those is that we are making all of JavaScript's shared global objects Immutable so as a result these is frozen checks are going to return true for us And if we remove them, we'll see that they quickly revert back to being false
This is great as we've made our global objects immutable But let's see how we can continue and make the rest of our JavaScript code tamper-proof So here, I'm just creating a make counter function and then inside here I'll initialize a count variable to be zero
And then we're just going to return this Alongside methods for incrementing and decrementing And so our increment method is Is going to shockingly Increase the count by one each time. It's called while decrement is going to take one away from work
Now that we have that wired up. It's going to create this my counter variable using our make counter function and Then I'm going to Invoke the increment and decrement methods a few times and Lastly, we'll just log out my counter to see what we're working with
If we take a look in the console we see that count is two and Three minus one equals two so our math checks out there Everything is Going smoothly, but oh no Somewhere along in our program My counter was mutated
The count property is now this string of he he's and So this is less than ideal as we'll find our count variable is now Showing us not a number So what changes can we make to prevent this? so for one let's
Stop returning this count property altogether Let's make it private to our function Now instead of using the this keyword we can just operate directly on this count all right, so now I'm just going to create a last value variable and set it equal to
the result of my counter dot increment and now if we look in the console we see that we've successfully prevented our program From running into such issues as Last value is printing the correct value So we're moving in the right direction but let's see what happens if we
Try to actually mutate one of our methods and here. I'm just going to change our increment and For right now, we're just going to have it log out the statement I
Have hijacked your increment it. There's nothing you can do and I'm just going to place this above any other Increment thins to really screw things up here and if we look oh, I need to correctly mutate this and
Remove this parentheses here Now that that's fixed if we inspect our council Oh We see that this message is shown for us over and over and over again So not good as we still have some holes we need to plug and
This is where the sass library can help us once again Specifically we can turn to this harden function Which is going to be made globally available to us once lockdown is invoked Harden is going to take objects and work to them recursively
making each property tamper proof so here we can see that we're just using it to make our counters methods tamper proof and The result of this is that Our code now throws an error Saying cannot assign to read-only property Incra
so now let's remove this because we can no longer change the value of anything within our my counter object and Now if we go back, we'll see that our program is behaving correctly again