Brief Intro to Security-First Development
Oct 11, 2020  •  5 min read

In fast paced software development, we are often given design choices that tempt us to write insecure shortcuts to our desired functionality. I'd like to take you through some simple ways we might keep security on our minds and mitigate the danger of hastily written code.


Table of Contents


Rules of thumb

In javascript land, some good rules of thumb to help you avoid consuming large amounts of memory and resources could include some of the following.

Use a proxy

As we'll mention later, use a reverse proxy (like Nginx) and put a cap on the maximum size of a request that can be forwarded to various parts of your app.

Don't give something for nothing

For unauthenticated APIs, don't return a lot more data than you receive, this can be abused by faking an IP address, and thus get your server to amplify someone's DDOS attack on someone else's servers.

Separate by request size

Similarly, separate the parts of your app that do need to allow large chunks of data in the requests and limit large or resource-intensive requests as much as possible to users that are authenticated or whom have proven some other sort of trustworthiness.

Careful what you duplicate

Avoid duplicating data: things like JSON.parse(JSON.stringify(value)) deeply copy entire objects, and could result in high memory usage if you blindly use it on the data of a request. Instead, as much as you can, work with references to the data you already have; in javascript for example, assigning one variable containing a primitive value to another, copies it; so if x = {y:1} and you need to pass y to a function, if you pass fn(x.y) then 1 will be duplicated for fn; if you can, write fn to take the object and reference its y value directly. (this makes more sense for larger string values and such)

Concepts

Buffers

While things like buffer overflows don't pop up as much in battle-tested, high-level languages like Javascript anymore, buffers created as a result of calling simpler abstracted functions for processing requests can have an impact on both security and performance.

For example, if you don't use a reverse proxy to limit the size of the request that the computer will accept, or you have a Server Side Rendered (SSR) application that continues to tally or record information about the user, your computer will have to allocate as much memory/space as possible for each request or user since it doesn't know ahead of time how big a buffer to create; it makes it much easier/faster to grow the size of your alloted memory/space until you run out and your app crashes or errors out and has to completely reload itself to fix the issue. While the performance implication (and UX failure for an SSR app) may be obvious, remembering that performance and security issues are often inseparable is helpful for catching smaller bits of logic in your app where a single request can either pass a large amount of data without restriction, or where your app may unnecessarily produce a lot of data from a small request, consuming memory/space that could be serving other users and giving malicious persons an easy way to DOS your app.

Basically this boils down to paying attention to the space and resources something takes up, for both the sake of performance and security.

Validations/Typing

Especially with the popularity of TypeScript, other statically typed languages, extensive unit / E2E testing, and schema validation libraries it's never been easier to apply strict rules for the properties you send and receive. I used to write small apis where the extent of the checking was if(!request.value) throw Error('missing value');, take it from me, it's a lot easier to start right off the bat with a validation library than to shoehorn proper validation into your api later.

Even using Typescript or a statically typed language, it's important to validate the initial inputs, and using something like a validation library means you can get predictable and consistent outputs, the most critical part of this is that you can ensure that things like errors only get logged or returned to what they're supposed to. It's a common vulnerability for things like your application data that you're comparing against to accidentally be returned to the client, or for Personally Identifiable Information (PII) or app secrets to be accidentally logged to insecure logging targets where the data could be compromised; many times these vulnerabilities come from previously unknown and unexpected errors that erupt with properties that your own validation or logger isn't expecting.

Another benefit of the consistent outputs that come with using a validation library, is it's much easier and safer to send responses with helpful errors without revealing too much about the internal state of your application; which will make your front end developers very happy.

Additional resources