Security6 min read
Secure by design: building security into everyday delivery
Practical ways to build security into software delivery — threat modeling, secure defaults, pipeline checks and secrets handling — instead of bolting it on.
Verolution EngineeringPublished
In many delivery processes, security first appears in the final weeks of a project, when a penetration test or review produces a long list of findings and an uncomfortable conversation about the launch date. By then, the decisions that matter most have already been made.
Secure by design treats security as a property of how software is designed, built and shipped, not a phase at the end. That takes neither a large security team nor a heavy process, just a few consistent habits and a pipeline that does the repetitive checking.
Why security at the end costs more and covers less
A late review finds problems where they are hardest to fix. A missing authorization model, or a data flow that sends personal data where it should not go, is a design problem. Correcting it means reworking the features built on top of it, not changing a setting.
Late reviews are also incomplete. A time-boxed penetration test covers what testers can reach in the time available, at one moment in the system's life, and finds instances rather than causes: one injection flaw gets fixed while the habit that produced it remains. Under deadline pressure, the default outcome is often accepted risk. Independent testing still matters, but as verification of a system built securely, not as a substitute for building it that way.
Public guidance agrees. NIST's Secure Software Development Framework (opens in a new tab) (SSDF, SP 800-218) describes outcome-based practices across the lifecycle, grouped into preparing the organization, protecting the software, producing well-secured software and responding to vulnerabilities. CISA's Secure by Design initiative asks software makers to take ownership of their customers' security outcomes and ship products that are secure by default.
Threat model the flows that matter
Threat modeling is a structured conversation about what can go wrong, held before it does. It does not need a multi-day workshop: an hour-long session whenever a team designs or significantly changes a critical flow often surfaces design problems while they are cheap to fix. Start with flows an attacker would care about: sign-in and account recovery, payments, data exports, admin functions and third-party integrations.
- Sketch the flow: actors, components, data stores and the trust boundaries where data passes from a less trusted context to a more trusted one.
- Ask what can go wrong at each boundary. STRIDE, a technique that originated at Microsoft, is a useful prompt list: spoofing, tampering, repudiation, information disclosure, denial of service and elevation of privilege.
- Decide what to do about each credible threat: mitigate it, accept it with a named owner, or remove the feature that creates it. Record mitigations as backlog items or acceptance criteria so they are built and tested like any other requirement.
Keep the diagram and decisions next to the code, and revisit them when the flow changes.
Make the secure path the default
No review process can inspect every small security decision a developer makes, so make the easy path the safe one:
- Deny by default. Every endpoint requires authentication and an explicit authorization rule, enforced on the server in one central layer. New routes stay closed until someone deliberately opens them.
- Least privilege. Service accounts, database users, cloud roles and CI tokens get only the permissions they need, and administrative access is time-limited.
- Parameterized queries. Never build SQL by concatenating strings. Use parameters or a query builder, and flag raw query APIs in review.
- Output encoding. Encode data for the context it is rendered in, rely on your template engine's automatic escaping, and treat escape hatches such as React's
dangerouslySetInnerHTMLas code that needs a justification. - Framework defaults, left on. Keep CSRF protection, secure cookie attributes and security headers enabled. Turning one off should require a comment and a reviewer.
Build these into service templates and shared libraries so new services start secure. The OWASP Top 10 (opens in a new tab) is a widely recognized reference for the most critical web application security risks, and the OWASP ASVS (opens in a new tab) provides verifiable security requirements, in levels of increasing rigor, that help define what "secure enough" means for a given application.
Let the pipeline do the repetitive checking
Automate mechanical checks so people can focus on design and logic. A practical baseline for every repository:
- Dependency scanning on every build and on a schedule, because new advisories appear for code you have not touched. Automated update pull requests keep the backlog small, and a software bill of materials (SBOM) shows quickly whether a newly announced vulnerability affects you.
- Secret scanning before code leaves a laptop, in CI and, where your platform supports it, at push time. A secret that reaches a shared repository is compromised: rotate it first, then clean the history.
- Static analysis, where it earns its place. Start with a small set of high-confidence rules for the risky APIs in your stack, and tune out false positives. A noisy tool teaches people to ignore it.
- Infrastructure-as-code checks that flag publicly readable storage, overly broad network rules, missing encryption and wildcard permissions before anything is applied.
Block merges on high-confidence, high-severity issues the change introduces, and route the rest to the backlog, so the gate stays fast and trusted.
Treat identity and secrets as infrastructure
Use an established identity provider over standard protocols such as OpenID Connect or SAML rather than building authentication yourself, and require multi-factor authentication for administrative access, preferably phishing-resistant. Authorization remains your job: model it explicitly and write tests proving that one user cannot read or change another user's data. Automated scanners struggle with these flaws because they cannot know who is supposed to see what.
Secrets belong in a secrets manager, injected at runtime and never stored in source code, container images or CI logs. Give each service its own credentials, and prefer short-lived ones: many CI platforms and cloud providers support federated workload identity, so a pipeline can obtain temporary access instead of holding a permanent key. Plan rotation from the start, because a secret you cannot rotate without an outage is a liability.
Log for investigation, not exposure
When something goes wrong, logs are how you reconstruct what happened. Decide in advance which events matter: sign-ins and failures, credential and MFA changes, permission changes, denied access, administrative actions, data exports and configuration changes. Each record should capture who acted, what they did, to which resource, when, from where, the outcome, and a correlation ID linking it to the originating request.
Just as important is what never goes in: passwords, tokens, API keys, session identifiers, payment card data and personal data you do not need. Log explicit, structured fields rather than whole request objects, add redaction as a safety net, and test that it works. Restrict access to logs, protect them from tampering and set retention to match your legal obligations. The OWASP Logging Cheat Sheet (opens in a new tab) covers both sides in detail.
Treat findings as owned, prioritized work
A finding without an owner is a risk nobody is managing. Results from scanners, reviews, penetration tests and external reports belong in the same backlog as feature work, prioritized by risk in context: how exploitable the issue is, how exposed the component is and what sits behind it. A generic severity score is an input, not the answer.
Give each finding an owner and a target date matched to its severity, with status visible to engineering leadership. Accepting a risk is legitimate when it is explicit: who accepted it, why, and until when. When a finding reveals a pattern, fix the class of problem rather than the instance, with a lint rule, a safer library or a better default.
Where to start
- Run a short threat modeling session on each of your two or three most critical flows.
- Add dependency and secret scanning to every repository's pipeline, and rotate anything the first secret scan finds.
- Confirm that new endpoints are denied by default, and add tests for cross-user data access.
- Move secrets into a secrets manager and replace long-lived CI credentials with short-lived, federated ones.
- Create a single, owned backlog for security findings, with target dates by severity.
Our cybersecurity team works alongside delivery teams to put foundations like these in place.