Secure JSON handling in Swift

Dave Poirier
Swift2Go
Published in
3 min readJan 15, 2022

--

When storing/reading data to/from disk, or when performing API calls, is your app’s only validation of data making sure the JSON can be decoded?

How are you receiving email addresses in your JSON? Using a simple String variable?

Chances are your current JSON is not as secure as it could be. Combined with other bugs in libraries and operating system, all kinds of undesired behaviors from crashes to compromised user devices could stem from your unsecure JSON handling.

Typical “Client” struct

Let’s imagine that we need to get a list of potential clients from an API containing their name, age and email address.

It would be typical to expect the following associated code:

Unfortunately this opens the door to all kinds of things to be received that wouldn’t make sense in our context. Does it make sense for the age of your client to be 150,000 years old? Yet your JSON allows it. Your email address could be set to “Hello” in the JSON and the decoder wouldn’t complain.

You may perform some validation of the Client data after it’s decoded. Then you would also have to remember re-validating when you read it back from CoreData later on, or your caching layer..

Instead, you could declare your Codable in such a way that validation is performed while decoding.

Age

Let’s begin by defining the minimum and maximum age for your clients.

  • Any “age” value higher than 150 is likely a mistake and shouldn’t be allowed.
  • Negative values are clearly out of the question (you could use UInt..)
  • You might even want to set a minimum value, let’s go with 1 for now

Above we created a struct that will contain the Int value for the age and we define the Errors that we will throw if the value doesn’t respect our criteria. Next we create a static function that will evaluate against our criteria.

The purpose of using a static function for the evaluation outside of the decoder, is that it could be re-used in other initializers or from anywhere in our app where we may need to evaluate if a value fits the criteria.

Finally for the Age struct, we define the initializer, that extracts the value, evaluates and stores it.

Name

Taking some time to reflect on client names, I’m not aware of anyone having a name longer than 100 characters… and surely no one having a name longer than 500. Surely no one has a @ symbol or a \ in their name.

  • Can contain alphabetical characters (from various alphabets and languages)
  • Can contain spaces but only between the names
  • Should contain at least 2 characters
  • Should be no longer than 100

We begin the same way by defining the criteria and possible evaluation errors. Followed by the static function to perform the evaluation and the initializer.

Note to the avid reader, Japanese digits and other symbols are still slipping through with the code above, you are expected to define your own rules based on your demographics and your acceptable level of risk.

Email

Emails are one of the hardest to validate by syntax alone, and this can clearly be seen by the sheer amount of RegEx that exists, all different from one another, to perform the same task.. all succeeding or failing to various degrees. Ref: https://stackoverflow.com/questions/156430/is-regular-expression-recognition-of-an-email-address-hard

While I wouldn’t advise to use the code below as the sole and unique way of validating email addresses, we can at least perform some minimum validation on our email strings.

  • Contains at least one @ symbol
  • Has at least 1 character before @ symbol
  • Has IP or domain after @ symbol

Secure “Client” struct

We can now go in our original Client struct and update the struct to use our new Codable values:

Conclusion

It is fairly easy to greatly improve our handling of JSON data by defining custom data types with their own evaluation rules and initializers.

Remember that validation rules need to be evaluated against:

  • Your business requirements
  • Intended demographics
  • RFC documents
  • Other libraries with which you interact (maximum variable length, CoreData SQL injections, …)
  • Other restrictions imposed by the database administrator, API, etc…

Finally, always define unit tests to validate that your code works as intended.

--

--

Dave Poirier
Swift2Go

Senior iOS Developer | Mobile Security And Reliability Specialist