Most security incidents in custom software do not start with an exotic attack. They start with a small, familiar mistake: a query built from a string, a missing permission check, a key committed to a repository. Secure coding is the habit of not making those mistakes, and of catching them in review when someone does.
This guide covers the practices that prevent most real vulnerabilities, why each one matters, and a checklist you can use in code review. If you run a site rather than write its code, our website security checklist is the better start; if you are new to programming, begin with our tips for new coders.
Why secure coding is a habit, not a phase
Security added at the end of a project is expensive. By then the data model, the authentication flow and the dependencies are fixed, and a design flaw means rework. The cheaper path is to make secure choices the default while the code is being written, and to let automation check the rest on every change.
That does not require every developer to be a security specialist. It requires a short list of rules the whole team follows, a review process that looks for the common failures, and tooling that runs without anyone having to remember it.
The standards worth building on
You do not have to invent the rules. Four public references cover most of what a team needs, and each does a different job:
| Reference | What it is | Use it for |
|---|---|---|
| OWASP Top 10:2025 | The ten most critical web application risk categories | Awareness and training; deciding what to review first |
| 2025 CWE Top 25 | MITRE's ranking of the most dangerous weakness types, from real vulnerability data | Prioritizing checks and static analysis rules |
| OWASP ASVS 5.0 | Testable security requirements for web apps and services, released May 2025 | Requirements, test cases and the definition of done |
| NIST SSDF (SP 800-218) | A framework of secure development practices | Organizing the process: training, tooling, review and response |
The 2025 lists agree on where the damage comes from. Broken access control is A01 in the OWASP Top 10:2025, followed by security misconfiguration and a new category for software supply chain failures. The top of the 2025 CWE Top 25 is cross-site scripting (CWE-79), SQL injection (CWE-89), cross-site request forgery (CWE-352) and missing authorization (CWE-862). The practices below map onto those lists.
Handle input and output safely
Injection flaws and cross-site scripting both come from the same root cause: data from outside the system is treated as code. The fix is to keep data and code apart at every boundary.
Validate input at the boundary
Check every input where it enters the system: form fields, query strings, headers, file uploads, webhooks and messages from other services. Validate on the server, even when the browser already checked, because client-side checks are a convenience for users, not a control.
- Prefer allowlists (what is permitted) over blocklists (what is forbidden).
- Check type, length, format and range, and reject anything that does not match.
- Parse structured input with a schema library instead of hand-written checks.
Use parameterized queries
Never build a database query by joining strings. Pass user input as a parameter, so the database treats it as a value and never as part of the command.
// Vulnerable: the input becomes part of the SQL text
const orders = await db.query(
`SELECT * FROM orders WHERE customer_id = '${customerId}'`,
);
// Safe: the input is sent as a parameter, never parsed as SQL
const orders = await db.query("SELECT * FROM orders WHERE customer_id = $1", [
customerId,
]);
The same rule applies to shell commands, LDAP queries and template engines. If an API accepts a single string that mixes command and data, look for the version that takes them separately.
Encode output for where it lands
Data that is safe in a database can be dangerous in a web page. Encode output for its context: HTML body, HTML attribute, URL or JavaScript each need different escaping. Modern frameworks such as React escape by default, so the risk concentrates in the places that bypass it, like raw HTML rendering. Treat every one of those as a review item, and add a Content Security Policy as a second layer.
Get identity and access right
Broken access control is A01 in the OWASP Top 10:2025, as it was in 2021. It is rarely a clever exploit; usually a request simply reaches data it should not.
Use proven authentication, never your own
Use a maintained identity provider or framework for sign-in, password resets and multi-factor authentication, and offer passkeys where your platform supports them. If you store passwords, hash them with an algorithm designed for passwords, never with a general-purpose hash such as SHA-256. OWASP's Password Storage Cheat Sheet puts Argon2id first, with at least 19 MiB of memory, two iterations and one degree of parallelism; scrypt next; bcrypt with a work factor of 10 or more for legacy systems; and PBKDF2 with HMAC-SHA-256 and 600,000 or more iterations where FIPS-140 compliance is required.
Check authorization on every request
Check permissions on the server for every request, not only when a page loads. The classic failure is an endpoint like /invoices/1042 that returns any invoice to any signed-in user who changes the number. Deny by default, check that the user may act on this record, and write tests for the refusal as well as the success. In an API this flaw is called broken object level authorization, and our guide to enterprise API development covers it with the rest of the OWASP API Security Top 10.
Protect sessions and cookies
Set session cookies with HttpOnly, Secure and SameSite, expire sessions after inactivity, and issue a new session identifier after sign-in. Protect state-changing requests against cross-site request forgery, which most frameworks handle once it is switched on.
Protect secrets and data
Keep secrets out of the codebase
API keys, database passwords and tokens belong in environment variables or a secrets manager, never in source code or in files committed to the repository. Assume anything committed is permanent: deleting it in a later commit leaves it in the history. If a secret leaks, rotate it first and clean up second.
Use vetted cryptography
Use the encryption your platform and well-known libraries provide, with current defaults: TLS 1.2 or later in transit, and managed encryption for data at rest. Do not design your own algorithms or protocols, and do not store encryption keys next to the data they protect.
Log events, not sensitive data
Record security-relevant events: sign-ins, failed sign-ins, permission denials and changes to roles or settings. Keep passwords, tokens, full card numbers and personal data out of the logs, because logs are copied to more places, and read by more people, than the database.
Fail safely
When something goes wrong, the system should fail closed: deny the action rather than allow it. The OWASP Top 10:2025 gives this its own category, A10, mishandling of exceptional conditions: an unhandled error in the middle of a payment or a permission check can leave the system in a state nobody designed.
- Catch errors where you can act on them, and roll back partial changes inside a transaction.
- Treat timeouts and unexpected responses from other services as failures, not as success.
- Show users a plain message and a reference number, and keep stack traces, SQL errors and internal paths in your logs. Detailed error pages in production tell an attacker exactly how the application is built.
Keep dependencies and configuration under control
Treat dependencies as code you ship
Most of an application's code comes from third-party packages, and their vulnerabilities become yours. The OWASP Top 10:2025 made this its own category, A03, software supply chain failures, covering compromised packages and build pipelines as well as outdated components.

