Significant security issues in widely used software are assigned a CVE (Common Vulnerabilities and Exposures) number. It makes it easier for organisations to communicate about issues and track vulnerabilities.
The two public CVEs that I discovered were:
A bug in Django (a popular python framework for creating websites) that meant you could trick its sanitizer into allowing unsafe URLS that would enable an attacker to perform cross-site scripting. Django fixed the issue within a week. Wordpress had a similar issue and it took them 6 months to fix.
A bug in safari on macOS that meant a specially crafted website could trick Safari into showing https://google.com in the address bar instead of the real domain – a phishers dream.
Our architecture makes some aspect of security easier and some aspects more challenging. We don’t need to worry as much about the security of our physical network as much as other banks – we treat our corporate network as hostile, being on the network alone doesn’t give you access to anything as everything is in the cloud behind several layers of security. Traditional banks have to keep all of their computers in tightly controlled physical environments.
(Almost) everything in our infrastructure runs on a single kubernetes cluster, in some respects this makes things easier to manage (you can apply security across the board rather than trying to secure n things independently). It also poses some challenges – you lose out on some of the security that you get for free by keeping things more isolated. In general though, the benefits outweigh the cons and I much prefer having the job of keeping our infrastructure safe than a traditional bank’s network.
A large part of my job is essentially reasoning about risk and economic theory. Consumers have a very different view of security – they tend to view it more emotionally, they have some rough sense of security from what they’ve seen that doesn’t always correlate with actual security.
For example, customers associate a feeling of security with their existing banks because they are subjected to many security checks. They don’t often consider the recovery flow – a password is only as secure as its recovery flow and it’s these recovery flows that are typically exploited by attackers.
Today by far the most likely way for money to fraudulently leave your account is for your card details to be compromised and used fraudulently. We will, of course, refund any card fraud that occurs on your account but consumers are used to carrying around a piece of plastic with what is essentially a key to your money embossed onto it.
So why hasn’t this improved? The answer is because banks aren’t generally liable for this fraud. There are huge misaligned incentives that means the situation continues to get worse. If an e-commerce payment doesn’t go through 3DS then the merchant is liable for fraud. If the transaction does go through 3DS then the bank is liable.
This should give merchants an incentive to apply 3DS to transactions. However, the 3DS flow of most banks is sufficiently bad that many merchants would lose more money to people dropping out of the checkout than they lose from fraud so they don’t use it. Banks have no incentive to improve the 3DS flow as doing so would just make them liable for more fraud.
The EU is attempting to sort this out with some legislation called Strong Customer Authentication. This will effectively require all e-commerce merchants to put every transaction through 3DS. This will level the playing field for all merchants and give the banking industry an incentive to improve the 3DS flow to properly balance between security and customer experience.
I’m genuinely interested to see what happens to e-commerce fraud rates over the next 5 years as a result of this legislation.
For the benefit of others that might not be super familiar with the difference between authentication and authorisation:
Authentication is essentially “who is making this request”
Authorisation is essentially “are they allowed to make this request”
We have multiple layers of authentication internally but the most relevant part is our internal authentication and authorisation services – these services power both the auth of our customers as well as the auth of our staff.
Our authorization system has the following concepts:
Roles are defined in code and map to a series of scopes. A user can perform an action if the have a role that gives them the desired scope.
There is also the concept of entitlements – this is essentially a filter that dictates what a client is allowed to do on behalf of a user. Entitlements are what prevents, for example, a third party application from moving money around even though you yourself have permission to do that.
We are quite fortunate in that by using Go we are less exposed to many vulnerabilities – Go code can have logical flaws that expose vulnerabilities but many classes of vulnerabilities are simply excluded by a language such as Go.
Within our monorepo each service has a CODEOWNER defined which requires that the owning team approve any changes to that service. Practically this means that any changes to any of the security services would have to be signed off by the security team. There is some automated checking that is done on our Javascript repositories and we have a new internal tool called svcchecker that runs static analysis on our codebase, but currently all of the checkers in this tool are focussed on catching non-security focussed bugs.
We also utilise defence in depth to mitigate the risk of security vulnerabilities.
For example, when an authenticated request comes into our platform it goes via an “edge” service – this service takes the access token from the request and converts it into an internal one. This means that whilst hundreds of services have access to internal access tokens a vulnerability that resulted in one of those services leaking the access token wouldn’t be fatal as these access tokens are useless outside our infrastructure.
The access tokens themselves have multiple layers of security – they have a digital signature but they also have a random value inside them that is verified on every request so access to the signing key is not sufficient to create a valid access token.
I wouldn’t say this was typical of all engineers at Monzo but I tend to be working in some capacity on many things at a time.
For example, over the last few weeks I’ve been involved in:
Joint accounts
Updating the core accounts service to support accounts with multiple owners
Updating dozens of services to migrate from the deprecated OwnerId field to the new AssignedPermissions field
Working on updating many financial crime systems to support joint accounts
Testing chargebacks with Mastercard
(semi) automating the processing of account data compromise alerts from Mastercard (Mastercard tell us if they believe some cards have been compromised due to a breach of a merchant)
Implemented a service for iterating over all users (this service can be used to backfill data into a new service – you just tell it which endpoint it needs to hit and it hits it for each user subject to a rate limit).
Updates to our card fraud systems (these updates have already declined more than £20k of fraudulent payments)
It’s where I have most experience and I generally prefer writing code that has no UI. Backend engineers do also contribute to our internal tooling so I do quite a bit of react work as well.
Is fraud on the current account at the same levels or lower/ higher levels than the pre paid having 600,000 + more customers since your last update.
So I’m going to break this down into different categories, I’ll naturally have to keep things quite high level but can share some info. I’d love to do another blog post with updated numbers but not sure when this will be.
Top up fraud (someone signs up for Monzo and then tops up with stolen debit card details)
This basically disappeared when we stopped acquiring new customers during migration and then flared back up when we opened the flood gates to new customers again. We implemented a brand new fraud engine to combat it that could process more data in real time and since then it’s back down to very low levels. I don’t have the numbers to hand but I think our rate is probably a bit lower than where it ended on that graph.
Money Muling
Very large increase in this compared with prepaid. The most common example is where a criminal organisation has convinced people to sign up to Monzo and then help them launder money in exchange for cash. This is really upsetting as the individual involved is often not the criminal mastermind and they are throwing their life away over a few hundred pounds. The fraudulent funds often come from customers at other banks that have been tricked into sending the payment / giving over their passwords. Sometimes people are being conned out of their life savings and its genuinely very upsetting to see.
This is where we’ve probably spent the most time – we’ve been working really hard to help detect this. It’s a challenge to trade off:
Potentially withholding funds from a genuine customer whilst waiting to confirm the funds are genuine
Being able to stop someone’s stolen life savings from leaving the account
I don’t have the figures to hand but we are preventing something like 70% of this fraud now and it’s so gratifying to be able to return the funds to the victim.
We have some genuine customers that feel let down because we withheld access to funds whilst verifying with the other bank. We also have customers of other banks that are angry that we couldn’t prevent the stolen funds from leaving the recipient account.
It’s genuinely a challenge to trade off these two things – someone who was been defrauded always thinks we should hold more funds and someone who has had their funds held thinks we shouldn’t.
Other Financial Crime
Seeing quite a bit more of this than prepaid – the addition of outbound FPS makes it more attractive to some people. We are very good at detecting it though and report money laundering to the NCA via a suspicious activity report.
Do you have any dev tools that you use everyday that you couldn’t live without?
What’s your favourite IDE or are you totally retro using vim?
I predominantly use Goland (although I still use Atom quite a bit).
What feature are you most looking forward to Monzo releasing this year?
Probably joint accounts.
I’m also really passionate about financial inclusion, we are already a lot more inclusive than many of the traditional banks but there is a lot more work to be done. There isn’t a concrete timeline for this work yet but we are starting to flesh out some details internally currently.
Get code reviews from any CODEOWNER on the PR and pass CI
Merge the PR
Build and deploy the PR
We have a tool called shipper that orchestrates the deployment of a PR. It works out the delta between what has been deployed and what you are about to deploy (so you can see if you are about to deploy changes from another PR that hasn’t been deployed yet).
We try and keep our changes as small as possible because the barrier to deployment is so low. Almost all changes can be rolled back and this carry fairly little risk.
Very occasionally there may be a change that cannot easily be rolled back once deployed and those changes have a lot more scrutiny applied to them with deployment plans etc.
how open can you be about your work internally? How do you balance the culture of openness at Monzo with “security”?
Pretty open – there are lots of things where we don’t talk about the specifics. For example details about our internal fraud controls are not known outside our team. Details of how our fraud engines work isn’t relevant for people outside our team but we share high level information.
how much contact do you have with other financial institutions? Do you see them as competitors or do you proactively work together to combat FinCime? Or does it depend on the organisation? (If you do all get together, I like to think that you all wear capes. Masks optional.)
We have a lot of contact, it’s in everyone’s interest to work together to combat financial crime.
In Monzo: the telenovela, what would your character’s dramatic story arc be? Who would you pick to play you?
Hmm, not sure on this one although I’d definitely be played Hugh Laurie.