Security in software products is an emergent property dictated by the cohesion of multiple factors throughout the development process, from its very conception to the product's end of life. When we talk about evaluating the security of computer programs, we refer to a set of activities throughout the development cycle that begins with the system's idealization and extends over its design, coding, and hardening.
We must not fall into the error of confusing system security with its features, such as the use of certain protocols like SSL. Nor should we confuse it with the security components embedded in the system architecture, such as the presence of firewalls, or limit it to compliance with a particular regulation or certification.
Product security is a dynamic property that varies over time and becomes critical when considering the role these applications play in modern society. It arises from recognizing that attackers exist and are always willing to test every potential entry path to gain control of the system, thus forcing software development companies to devise control mechanisms to withstand these attacks.
It is estimated that currently, 50% of system vulnerabilities originate from design flaws. These differ from implementation flaws—commonly known as bugs, arising during coding or testing—because they emerge early in the development process and have such a profound impact on the system that they demand its reengineering. This is why it is crucial to deploy the necessary resources to identify and fix these design flaws early, in order to reduce the cost they incur by becoming ingrained in the product being created.
How do we guide secure development?
To help assess the maturity of security in the software development process, the speaker presents a list of common issues when designing applications that could affect the security of the final product. Let's look at these tips for secure design.
1. No component is trustworthy until proven otherwise
A common mistake in software development is to embed sensitive functionality in an execution environment over which we have no control. It is not appropriate to assume system components are trustworthy until this can be demonstrated.
For example, if we have a client-server environment, precautions must be taken against potential tampered clients by deploying verification mechanisms. We must consider that the client is in the user's domain, who may not always have the best intentions.
2. Outline authentication mechanisms that are difficult to bypass
Authentication is the process that allows us to verify the user's identity and assign them a unique identifier. Developing centralized authentication methods that cover every possible entry path is one of the pillars of building secure applications.
If dealing with web pages, we must consider which sites will require handling authenticated users and ensure that unauthorized third parties do not intrude into the system via unprotected URLs. Using multi-factor authentication will allow us to strengthen the system by verifying not only what the user knows but also, for example, what they possess.
3. Authorize, in addition to authenticating
Authorization is the process that determines whether an authenticated user can perform an action that changes the system state. Authorization processes for authenticated users must be considered from the design stage and help prevent sessions that have fallen into the wrong hands.
4. Separate data from control instructions
This point is key when working with code that can modify itself, or languages that compile such code at runtime—such as JavaScript—where the same instructions are received as data. Therefore, it becomes extremely important to sanitize the system's inputs to prevent attackers from manipulating the execution flow by entering malicious data.
5. Validate all data explicitly
System inputs should be evaluated with a whitelist philosophy over a blacklist: determine what will be allowed and deny everything that does not match. We must consider that an attacker interprets data as potential programming languages, with the intention of manipulating the system state. Therefore, it becomes necessary to inspect these input data, generating automatic procedures to bring them into well-known canonical forms.
Additionally, this input validation should occur close to the moment the data is actually used, since the gap between validation and use provides a window of opportunity for generating attacks.
To implement this, common components can be designed to centralize both syntactic (structural) and semantic (meaning) validations, and take advantage of the data types present in the programming language being used.
6. Use cryptography correctly
Understanding the cryptographic concepts applicable to the system under development is necessary to know which elements and which characteristics of them we seek to protect, against what forms of attack, and consequently, what is the best way to achieve this goal.
Creating one's own cryptographic solutions, as always, is a risky decision that can lead to a flawed system and is therefore strongly discouraged. Instead, proper advice should be sought to find libraries and tools that allow us to increase the cost of attack for the cybercriminal.
7. Identify sensitive data and how it should be managed
It is difficult to protect our information if we are not clear about what we really aim to safeguard. Defining the data whose protection is essential for system operation is critical, because from there we can begin to outline processes for designing security from the very start of the development cycle, rather than as an addition during implementation or deployment stages.
Defining anonymity requirements and the metadata handled will lead to decision-making regarding the paths that ensure their protection.
8. Always consider system users
A technically perfect system that does not meet user needs is a useless system. Usable security should be one of the goals to achieve when setting security objectives for the system. On one hand, it is not prudent to transfer security issues to the user that developers can resolve themselves, in order to avoid fatigue.
On the other hand, it is necessary to maintain communication with the user to provide a certain degree of transparency about how the system operates. The default configuration must always be the secure configuration.
9. Component integration changes the attack surface
Current applications are complex systems with many components interacting simultaneously. Every time a change is made to the system, the security landscape changes and must be reassessed. This reexamination results from coordination between areas and projects.
Components should be analyzed individually and collectively, considering how they are combined, maintained, or replaced.
10. Consider future changes in objects and actors
From the design stage, we must consider that system properties and its users change constantly. Some factors to consider include user population growth, how migrations affect the system, or how future vulnerabilities will impact components deployed at scale.
Secure update procedures should be designed with a future horizon of months, years, or even decades.




