Security design as the dual of software design
Security-related design isn't mysteriously difficult. It's primarily explained by one simple principle: in non-security software, it's usually sufficient that there is some single successful path through the system - in security software, it's not enough that the good path does work, but all of the other paths must not work. This leads to the horrible realization that the main thing about security awareness is that it involves a lot more work.
Think Like an Attacker?
"Red Team" and "Think Like An Attacker" sound like a lot of fun, but they're generally not helpful for improving overall product security. The attacker only needs to break one thing to win - and sure, fixing that one thing in your product is important - but as a defender you need to keep all interfaces clean and correct. There's certainly value in understanding the kind of "tricks" attackers use, but this ends up being more about pointing out areas where you need to pay attention and not take shortcuts. It's also about developing a sense of dismay about how easy many attacks are, once they're noticed - and 30 years later when the same kind of exploits still work.
Attack Surface
As part of Microsoft "turning things around" in terms of Windows Security they formalized the idea that a system had an "attack surface" - the area that was exposed to outside attackers - which you could consciously minimize. This means obvious things like closing unneeded ports, design choices like having distributed systems contact servers and not the other way around, and higher level ideas like reducing or isolating third party dependencies because they're part of your attack surface.
As mentioned above, if you need to keep all interfaces clean and correct - this is more tractable if there are fewer and smaller/simpler interfaces in the first place.
Privilege Boundaries
The biggest place where security becomes a concern is any place someone can get more access. While there are theoretical reasons to focus on these areas in both design and review, the practical reason is more important: that's where the attackers are going to focus.
In the simplest case, being able to get from off-box to on-box is an increase in access. Likewise, getting from an isolated sandbox user to some other user, or from a non-root user to root. There are more complicated paths for some of these - file creation attacks, symlink race conditions - but the root of the problem is fundamentally an unintended increase in access,
Correctness and Security
There are some common areas where one way of writing a piece of code is correct, and another is wrong, and the wrong version happens to also have a security problem. One popular example is the SQL Injection attack, often framed as a "quoting" problem, but fundamentally is about treating user input as anything other than untrustworthy data that needs to be handled with gloves. Using naïve string interpolation on queries is wrong from a data type perspective, and the roots of why it's done at all go back to at least the 1980s when the only interface to the database involved passing strings around rather than a typed interface; this kind of interface persists because it's simple, and the bugs persist because it's slightly difficult to automatically detect.1
The "S" in IoT
For ages the "Internet of Things" has been derided as a security problem - calling it The Internet of "Things that should not be on the internet", or the catchphrase "The "S" in IoT is for Security" - which goes usually comes down to
- outdated beliefs about how much hardware you need to run secure software, once you have enough to be on a network at all, or
- poor planning about the inevitability of firmware updates.
(Let's assume that the case of "the 15 year old firmware that you're copying from a discontinued WiFi router to make an Internet dishwasher" - while definitely part of the problem - doesn't suggest that you're actually interested in making anything better.)
Modern (2025) embedded hardware has support for modern cryptographic security interfaces, even at the cheap end of the scale. For example, the ESP32 supports
- WPA/WPA2/WPA3 and WAPI (for wifi)
- Acceleration for AES, SHA-2, RSA, and ECC
- Secure Boot
- Flash Encryption
and the ESP32 version of CircuitPython supports SSL and HTTPS directly. So while you'll need a process to update your embedded hardware to respond to security issues that appear over time, the tools are there, the hardware and operating systems are not letting you down today.
History
CPU performance has traditionally tracked Moore's Law (in terms of marketing and physics) with a doubling of transistor density every year since 1965. This has a bunch of implications:
- performance-based security (password hashing for exmple) does need to adapt over time (fortunately, modern mechanisms simply have dynamically adjustable tuning parameters)
- encryption that needs fast hardware this year will run on literal toys in five years.
In the 1980s, "encrypting everything over the wire" was seen as ludicriously paranoid. By the mid-1990s, a desktop computer could keep up with DES encryption of an entire 10 megabit network connection (and CPU has only increased faster than network bandwidth - coupled with stronger encryption algorithms that are more CPU-efficient than ever.) Fortunately, by 2010 you could "obviously" use no less than SSH and HTTPS2 for everything over the wire, and the balance had shifted from "that's too paranoid" to "that's minimum basic responsibility".
-
The unbounded-string input interface (
gets) that was a key part of the Morris Worm was eventually suppressed by various techniques including developing a new mechanism to emit link-time warnings, before it was finally removed by ISO C11 a quarter-century later. SQL injection is harder to detect at the language level because "interpolating user data into the string" happens far away from "passing the string to something that could execute it"; perl's "tainted data" mechanism was one way of treating this but it didn't really catch on elsewhere. ↩ -
HTTPS Certificates were a big political roadblock here, but LetsEncrypt solved this a decade ago. ↩