What security testing should every app go through
A product that reaches users with a security flaw baked in does not get a quiet second chance. The flaw spreads, the press notices, the reviews reflect it, and the trust that took months to build is gone in days. Security testing is the work that prevents that, and it is a series of deliberate decisions made across the entire development process, from the first design conversation to the day the app goes live and every sprint after that.
Security decisions made at the architecture stage cost a fraction of the same decisions made after the product is built.
The trouble is that security is treated, on too many projects, as a layer applied at the end. A checklist run through before submission, a penetration test ordered when the budget allows, a set of rules added to a database after the architecture is already fixed. By the time those checks happen, changing anything costs real money and real time. The decisions that created the vulnerabilities were made weeks or months earlier, and unpicking them is expensive work.
This article covers what a thorough security testing process actually looks like, across every phase of development. It draws on specific projects we have run, including a fitness social app, a performance coaching survey tool, an anonymous messaging product, and a dating app where a single skipped discovery session cost £15,000 and two months of rebuild time. The principle running through all of them is the same: security is a design decision, and it belongs at the table from the start.
Threat modelling before a line of code is written
Threat modelling is the practice of sitting down before development begins and asking who might want to misuse this product, how they would do it, and what the consequences would be. It sounds straightforward, and it is, but most products skip it entirely and move straight to building features.
The output of a threat modelling session is a list of risks ranked by likelihood and impact. For a wellness app that stores health data, the risks look different than for a hospitality booking tool. The types of user, the sensitivity of the data they share, the third parties the app connects to, and the financial flows it handles all shape the threat profile. A product handling payment details needs different protections from one that stores only a user's workout log.
Good threat modelling also considers the users themselves, not just external attackers. A feature that seems safe against outside interference can still expose one user's data to another, or allow a determined user to manipulate outcomes in ways the product was not designed to handle. Both directions of risk belong in the model.
The practical output is a set of requirements that feed directly into the design and architecture decisions that follow. Which authentication method to use, whether data needs encrypting at rest, how to handle user-generated content, whether an API layer is needed: all of these are easier and cheaper to get right at this stage than to fix later.
Security requirements built into feature design
Every feature has a security dimension, and the right moment to address it is during the design of that feature, before it is scoped, estimated, or built. When security requirements are added after a feature is built, they tend to conflict with design decisions already made, and something has to give.
A practical approach is to treat security as a design constraint in the same way performance or accessibility is treated. Each new feature gets a set of questions answered before development starts. Who can access this? What data does it create or expose? What happens if a user tries to access it without the right permissions? What does abuse of this feature look like, and does the design prevent it?
On a dating app project, the client chose to skip the discovery phase for the messaging component and focus the budget solely on onboarding. The onboarding was rigorous: verified profiles, bot prevention, a careful authentication flow. But because no discovery work was done on messaging, a generic messaging system was built that allowed automated and fake messages, which directly contradicted everything the verified onboarding had been designed to protect. The mismatch meant the entire messaging section had to be rewritten, costing approximately £15,000 in additional budget and two months of extra work. The security requirement was not discovered late, it was simply never asked during feature design.
Start your app project the right way
We deliver the complete blueprint before a line of code is written. User research, psychology-driven design and full technical specifications. You choose who builds it.
How real-time location sharing exposed a safety flaw before it shipped
We worked on a social fitness app whose core feature was map-based location sharing. The idea was that users could see where other people were working out in real time, making it easy to find a partner for a run or a session. The feature made sense for the use case, and the design was solid on paper.
Research changed the picture. The user data showed a high proportion of female participants, which made precise real-time location sharing a genuine safety risk, not a theoretical one. Sharing your exact location with strangers in a social fitness context is a different proposition when that community skews female and the strangers have not been verified.
A feature that worked for one user profile created real danger for another, and research made that visible before launch.
The team redesigned the feature to introduce randomised location offsets. Users could see the approximate area where a potential workout partner was located, not their precise coordinates. From there, users could exchange messages and review each other's profiles before choosing to share their exact location, and that disclosure was opt-in. The core value of the feature was preserved: enough proximity data to judge whether a meetup was practical. The safety risk was removed.
This is threat modelling applied to a specific feature midway through a project. The security flaw was not in the code; it was in the feature premise. Catching it before it shipped required research, not just testing.
Authentication and access control testing
Authentication is the first line of defence, and it is where a lot of apps create unnecessary risk. The two most common approaches, email-and-password registration and social login, both carry costs that are worth examining before committing to either.
Email-and-password authentication requires users to create and remember credentials, which leads to weak passwords, reused passwords, and a constant volume of password reset requests. Social login solves the memory problem but shares user data with third-party platforms and creates a dependency on those platforms' continued availability.
We advise clients to consider a passive login approach instead: a user enters their phone number or email address and receives a one-time code. There are no passwords to remember, no reset flows to build, and no credential interception risk because nothing persistent is stored on the user's device. The data stays internal rather than passing through a third-party authentication service. According to Verizon's 2024 Data Breach Investigations Report, 76% of data breaches involved compromised credentials, which puts the case for removing stored credentials entirely into clear context.
Access control testing checks that the right users can access the right things, and nothing more. This includes testing that admin functions are genuinely inaccessible to standard users, that one user cannot read another's data by manipulating a URL or request, and that privilege levels are enforced server-side and not just hidden from the interface.
Securing data in transit and at rest
Data in transit is data moving between the app and its servers. Data at rest is data sitting in a database or on a device. Both need protection, and the methods are distinct.
For data in transit, the minimum requirement is HTTPS across every endpoint, with no exceptions for internal or lower-priority routes. Certificate pinning adds a further layer by ensuring the app only communicates with a known server certificate, preventing man-in-the-middle attacks where a third party intercepts traffic by presenting a plausible-looking certificate.
For data at rest, encryption protects stored data if a device is lost or a database is compromised. The question to work through during design is which data is sensitive enough to require encryption, because encrypting everything has a performance cost and encrypting nothing is indefensible for an app handling personal or financial information.
Run a data audit before deciding what to encrypt. Map every piece of data the app stores, where it lives, and who can read it. Then apply encryption to anything that would cause harm if exposed: health data, financial details, private messages, location history.
Testing for both requires checking real behaviour, not just configuration. A setting that appears to enable encryption needs verification that it is actually encrypting data in practice, and that decryption requires the expected key.
When there is no API layer: hardening direct database access
A traditional app architecture places an API between the client and the database. The API acts as a gatekeeper: it checks permissions, validates inputs, and controls exactly what data the client can read or write. Without that layer, the client communicates directly with the database, which shifts all of that control into the database's own security rules.
On the performance coaching survey app we built, the decision was made early to skip a traditional API for the MVP. It was a deliberate trade-off to move faster and reduce build cost. But it meant the Firebase security rules had to carry all the weight that an API would normally handle, and getting those rules right required very careful scrutiny.
Every read and write rule had to account for who was making the request, what they were allowed to access, and what they were allowed to do with it. There was no controlled intermediary to fall back on if a rule was wrong. A misconfigured rule in a traditional API might expose an endpoint; a misconfigured Firebase rule in a direct-access architecture can expose data directly.
The lesson from that project is not that skipping an API is always the wrong decision, but that the security implications of that choice need to be fully understood and accounted for before the architecture is set. The rules that protect the database need to be treated with the same rigour as API endpoint security.
Testing for fraudulent and duplicate input
Any system that accepts user input needs to consider what happens when that input is false, repeated, or automated. For an app handling surveys, reviews, votes, or registrations, fraudulent or duplicate submissions can corrupt the data the product relies on to function.
On the performance coaching survey app, we built a multi-layered approach to this problem without a traditional API to lean on. Only authenticated coach accounts could create surveys. Each QR code generated for a survey contained a unique key that granted read access to that specific survey only. When a respondent scanned the code and accessed the response URL, we used a cookie combined with device fingerprinting to restrict write access to one submission per device per survey. That prevented duplicate responses without requiring respondents to log in or create accounts.
The QR codes themselves were generated using iOS's built-in libraries, entirely within the presenter's native app. Nothing was stored externally. The code existed only while the survey was live, and once the presenter marked a session as finished, no further responses could be recorded. The survey was time-bounded by design, which removed the possibility of responses trickling in after the fact.
For any feature that accepts input tied to a specific event or session, build time-limiting logic into the architecture from the start. An open-ended submission window is a vulnerability as much as it is a UX problem.
This kind of layered approach is more reliable than any single mechanism because it requires an attacker to defeat multiple independent controls, not just one.
Penetration testing and what it actually covers
Penetration testing is the process of hiring someone to try to break into the product. A penetration tester approaches the app the way an attacker would: looking for inputs that can be manipulated, authentication flows that can be bypassed, data that can be read without proper authorisation, and logic that can be exploited.
It is a valuable process, but it is not a substitute for the earlier steps in this article. A penetration test finds vulnerabilities in what has already been built. It cannot fix architectural decisions that created those vulnerabilities, and it cannot recover budget already spent on building the wrong thing.
What a pentest covers
A penetration test typically covers authentication and session management, input validation across all user-facing fields, access control across different user roles, data exposure through API responses or error messages, and known vulnerability patterns like SQL injection or cross-site scripting. The scope is agreed in advance, and a responsible tester will provide a report that ranks findings by severity.
What a pentest does not cover
It does not cover product decisions: a feature that is working as designed but designed badly is not a finding in a penetration test. It also does not cover the dependencies the app relies on, unless those are explicitly in scope. A clean penetration test result means the tester did not find what they were looking for within the defined scope, not that the product is comprehensively secure. According to NowSecure's mobile app security research, 95% of tested mobile apps fail at least one OWASP MASVS security control, which gives some indication of how much is typically found when someone looks carefully.
Dependency and third-party library audits
Every app relies on code it did not write. Libraries for image handling, date formatting, authentication, analytics, and dozens of other functions are pulled in as dependencies, and each one carries its own security history. A vulnerability in a library the app uses is a vulnerability in the app, regardless of how carefully the app's own code was written.
The practical problem is that libraries are updated over time, but apps often are not. A library pinned to a version from eighteen months ago may have had several security patches released since then, none of which the app has received. According to Veracode's research on third-party libraries, only 52% of developers always consider security when evaluating a new library, compared with 67% who prioritise functionality. The same research found that 69% of vulnerabilities in those libraries involve only a minor patch that would rarely cause breakage, which means most of the update risk developers fear is smaller than it appears.
A dependency audit involves listing every library the app uses, checking the current version against known vulnerability databases, and determining whether updates are available and safe to apply. This is not a one-time exercise: new vulnerabilities are discovered in existing libraries regularly, and the audit needs to repeat on a schedule.
Third-party services, not just libraries, belong in this review. An analytics provider, a payment processor, or a customer messaging tool that has access to user data is part of the product's security surface, and their own security practices and incident history are relevant.
What anonymous and user-generated content features require
Anonymous features and user-generated content introduce a category of risk that is distinct from the technical vulnerabilities covered elsewhere. The risk is human behaviour: what users say to each other, what they post, and what happens when the product creates a space for harm.
On an anonymous messaging app we worked on, the safety implications became clearer as the project progressed and we examined what anonymous messaging actually enables. Anonymous communication can be useful, but it removes social accountability, and without design intervention it creates conditions for harassment and abuse. We added in-product reporting features that allowed users to flag messages for review by the client's admin team. Users could escalate concerns about inappropriate or bullying content directly through the app, without having to leave it or contact support through a separate channel.
That reporting feature was not in the original scope. It emerged from examining the product's behaviour honestly once it was partly built. The lesson is that any feature enabling anonymous interaction needs a safety review that asks what the worst-case use looks like and how the product responds to it.
- Define what content is and is not permitted, in writing, before launch.
- Build reporting and moderation tools that match the scale of content you expect.
- Test the reporting flow with real users to confirm it is accessible and not buried.
- Decide how the admin team will respond to reports and at what speed.
Technical debt as a security liability
Technical debt is the accumulated cost of shortcuts taken during development. Every decision to skip a proper fix in favour of a quick patch, every temporary solution that becomes permanent, and every area of code that no one has touched in a year because no one quite understands it: these are all debt. And on a security-sensitive product, debt is a direct liability.
We worked on a communications product built around trust and security as its core value proposition. Over time, technical debt accumulated across the codebase. The client declined, sprint after sprint, to allocate time to address it. The debt was not invisible: it was flagged, documented, and deprioritised in favour of new features. The product became increasingly fragile. Eventually the fragility reached a point where the product's core promise, that it was trustworthy and secure, could not be sustained. Only then was time allocated to rework the core flows and bring the product back to a state where it behaved consistently. The rework cost significantly more in time and budget than the incremental fixes would have.
The pattern is common. Security and stability work is easy to defer because the cost of deferring it is not immediately visible. But a product built on trust has no margin for fragility, and the longer debt accumulates, the more expensive the recovery becomes.
Allocate a fixed proportion of each sprint to debt reduction, rather than treating it as a separate project that competes with features for budget. Debt addressed continuously stays manageable. Debt addressed in a single large rework costs far more and disrupts the roadmap.
Testing coverage across platforms and devices
A security test run on one device, in one configuration, against one operating system version, is not comprehensive testing. Vulnerabilities can be platform-specific, version-specific, or emerge only under particular device conditions. An app that handles authentication differently on Android and iOS, or that stores data differently on older operating system versions, needs testing that reflects those differences.
Platform coverage means testing on both iOS and Android where the product supports both, including older OS versions that your user data shows are still in use. Restricting testing to the latest OS versions misses a portion of the real user population, and vulnerabilities that affect older versions can be just as exploitable as any others.
Device conditions also matter. An app tested only on fast, stable connections may behave differently on a slow or intermittent one. Timeout handling, retry logic, and session management can all produce unexpected behaviour when network conditions change mid-flow, and some of that behaviour creates security gaps.
The testing matrix does not need to cover every permutation, but it should cover the combinations your actual users represent. Pull the device and OS data from your analytics, identify the tail you cannot reasonably test against, and make a conscious decision about it rather than ignoring it by default.
What a pre-launch security review should confirm
A pre-launch security review is a structured check, not a hope that nothing was missed. It should confirm specific things about the product's current state, not in general terms but with evidence.
Technical checks
- All data in transit is encrypted and certificate pinning is in place where appropriate.
- Authentication flows have been tested for bypass and credential interception.
- Access control has been verified across every user role, not just the primary one.
- Fraudulent or duplicate input has been tested against every submission flow.
- Third-party dependencies have been audited and known vulnerabilities addressed.
- Database security rules have been reviewed if there is no API layer.
Product and process checks
Beyond the technical list, the review should confirm that user-generated or anonymous features have moderation and reporting tools in place, that the team knows how to respond to a security incident and has a process for doing so, and that security responsibilities post-launch are clearly assigned. A product that is secure at launch but has no process for maintaining that security will not stay secure.
The pre-launch review is also the point to confirm that technical debt in security-critical areas has been addressed or has a documented plan. Launching with known debt in the authentication or data storage layer is a decision that should be made consciously, not by oversight.
Conclusion
Security testing is a series of decisions made throughout the process: during threat modelling, during feature design, during architecture choices, and during every sprint that follows. The cost of getting each of those decisions right is low at the point they are made and rises steeply the later they are left.
The projects described in this article illustrate the range of ways security can be built in or missed. A fitness app that redesigned its core location feature after research revealed who was actually using it. A survey tool that built multi-layered fraud prevention without a traditional API to lean on. An anonymous messaging product that added reporting infrastructure once the safety implications of anonymity were properly examined. A dating app that spent £15,000 rebuilding a messaging system because discovery was skipped for one feature. A communications product that deferred technical debt until the product's core promise became unsustainable.
The common thread in the failures is that security was treated as something to address when there was time for it, rather than as a condition of building the right thing in the first place. The common thread in what worked is that the right questions were asked early, and the answers shaped the design.
If you are building a product and want to work through what security testing should look like at your current stage, let's talk about your product's security approach.
Frequently Asked Questions
Threat modelling is the process of identifying who might misuse a product, how they would do it, and what the consequences would be, before any code is written. It produces a ranked list of risks that feeds directly into design and architecture decisions. Getting this right at the start is far cheaper than fixing vulnerabilities once the product is built.
Security testing should begin at the very first design conversation, not at the end of development. Treating security as a final checklist means vulnerabilities are already baked into the architecture by the time they are spotted. The earlier security is considered, the less expensive it is to address.
When security is treated as a finishing layer, the decisions that created vulnerabilities have already been made, and unpicking them costs real time and money. The article gives the example of a dating app where skipping a discovery session led to £15,000 in costs and two months of rebuild work. It is a common and avoidable mistake.
Yes, the threat profile of an app depends on the type of data it handles, the users it serves, and the third parties it connects to. A wellness app storing health data faces different risks than a hospitality booking tool. Security requirements need to be tailored to the specific product rather than applied as a one-size-fits-all checklist.
Yes, threat modelling should consider the behaviour of actual users, not just outside attackers. A feature that is safe against external interference can still expose one user's data to another, or allow a determined user to manipulate outcomes in unintended ways. Both directions of risk need to be included in any thorough security assessment.
Security should be treated as a design constraint in the same way that performance or accessibility is, addressed before a feature is scoped, estimated, or built. Each new feature should have its security questions answered at the design stage. Adding security requirements after a feature is built tends to conflict with decisions already made, which forces compromises.
A penetration test alone is not sufficient, particularly if it is only ordered when the budget allows, after the architecture is already fixed. It is one useful tool, but it works best as part of a broader process that includes threat modelling, security requirements in feature design, and ongoing testing throughout development. Running it too late means any findings are costly to act on.
A security flaw that reaches users can destroy trust quickly, attracting negative press and poor reviews that undo months of work. Security decisions made at the architecture stage cost a fraction of the same decisions made after the product is built. Early investment in security testing is significantly cheaper than the reputational and financial damage of a public failure.