- Commit a lockfile, pin versions and remove packages you no longer use.
- Update on a regular schedule instead of all at once when something breaks, and let a bot open the pull requests.
- Check new packages before adding them: who maintains them, how active they are, what they pull in, and whether the name is a near-miss of a popular package (typosquatting).
- Generate a software bill of materials (SBOM) at build time, so that when a vulnerability is announced you can tell in minutes which applications contain the affected version.
- Protect the pipeline itself: least-privilege tokens in CI, protected branches, and signed or provenance-checked build artifacts.
Ship secure defaults
Turn on the protections that cost nothing once configured: HTTPS everywhere, security headers, and least privilege for database users and service accounts. Disable debug modes, sample pages and default credentials before anything reaches production.
Make it part of the workflow
Rules only work if they are checked. Code review catches design problems that tools miss, and automated checks catch the repetitive ones that reviewers stop noticing. Run both on every change.

| Check | What it catches | When it runs |
|---|---|---|
| Static analysis (SAST) | Injection patterns, unsafe APIs and missing checks in your own code | Every pull request |
| Dependency scanning | Known vulnerabilities in third-party packages | Every build, and on a schedule |
| Secret scanning | Keys and tokens committed by mistake | Every push |
| Dynamic testing (DAST) | Flaws and misconfigurations visible in the running application | Against staging, before release |
| Container and IaC scanning | Vulnerable base images and insecure cloud or Kubernetes settings | Every build of an image or template |
If you do not have these checks in place yet, our security testing service sets them up and reads the results with you, so findings arrive ranked and with a fix, not as a raw report.
Review AI-generated code like any other contribution
Coding assistants now write a large share of new code, and they write it with confidence whether or not it is right. Treat their output like a pull request from a capable new contributor who has never seen your threat model. What the studies say about AI assistants and code security, and how teams adopt them, is in our guide to the future role of software engineers.
- Check every import. In a study presented at USENIX Security 2025, code-generating models recommended packages that do not exist, at least 5.2% of the time for commercial models and 21.7% for open-source ones. Attackers can register those names, so a hallucinated dependency can become a malicious one ("slopsquatting").
- Look for the old mistakes. Generated code reproduces patterns from its training data, including string-built SQL, disabled certificate checks and missing authorization.
- Keep secrets out of prompts. Do not paste keys, customer data or proprietary code into tools your organization has not approved for it.
- Let the same gates apply. AI-written code goes through the same static analysis, dependency and secret scanning, tests and human review as everything else.
A checklist for your next code review
- Is every new input validated on the server, with an allowlist?
- Do queries and commands take user data as parameters, never as concatenated strings?
- Is output encoded for its context, and does anything bypass the framework's escaping?
- Does every new endpoint check that this user may act on this specific record?
- Are secrets read from the environment, with none added to the repository?
- Do errors fail closed and keep internal details out of responses?
- Are security events logged without sensitive data?
- Are new dependencies necessary, maintained, pinned and real (not a hallucinated or look-alike name)?
- Does the change handle errors and timeouts by failing closed?
Where to start with an existing codebase
You do not need to fix everything at once. Start with the checks that run themselves: dependency and secret scanning take an afternoon to switch on and immediately show your largest exposures. Then review the areas attackers reach first, which are authentication, authorization and anything that accepts file uploads or builds queries. Fix what you find, add a test for each fix, and make the checklist above part of every review from then on. Pick the ASVS level that matches the data you hold, and measure the application against it once a year, or after a major change.


