How to avoid one of the biggest risks in mobile security as an iOS developer
What is the most significant security concern in your average mobile app?
What could cause GDPR violations, fines and damage company reputation?
Unintentionally leaking user data. More specifically, this article will focus on how to avoid unintentionally logging sensitive user data.
Sensitive user data could be anything from users’ addresses, credit card information to health info.
“But my logging system is only available internally”
Many studies show 20–80%+ of data breaches are internal (Sources 1, 2), so part of having a robust security strategy is making sure user data is not available to *anyone* it doesn’t need to be.
What can I do as a developer to avoid this and have impact across my codebase?
Design Smarter — Unified Logging system
Multiple logging frameworks makes it hard to manage sanitisation, processes and ensure every line of code meets the same standards. A unified logging system is ideal as it allows you to have a common approach for logging safely across the system.
Filter data in your logging system.
Using NSRegularExpression
, NSDataDetector
and blacklists you can filter out specific values you do not want to log. A breakdown of each method:
NSRegularExpression
— Use Regex to define specific patterns that can be used to filter logs and redact if any matches are found.
For example, you could create a Regex pattern to check for values that resemble emails and then redact these values before they get logged.
func redactAddresses(in text: String) -> String {
// Define a regex pattern for matching addresses
// This example pattern looks for a typical street address format (e.g., “123 Main St”)
let pattern = "\d{1,5}\s\w+(\s\w+)*\s(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Boulevard|Dr|Drive|Ln|Lane|Ct|Court|Pl|Place|Way|Terrace|Terr)\b"
// Create the regular expression object
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
print(“Invalid regex pattern”)
return text
}
// Define the range of the input text to be searched
let range = NSRange(text.startIndex..<text.endIndex, in: text)
// Perform the regex search and replace matches with “[REDACTED]”
let redactedText = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: “[REDACTED]”)
return redactedText
}
// Example usage
let inputText = "John lives at 123 Main St and his office is located at 456 Elm Ave."
let redactedText = redactAddresses(in: inputText)
print(redactedText)
// Output: “John lives at [REDACTED] and his office is located at [REDACTED].”
Pro: You can define intricate patterns to search for in your data.
Con: You have to consider and handle any localisation and formatting of the data yourself. This means if your data is in other languages or could have various formatting you must account for this in your Regex definitions.
NSDataDetector
— use it to filter common data types (phone numbers, urls, addresses). This approach requires less work than regex but can fail quite easily if the data doesn’t follow common structures.
func redactAddresses(in text: String) -> String {
// Create an NSDataDetector for addresses
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.address.rawValue) else {
print(“Failed to create NSDataDetector”)
return text
}
// Create a mutable copy of the input text to perform replacements
var redactedText = text
// Define the range of the input text to be searched
let range = NSRange(text.startIndex..<text.endIndex, in: text)
// Find matches and replace them with “[REDACTED]”
detector.enumerateMatches(in: text, options: [], range: range) { match, flags, stop in
if let matchRange = match?.range {
if let swiftRange = Range(matchRange, in: text) {
// Replace the matched address with “[REDACTED]”
redactedText = redactedText.replacingCharacters(in: swiftRange, with: "[REDACTED]")
}
}
}
return redactedText
}
// Example usage
let inputText = "John lives at 123 Main St and his office is located at 456 Elm Ave."
let redactedText = redactAddresses(in: inputText)
print(redactedText)
// Output: “John lives at [REDACTED] and his office is located at [REDACTED].”
You’ll notice you have the ability to choose from a predefined CheckingType
with NSDataDetector. Some of the types it can be useful at checking for:
Address
Links
Phone numbers
Pro: It handles localisation and formatting for the different data types it detects (nicer for international use)
Con: Less flexible and sometimes won’t pick up on patterns if it doesn’t follow a well-known structure (e.g it will not detect some addresses from some countries/areas that don’t follow standard address formats)
I would say this can be a good option to get you started if you are looking to filter the types listed above and under time constraints. Also, it could be used in conjunction with an NSRegularExpression
filter.
Blacklist
Blacklist certain values you want to specifically target. This is great if you have a particular value in mind you want to filter out. However, because it is so specific, it can mean variations of that value are not caught as they would be with NSRegularExpression
or NSDataDetector
.
The secret sauce to building a logging system that can adapt in real-time
How can your app handle a logging incident? Do you have to write an update to go out in the next release cycle, meaning you are potentially waiting weeks until it is effective?
Or do you have to disable an entire feature behind a feature flag to stop logging sensitive data?
Putting your filters behind feature flags so they can be modified in incident response is a powerful way to build an app that can be updated in real-time to fix data breaches. With the power of building feature flagging into your app, you can use this to your advantage for a more effective incident response process.
Anytime your logging system alerts you or another team that sensitive data is being logged, you can respond immediately by updating the regex or blacklist values your app uses to filter for a new type.
For example, if your app is found to be logging username and passwords, you could modify the feature flag to include a regex pattern for username and passwords:
let patternsString = """ ^[A-Z0–9._%+-]+@[A-Z0–9.-]+\\.[A-Z]{2,}$;^[A-Za-z0–9]{3,16}$;^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[@$!%*?&#])[A-Za-z\\d@$!%*?&#]{8,}$; """
// Split the string into an array of patterns
let patternsArray = patternsString.split(separator: “;”).map { String($0) }
(You can keep your pre-defined regex patterns separate but have a string feature flag value for values you want to append on the fly)
This means you now have an incident response process for all app users and everyone who works on your app to quickly rectify any logging issues. So the next time you (or a team member) is paged about a production incident in your free time, you can quickly sort it and get back to watching your TV show.
Develop smarter with property wrappers
One of the reasons sensitive data can often get logged is due to logging an entire value (like a User Struct
) without realizing you are logging all the properties from that Struct
, i.e. name, email address, etc.
A smart way to avoid this is a method raised here by Oleg Dreyman that uses property wrappers on any values linked to sensitive data. https://olegdreyman.medium.com/keep-private-information-out-of-your-logs-with-swift-bbd2fbcd9a40
To summarise, your property wrapper has overridden definitions of the values typically used:
@propertyWrapper
struct LoggingExcluded<Value>: CustomStringConvertible, CustomDebugStringConvertible, CustomLeafReflectable {
var wrappedValue: Value
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
var description: String {
return "— redacted — "
}
var debugDescription: String {
return " — redacted — "
}
var customMirror: Mirror {
return Mirror(reflecting: " — redacted — ")
}
}
So if you go to log a struct like User, that has its sensitive data properties marked with this property wrapper @LoggingExcluded
the User struct logged will show — redacted —
for the sensitive values. Checkout the article for more on this.
Enable Logging alerts in staging
Enabling alerts in your staging environment allows you to receive notifications when sensitive data is logged before it goes out into production. It can be easy to ignore staging alerts but treating them with care can help catch these issues before they impact production.
Personal recommendation
If there was one takeaway I had to advise you to implement in your application it would be putting your logging filter behind a feature flag so you can easily update it on the fly across all of your app users.
Implement these techniques to build a smarter, more secure system and have an impact across your codebase.
Want more security insights in mobile development?
I’m Alex, a software engineer and security champion. I write about mobile security and provide tips you can implement in your apps.
To stay up to date on future posts and short form content: