PAGNI's seem to fit well with secure coding development
So the longer you watch the back and forth between application development patterns and anti-patterns the more you'll notice a back and forth undulating of what is good and what is bad. Often it seems a given pattern or anti-pattern achieves acceptance for reasons that may not be reflective of any scientific proof, but rather general trends in anecdotal experiences. Such that a good pattern of yore becomes and anti-pattern of today, and likely will be reborn as a good pattern in the future, and so forth. This type of instability of principles in software development reminds me of philosophy and metaphysics more than any scientific field of study, but I think we can still derive value from gesticulating wildly about the problem space.
So in the spirit of furthering the craft (ie, unscientific/artistic side of software development) let's explore some interesting concepts around designing/developing things early that you may not need right away but are most likely a good idea. This is in contradiction to the KISS pattern (K.eep I.t S.imple S.tupid) and YAGNI (Y.ou A.ren't G.onna N.eed I.t), which is a set of patterns that certainly helps build software faster, but does indeed lead to software that is more difficult to work with in the future. I like the term PAGNIs coined by Simon Willison in his blog post reponse to Luke Plant's observation.
In my experience the idea that there are some things you should always include in application development, even if the requirements don't explicitly state it, is actual pretty core to concepts around secure coding. Such as user controlled input validation, seems pretty reasonable right? But honestly it's an often critically missing element to a secure application that I have actually heard KISS being stated repeatedly as the reason for not doing it. I mean, technically the application works without it, right? So you don't need it, right? Well yes. But if the application gets hacked and crypto-ransomed because of the remote code execution weakness validation would have prevented technically the application was broken because of missing validation right?
Yeah.
So before I jump into what I think are security PAGNIs, let's sum up Luke and Simon's ideas around the subject (my summary in italics):
Luke
- Zero, One, Many
- Always plan for multiples of unique data objects early if it is at all likely to be a future requirement.
- Versioning
- For all data schemas, API's, file formats, protocols, structured data types.
- Logging
- I feel this shouldn't need to be said, but here we are.
- Timestamps
- Mentions
created_at
, butmodified_at
is also useful.
- Mentions
- Relational Data Stores
- Document stores and key=value stores have their limits.
Simon
- Mobile App Kill Switch
- I would go farther and set all features as something that can be configured without a code change
- Automated Deploys
- I feel this shouldn't need to be said, but here we are.
- Continuous Integration
- I feel this shouldn't need to be said, but here we are.
- API Pagination
- Pagination is vastly harder to implement later so always implement it up front.
- Detailed API Logs
- Yes, but I have some caveats to talk about later.
So I would have to agree with every point made by Luke and Simon. Some things are just easier to bake in up front even if it makes it "not simple" or we "don't need it(yet)". But I'm going to consolidate the list a bit and then extend it with some security specifics that should always be implicit requirements (that is if you care about your apps security at all).
Consolidated
- Zero, One, Many
- Versioning
- Logging with Timestamps
- Relational Data Stores
- Feature Control in Configuration
- CI/CD
- API Pagination
I really would have liked to get the list smaller, as simpler is better in this case, but this seems to be pretty much it. So seven things you should consider building in from the beginning on anything but the simplest of applications.
Ok. So here's why I think some of these are very important for security. It's a smaller list, but that's ok as I'll add a couple for security in a bit:
Security Considerations of the Consolidated List
- Versioning,
- In this case I'm in favor of GitOps style where every change must go through version control. The API's, data and file schemas originally mentioned are just part of what should be controlled with the version as externally exposed artifacts.
- So mandatory versioning is important for several security reasons. If every change is forced to go through a cryptographically backed version control system you have good non-reputability, which is a security requirement that every change can be attributed to a person (hopefully an authorized person).
- Versioning also gives you the potential to roll back a change or roll out just a canary version of the change more reliably. These lead to better availability, or rather business continuity, which is fundamentally what security is trying to achieve.
- Logging with Timestamps
- So this is actually a big one, insufficient logging seems to stay on the OWASP top 10 pretty perpetually. Writing an application without good logging is like writing an application without any comments and single letter variables and function/class names. Learn to use logging libraries or built ins, and implement them early with good timestamps. Logs are important for when things go wrong both in regular app issues and critically in security incidents and events. It's also far easier to write a little logging up front for every new piece of code instead of having to go back and add logging in one large block of work.
- When working with data stores it's also important that the timestamps for log messages match any timestamps for data events. While analysis can usually make the leap over timestamp disparities, having them match up is much more desirable when figuring out what went wrong.
- It is possible to overdo logging and end up exposing sensitive information. So it's important to think of logging like error messages, know the audience is not necessarily you, the developer, and take care to provide only pertinent information and omit or mask sensitive information. You can't just dump a whole complex data object in your logger and call it a day, you actually have to take a little time to just add what might be needed if things go wrong or a sensitive operation is being initiated that could potentially be abused. It takes a little more work up front, but this is just a feature of quality coding.
- Feature Control in Configuration
- The idea of a "kill switch" is really just scratching the surface of good modular application design. Discrete features should be able to be enabled and disabled without touching the code, preferably still in GitOps control but separate from the application code base. This allows for rapidly stopping the bleeding if any given feature is found to have an actively exploited security vulnerability in the future. This is invaluable to security response in the operation of your application. And in a best case scenario it can allow your application to continue to support the business in a reduced manner but not completely shut down while the dev team has the time to apply a security fix. It can even help avoid hasty patches that actually make things worse by reducing the priority of any fix and only enabling the feature after the fix is in place and fully tested.
- CI/CD
- So we're getting to the point where every app should have a CI pipeline, it's just a better way of building apps. And security wise it is the most desirable mechanism for baking in application and infrastructure security scanning tools.
- CD is a little less a requirement, but the automated nature of CD should be pretty much required at this point. Your CD tooling should in theory be able to deploy as fast as your CI pipeline runs, even if you don't use it that way and instead require a manual triggering. Continuous delivery tooling provides consistency of production execution and a good hooking point for security and operations to trigger post deployment checks.
Some additional AppSec PAGNIs
- Standardized Input Validation Libraries and Use Them Early
- This can be as simple as making sure to use a given library for all explicit user inputs. Or just agreeing on what your base text input whitelist looks like, such as
^[a-zA-Z0-9_- ]{1,50}$
as your base validation regex. But in any case a developer or team of developers should agree to have strict text input validation everywhere. Yes, it's a little bit more work, but it's so much more secure than hoping your variable typing or built in validators are going to catch malicious escape strings. - Whitelist valid inputs, exceptions or leniencies in validation patterns should be limited and carefully applied. Documenting each use case is just good planning for when the security response team comes over to your desk/zoom asking why the characters
() { :; };
where allowed in a API endpoint (that's the Shellshock escape string if you were wondering).
- This can be as simple as making sure to use a given library for all explicit user inputs. Or just agreeing on what your base text input whitelist looks like, such as
- Manage Your Secrets
- Again this can range from using one of the
dotenv
implementations right on up to using Hashicorp Vault or one of the big cloud providers secrets managers. It's a little more work up front but will vastly improve the security of your application by lessening the chances of a sensitive piece of information ending up hardcoded and in the wrong place in front of the wrong eyes.
- Again this can range from using one of the
So just to be clear, those last two are not even close to everything you could be including early to improve the security of an application. But they have some of the greatest impact to include early in development. Input validation in particular can help prevent wide swaths of specific security problems like SQL injection, remote code execution, malicious filenames, XSS, SSRF and many more.
In general I like the idea of PAGNIs and I think it can be extended to guide not just more mature software development but also more secure software development. I'm thinking maybe there should be a fairly large list of potential PAGNIs, but on project start you should pick a small subset of that list to ensure your development is both secure and future proofed against expensive, tedious coding exercises when requirements change or expand. And of course thinking about security early can prevent painful rewrites and security remediation tasks in the future.