One way to think about risk is to decide if a feature is naturally risky, or if the risk is a result of the implementation of the feature. Stealing some terminology from the description of software complexity, I call these Inherent Risk and Accidental Risk.
Risk Creep
Sometimes unnecessary risk creeps into a system through implementation details: saving private information for troubleshooting purposes, saving financial information for convenience, accessing private data unnecessarily. Many of these kinds of risk come from wouldn’t it be interesting if we…
-type brainstorming. They sometimes uncover very important use cases that could mean the difference between a successful system and a flop. But, much of the time, the result is unacknowledged risk.
Risk of Implementations
Most of the time, people either don’t acknowledge the risk of these features, or don’t explore less risky implementions.
Many physical stores use a phone number or email address as a unique identifier for rewards programs. This has the added benefit of giving them a way to market to you. However, they are now responsible for keeping this information private and secure. It is probably not as risky as credit card information, but simpler personal information could still be damaging/embarrassing. In the past, these companies used loyalty cards, which had a unique id attached. By turning to personal information, each store is now at risk for personal information leakage.
The feature is the ability to recognize an individual customer. The implementation determines the level of risk of the feature.
Password Risk
Probably the classic example for accidental risk is storing passwords for login. Some systems take the simple approach of storing the username and password in a database. When the user logs in, the system compares the username and password with the entries in the database and quickly determine if the user is known. Most developers know by now that we should not be storing the passwords in plain-text in the database. If someone manages to get access to the database, they can impersonate anyone.
The key insight is that we need to verify that the user knows their password. We don’t actually need the plain-text password to do that. Hashing the password allows us to test the supplied password against the stored hash and identify if the user has access. This requires slightly more complex code, but we have reduced the accidental risk of the implementation.
Username Logging
One of my favorite examples of accidental risk comes from logging failed login attempts. You could do this one of three ways:
- Log the invalid username and password pair
- Log the username for the failed attempt
- Log the fact that there is a failed attempt
In the first case, the accidental risk comes from the fact that you might end up capturing a username and password that is wrong because of a simple typo. This means the log contains information that would greatly simplify an attack on the account.
The second case sounds like a good compromise except for the accidental risk of someone entering their password in the username field. This might happen from fumbling a tab (to go to the next field), or an overzealous enter after the username (which I’ve seen submit the login and then restore the form where you type the password into the first field). So, once again, the log can be used to simplify a login attack.
The final case stores only what we need to know, a failure occurred. This gives us useful information with minimal risk.