Designing a functional and secure finance web app

Published July 30, 2022

A few months ago, I made tobcalc, a single-page web app that helps investors with filing and calculating the stock market transaction tax in Belgium. The number one priority while designing the app was security as the app deals with sensitive personal and financial information such as Belgian citizens’ national registration number and transaction history from brokers. Security breaches and bugs in programs are always bad but they are especially bad if that program is dealing with such sensitive data. In this post, I’ll be explaining how I designed the app to be functional as well as secure.

Do as much as possible on the client instead of the server

The app I made needs to generate PDFs for tax declaration purposes. PDF generation and file manipulation is something that is generally done on a server. The problem in my case would be that the generated PDF contains the user’s national registration number. This means that the server would have to process that number and that it might end up being logged somewhere. Even if you configure the server perfectly so the number would only be in memory while the PDF is being generated, how do prove that to the user and get them to trust you?

Fortunately, the web is a very capable platform nowadays with the majority of users using new browsers that support modern APIs. PDF generation is entirely possible in the browser meaning no information has to be sent to a server. And APIs such as Web Workers make it possible to offload the computation to a different thread that isn’t handling the HTML or user interface, meaning the user experience should remain the same as with a server-side computation.

Keeping everything to a minimum

Generally speaking, the more code you write and use, the more bugs and security issues you’ll have. This is especially true for developers that work in the JavaScript ecosystem where it has become the norm for projects to install hundreds of packages for a project.

At the time of writing, one of the most used frameworks for writing web apps is React. Create React App is a popular boilerplate package the React documentation recommends if you’re creating a single-page web app. I followed their exact instructions to setup Create React App and it installed a whopping 1393 packages. The infamous node_modules directory for this project is over 200MB.

Note that if I say 100 packages are installed, it could be that the project itself only requires one package such as React and in turn, React has 99 dependencies that are now also required for the project.

Create React App results

It’s difficult to quantify the impact on security of this practice. By simply following the offiicial documentation, npm claims there’s already 6 “high security vulnerabilites” in the installed packages. Despite all of these packages being open-source, malicious code can still seep through the cracks. Supply chain attacks are one of the biggest threats when you are using so much third-party code. For example, instead of an attacker trying to steal their credentials, they can indirectly attack your project by trying to get malicious code in one of the 1393 Create React App packages that your project uses.

Striking the right balance between writing things yourself but possibly making a more secure app or using someone else’s code but accepting the risks that come with that is difficult. For tobcalc I used Svelte, a web framework that handles things like state management. If I had to implement state management myself from scratch, the project would’ve taken a lot longer and I probably wouldn’t have finished it. In my judgement, it made sense for me to use a framework. However, the official Svelte template I used installs a total of 133 packages, most of which are used for things such as module bundling or transpiling. A potential threat would be some package in the module bundling process inserting malicious code into the bundle that every visitor of the site will run. If this were to happen, there are still ways you can protect the user which I’ll discuss in the next section.

Thinking of possible attack vectors

As I mentioned in the previous section, supply chain attacks are a large threat to web apps. The code for modern web apps passes through numerous compilers, transpilers, plugins and module bundlers before it ultimately gets incorporated into a JavaScript file that your users’ browsers will execute. You should assume that in some point in time, some malicious code will get deployed onto your website.

It’s likely that you also use some kind of analytics or newsletter service that requires embedding their script into your site. If it’s a small script that’s open-source and auditable, then you could probably check the code yourself and them embed it. However, it’s possible that their scripts might change in the future and include code you aren’t happy with or that their script gets hacked.

Knowing every single kind of possible attack that could happen is practically impossible. The more you learn about browsers, HTTP and web development in general, the more attack vectors you’ll become aware of. A good place to start is this short list of some security related HTTP headers on MDN and OWASP’s top ten security risks.

For example, one of the items on MDN’s list is a Content Security Policy (CSP). This ensures the user’s browser can only make requests to domains approved by the server. In my case the tobcalc app only needs to make requests to the tobcalc.com domain. By restricing all requests to the same origin, it becomes impossible for a hacker to sent data outside of that origin limiting the amount of damage a hacker can do even if they manage to get their code into the website. Subresource Identify is another useful feature that will stop third-party scripts from executing if their content changes from the version or hash you specified.

Unit tests

I already touched on why writing unit tests were useful for structuring the codebase in the first section but the main benefit of unit tests is ensuring your code is functioning as intended. This is likely more important to a finance application than something like a game or landing page. For example, a number of rules have been coded into tobcalc to calculate the tax rate for different types of securities. The unit test checks if the determined tax rates for different types of securities matches the real tax rates of those securities.

Certain funds such as IWDA and VWCE are extremely popular within the Belgian passive investing community, so there are extra tests to make sure the tax rates of those funds are also correct.

Deno command line interface testing funds

A clean codebase inspires confidence and trust

It goes without saying that you should understand the code you’re writing and the technologies you’re working with. A great way to convince your users you’re a competent developer is by writing readable, high-quality code. What personally helped me do so was writing as much code as possible that could be unit tested. This meant avoiding large functions that contained other “units” of code that should really be tested independently. My code had to be structured in a readable and organised manner which was not only easier to test but also easier to understand and audit for someone that is going over the codebase for their first time.

Test files

Writing comments is also a good way to ensure your code is understood correctly. However, writing useful comments is easier said than done. More often that not, a comment should be used to explain why a piece of code is necessary. What your code is doing can usually be understood by looking at the functions that are being used. If have no choice and need to resort to code that performs some form of black magic, then comments and adequate documentation become even more important and documenting what your code will most likely be useful to other developers.

Subscribe to receive an email when I publish an article