Secure Code Reviews: Finding Security Issues Before Attackers Do

For most corporate IT workshops, we are often trying to abstract as many security details away from application code as possible. I understand things may work differently at Big Tech / shops-with-many-competent developer resources. I initially started jotting down notes for this blog post while I was preparing for a job interview for a security architecture position. In the current job market, I’ve found that there are opportunities, even for independent cybersecurity firms, but the prospective client wants to do what is essentially an interview before they commit. This particular client was very interested in “secure code reviews” — processes, skill sets, tools, etc. In fact, my earlier post about “Software Supply Chain Security” started as a collection of notes while talking to the same client: “SOFTWARE SUPPLY CHAIN SECURITY: CI/CD/CT PIPELINES AND SECURITY TOOLS — PART 1” and “SOFTWARE SUPPLY CHAIN SECURITY: CI/CD/CT PIPELINES AND SECURITY TOOLS — PART 2”
In the Age of AI Hype, never take the humans completely out of this loop. Someone should be looking at the code that agent is generating
Software vulnerabilities rarely appear out of nowhere. Most security flaws are introduced during development and remain hidden until they are discovered by testers, security researchers, or attackers.
One of the most effective ways to identify these issues early is through secure code reviews.
While automated security tools play an important role in modern software development, they cannot replace the human ability to understand business logic, application architecture, and how seemingly harmless code changes can introduce security risks.
A well-executed secure code review can uncover vulnerabilities long before they reach production, reducing remediation costs and improving overall software quality.
What Is a Secure Code Review?
A secure code review is the process of examining source code with a specific focus on identifying security weaknesses.
Unlike traditional peer reviews, which primarily focus on functionality, maintainability, and coding standards, secure code reviews concentrate on questions such as:
- Can an attacker manipulate this input?
- Is authorization enforced correctly?
- Are secrets being exposed?
- Can sensitive data be leaked?
- Is cryptography implemented safely?
- Could this code be abused in unexpected ways?
The goal is not simply to verify that the code works — it is to determine whether the code can be exploited.
Why Secure Code Reviews Matter
Many vulnerabilities are difficult for automated scanners to detect because they involve business logic or application-specific behavior.
Consider the following examples:
Insecure Authorization
An API endpoint may correctly authenticate users but fail to verify whether they are authorized to access a specific resource.
The code functions exactly as intended from a technical perspective, but still creates a serious security vulnerability.
Sensitive Data Exposure
For production, disable debugging mechanisms.
Debug logging may inadvertently record:
- Passwords
- Session tokens
- API keys
- Personally Identifiable Information (PII)
Static analysis tools may miss these contextual issues.
Business Logic Flaws
An online store may correctly process discounts but allow customers to stack promotions in ways that were never intended.
These vulnerabilities often require human analysis to identify.
Common Vulnerabilities Discovered During Code Reviews
Secure code reviews frequently uncover issues such as:
Injection Vulnerabilities
Examples include:
- SQL Injection
- NoSQL Injection
- Command Injection
- LDAP Injection
Reviewers examine how user-supplied input is handled and whether appropriate validation and parameterization techniques are used.
Broken Access Control
Broken access control remains one of the most common application security issues.
Reviewers verify that:
- Authorization checks exist
- Checks cannot be bypassed
- Users cannot access resources belonging to others
Authentication Weaknesses
Reviewers examine:
- Password handling
- Session management
- Token validation
- Multi-factor authentication implementation
Cryptographic Misuse
Common mistakes include:
- Hardcoded encryption keys
- Weak algorithms
- Custom cryptographic implementations
- Improper certificate validation
Insecure Secrets Management
Developers sometimes accidentally commit:
- API keys
- Database credentials
- Cloud access tokens
- Private certificates
Code reviews help catch these mistakes before deployment.
What Reviewers Should Look For
A secure code review should follow a structured methodology.
There’s probably as many opinions about this topic among cybersecurity professionals as there are stars in the sky. This is my take on it. Your mileage may vary.
Please treat this list as a starting point, not a complete guide. Not every recommendation is going to be relevant to every use case.
Key areas include:
- Language-specific security best practices
- Secure software supply chains
- Understand what the code is supposed to be doing
- Don’t reinvent the wheel
- Runtime environment
- Threat modeling
- Logging
- Error Handling
- Secure credentials storage
- Patching
- Data classification
- Data flow
- Data protection
- Input validation
- Identity: Authentication
- Identity: Authorization
- Identity: Session Tracking
- Identity: User Definitions
- Identity: Other
- Third-Party Dependencies
- PKI
- Encryption: Inflight
- Encryption: At Rest
Each section is not meant to be a full discussion on the topic, but rather an introduction that will reference additional blog posts I’ve written on the topic — where available.
Are you, as the secure code review engineer, going to look at every line of code? It would be great if you could. This is very often not practical, but at least look through a subset of the code base to understand what is out there. If they make the same mistakes over-and-over, point out a couple of places where they do it and request that they consistently fix it throughout the code base. The industry is actively trying to reliably abstract away as many of these details in the process as possible. But, we will never be able to, and don’t want to, remove the human from the loop.
If you miss something in the secure code review process, does that mean the developer is off the hook for responsibility? Absolutely not, but I’m sure they will try.
Always apply “Application Security Best Practices” as a starting point.
Language-Specific Security Best Practices
Security is ultimately less about choosing the “right” programming language and more about consistently applying secure development practices throughout the software development lifecycle. Regardless of language, organizations should:
Rather than maintaining separate internal standards for every language, many organizations use the language-specific guidance published by the Open Worldwide Application Security Project (OWASP) as a baseline. Here are the 2026 OWASP recommendations for the five most popular languages.
- Python: OWASP Python Security Cheat Sheet
- Javascript / Typescript: OWASP Node.js Security Cheat Sheet
- Java: OWASP Java Security Cheat Sheet
- C# / .NET: OWASP .NET Security Cheat Sheet
- Go: OWASP Go Secure Coding Practices Guide
Secure Software Supply Chain
Your software is only as secure as the build system that produces it.
In fact, all the stuff described in my “SOFTWARE SUPPLY CHAIN SECURITY: CI/CD/CT PIPELINES AND SECURITY TOOLS — PART 1” and “SOFTWARE SUPPLY CHAIN SECURITY: CI/CD/CT PIPELINES AND SECURITY TOOLS — PART 2” blog posts should already be in place and producing usable results prior to the information security department committing resources to secure code reviews for every application.
I’m including a couple of the especially important highlights in this section.
SAST / DAST Tool Reports
The build pipeline for the application should already run these tools. There should be reports available that the reviewer can analyze. Ideally, these reports will already have been addressed by the development team.
- Package dependencies with known vulnerabilities.
- code scanning with best practices
- container image scanning (not directly relevant to this stage)
Coding style enforcement tool
The build pipeline should analyze the code base for standardized coding style, error handling, and other syntactic consistencies that are easily detected by such tools. The pipeline should generate a report. The developer should have already addressed issues in this report. You should have access to the report. There shouldn’t be anything interesting in the report at this point.
In the absence of all other guidance, be consistent. I could repeat this advice in virtually every section of every post that I have ever written.
Dependency License Checks
For OSS packages/libraries is an acceptable license being used?
Has your organization’s legal department been engaged to identify what they are comfortable with? If not, now would probably be a good time to contact them.
Try to stick with MIT, but every organization has (or should have) their own policy that legal is comfortable with.
As recently as five years ago, I walked into an organization that did not allow OSS code to be used in any homegrown software. They’ve been sold several times since then and have laid off over 75% of their workforce from that time; so, attitudes are changing.
Code Copyright Infringement
The code scanner that does the dependency license checks and style enforcement should also do some type of code copyright violation check.
About fifteen years ago, a buddy of mine had done a Java application project for a large bank. Part of the QA process in place was to run such a scanner. It flagged part of his code as having been copied from an OSS project that had a copyright notice. This wasn’t considered acceptable by that organization — I don’t know all the legalities.
After a quick review of the resulting report, my buddy pointed out that he was listed in the copyright holders file. And, that he was authorized to use the code in the manner that he had and by extension as his client they could as well. The organization’s legal department was okay with this and the project proceeded to production.
I have no idea how this scenario would unfold today, but you get an idea of what this type of functionality is looking for. The goal is to protect the organization from software copyright entanglements down the road.
What Is The Code Supposed To Be Doing?
Understand what this code is supposed to do.
Ideally this is obvious.
Context matters. Especially before you start demanding changes be made.
Some developers like to write “clever” code that is nearly unreadable. Slap them and make them rewrite it. “clever” coding that isn’t clear what is happening has no place in business applications for corporate IT shops. My experience has been that in the realm of corporate IT, the “clever” developers need to go somewhere else and be clever.
Maybe you run it in a safe environment to see what happens and build an understanding. Not usually necessary. Most business apps aren’t that complicated. If it is this complicated, then who’s problem is that? The code reviewer or the developer who wrote it? You could always do a code walk through with the developer the first time so you understand the flow of the code and the basic idea of what it is supposed to accomplish. Doing this in a non-confrontational way may be beyond the skill sets of many cybersecurity professionals.
My experience has been that well-written business application code has plenty of comments to understand what the code is supposed to be doing in each block. It doesn’t have to go into a lot of detail; just one-liners that explain the next bit. Again, this is one of those topics that has many opinions.
Understand what is calling this code and what this code calls. So, that a mindmap-style breakdown of the code base can be developed.
Understand what downstream systems/services does code dependencies (such as IAM, messaging libraries) come from.
Get into the mindset of the developer who wrote this code. What were they trying to achieve? Why did they do it this way? Laziness, incompetence, ignorance, inexperience, malice, or the opposite of each one of those may all be motivating factors.
In this day-and-age, it may also be the case that the developer didn’t write any of it — some coding agent did. Is the code identified through comments or git commit messages that it is AI-generated? Does your organization have a policy requiring such disclosures?
Similarly, the equivalent of this from past decades is code copied from StackOverflow and similar developer help websites. What is your organization’s policy about such disclosures via comments (or whatever mechanism)?
Test cases should include both positive and negative scenarios to validate all major functionality.
Don’t Reinvent The Wheel
Is there an OSS library that does the same thing? Especially for a critical function like authentication client library — very few organizations would have the need to roll their own. There’s almost always some OSS library that can be used.
Or, is there an existing service that does the same thing?
But, keep in mind politics, perception, performance, etc, etc.
This may strike you as coding advice, but recreating a complex library from scratch almost always introduces new vulnerabilities. Do you think the one everyone is using that is big and complicated got that way overnight?
Runtime Environment
You need to understand what the target runtime / execution environment is.
- What does the runtime / execution environment look like?
- What is the hardware platform?
- What Public Cloud Platform is being used?
- What is the Operating System?
- Are containers being used? What is the Container Management Platform?
- Is this a serverless application? If so, what is the runtime service?
- What is the language runtime?
- What package management tool (pip, npm, maven, etc) is used?
Threat Modeling
Threat modeling is a valuable companion to secure code review because it helps reviewers understand not just what the code does, but how an attacker might misuse it.
A reviewer should identify trust boundaries, data flows, privileged operations, and external dependencies to determine where threats such as spoofing, tampering, information disclosure, denial of service, or privilege escalation may arise. This analysis should consider the application’s architecture, authentication mechanisms, authorization model, APIs, third-party integrations, and deployment environment. Rather than reviewing code in isolation, threat modeling encourages reviewers to ask questions such as:
- What assumptions does this code make about its inputs?
- Can an untrusted user reach this functionality?
- What happens if this component is compromised?
By systematically identifying attack surfaces and abuse cases, reviewers can focus their efforts on the portions of the codebase that present the greatest security risk and ensure that appropriate controls are implemented to mitigate identified threats.
Trust Boundaries
For each identified trust boundaries, understand:
- What protocol is being used to cross that trust boundary?
- What authentication mechanism is used at the trust boundary?
- What credentials are used at the trust boundary?
- What access control mechanism is in use at the trust boundary?
Logging
Most applications are going to have a notable amount of the code base dedicated to logging. If there is no logging, it’s going to be very difficult to do troubleshooting when things break or have proper audit logging.
At a minimum, every request the system processes should generate a metric or log entry that represents that the call was made, how long it took to execute, the result of that query (failure, success, not found, HTTP return code, etc), who the authenticated user is, and other information deemed important to your situation.
Every request should be assigned a unique identifier. Maybe the identifier (a guid) is generated in the browser and passed into the system. Maybe a new request has a unique GUID injected at the edge of the system. This could be tracked as a custom HTTP header, a query parameter, message body, or other mechanism. Every log entry generated, including errors, by this request should include the unique identifier. This way, every log entry related to a specific request can be easily retrieved.
Ideally, some type of central logging platform is used to gather up “standard out/err” logs that are generated by application / component pieces. Or, even better, there is a logging service that all log entries are asynchronously sent to. One may need to be careful if you are being charged by API call or total volume of network traffic or similar metric that may be artificially inflated by using a logging service.
Error Logging
Don’t swallow errors.
At least print the error out including stack trace for later troubleshooting. Write this information to your logging service (standard out, whatever).
Do NOT include the detailed error information in the response to the caller.
Be consistent in exception / error handling and logging.
Debug Logging
It should be possible to enable debug logging in a non-production environment and get a detailed dump of what code is doing as an alternative to hooking up a debugger to it.
If the testers and operations people are separate people / teams from the development staff, this is an important characteristic for the non-devs to be able to do basic troubleshooting.
Likewise for a product that is setup in an environment you do not control, this is going to be important for troubleshooting purposes.
Log Hygiene
Don’t write sensitive values to your logs. Maybe you write partial values for debug logging in non-production environments.
Some shops utilize data masking libraries for the UI layer and logging to provide a more systematic, configuration-drive approach to ensuring sensitive values are not logged. There are several opensource libraries that provide this type of functionality.
Error Handling
Error messages should not expose:
- Internal architecture
- Database structure
- Source code details
- Stack traces
Be consistent.
For large / robust applications, consider having a documented set of error codes complete with recommended troubleshooting steps for each error code. This may very well take as much effort as writing the software to begin with.
Test cases should include scenarios that exercise error handling across all major functionality.
Secure Credentials Storage
Don’t hard-code credentials.
Don’t store credentials in unencrypted text files.
Don’t check credentials into source control. If you do, make sure you remove it from the commit history as well, invalidate the credential, and rotate.
Store all credentials and sensitive values in a secure storage mechanism. Think HashiCorp Vault, CyberArk, AWS Secrets manager, Google Secret manager, Azure key Vault, etc, etc, etc.
Never log these values. For debug logging, maybe you log its length and the first character to confirm you have the value there that you think you do.
Test cases should include both positive and negative secure credentials read scenarios.
Patching
I talk about patching in the “SOFTWARE SUPPLY CHAIN SECURITY: CI/CD/CT PIPELINES AND SECURITY TOOLS — PART 1” and “SOFTWARE SUPPLY CHAIN SECURITY: CI/CD/CT PIPELINES AND SECURITY TOOLS — PART 2” blog posts.
There are going to be numerous layers that have patches available to be applied including:
- Direct package dependencies to your code.
- Indirect package dependencies
- Tooling updates
- Container image updates.
- Runtime execution environment updates
- OS package updates (containers / VMs)
- Cloud platform configuration updates or service version updates (context dependent)
- Others
Each layer should be updated at least once per month — within twenty four hours for high-severity vulnerabilities. This patching process should be automated to the greatest extent possible.
Validation of this patching activity should be automated through an automated test suite using established testing frameworks.
No, it is not your responsibility as the security engineering performing a secure code review to validate all aspects of these processes — that would happen out-of-band. But, it is a question that should be asked in the normal course of your secure code review (probably through some type of checklist / questionnaire).
Do understand the source of patches for each layer. All patches, all third-party software, should be from trusted sources. This is
- Are they pulling development images from DockerHub?
- Are they using well-known / common modules?
- Could the same thing be accomplished using an alternate package / module that has 500+ fewer dependencies? It happens.
At an organizational-level, there should be a list of packages that have been reviewed and are considered trusted (along with all dependencies). This should be a known quantity for a routine secure code review. There are services available such as JFrog Curation that provides a curated package repo (or logical equivalent of one) that has already been scanned for security issues — this can be expensive, but worthwhile.
Data Classification
What is the nature of the data passing through this system?
Your organization should have a data classification standard. Every project / data store / schema should have this documented (along with data owner and purpose) in a standard format stored in a standard location. This should be a simple checkbox item for the secure code review, but if it isn’t done, the team has work to do.
For you as the security engineer performing a secure code review, the data classification should be one of the first data points you are looking for to understand how sensitive this system and its data is.
Data Flow
This one is usually my starting point when I come into a new organization / project and they want me to do a security architecture review — a very common engagement format.
- What are the data flows?
- Where is the data coming from?
- Where is it going?
- Where is data stored at rest?
- Who can view the data?
- Who / what can modify the data?
Data Protection
Reviewers should identify:
- Sensitive data storage locations
- Encryption (transport layer, field-level, message-level) usage
- Digital Signature usage
- Logging behavior (discussed above)
- Data transmission methods
- Access Control
For a large / established organizations, there should be standard / approved patterns for these things.
Input Validation
Check out my “Input Validation: Because Users Are Creative, and Attackers Are More Creative” post for a larger discussion of this topic.
Questions to ask:
- Can attackers supply unexpected values?
- Are inputs validated on the server side?
- Are dangerous characters properly handled?
I recommend / prefer there always be an input validation layer. Even if you believe the input is coming from a trusted source. Those trusted sources can make mistakes. This may seem like commonsense to many at this point, but you would be amazed how many IT shops have no concept of it. I’m usually doing input validation for my projects through something inline that is controlled by a security engineer, not by individual development teams. Not inline in the code. Think WAFs and API Gateways as a starting point, but not the whole story. This is an example of abstracting away security concerns from application code.
If you don’t have that kind of system or want to do it within the code, then there are OpenSource packages that can help with that. At a minimum, your input validation layer should:
- Type checking.
- schema validation of complex types (XML, JSON, etc).
- Check for extra fields
- Check for missing fields.
- Use regular expression matching to make sure the input value on strings or numbers looks like what is expected.
Test cases should include both positive and negative input validation scenarios. It doesn’t have to be every field, but enough to convince you that it is working.
Identity: Authentication
So, authentication is, generally, fairly important. It should be there; if it isn’t, definitely, some questions to be asked. I’ve discussed authentication extensively in previous blog posts. We’re not going to attempt to rehash all of that here.
Just like input validation, for most of the contexts where I am operating, we’re trying to abstract this away from application code.
There are many possible ways of doing this. Consistency is important.
Understand, what is being authenticated? Is it a system or a human user? Something else?
Reviewers should verify:
- Identity verification occurs consistently
- Tokens are validated correctly
Test cases should include both positive and negative authentication scenarios.
Identity: Authorization
One of the most important questions in application security is “Who is allowed to do this?”
Every sensitive operation should include an authorization decision (access control).
Again, I’ve discussed authorization extensively in previous blog posts. We’re not going to try to repeat all of that here.
Be consistent in how authorization decisions are enforced. Ideally, not just within the one application, but across your organization.
Test cases should include both positive and negative authorization scenarios.
Identity: Authenticated Session Management
A security code reviewer evaluating authenticated session management should verify that sessions are created, maintained, and terminated securely throughout the application lifecycle. This includes ensuring session identifiers are generated using cryptographically secure random values, are sufficiently long and unpredictable, and are transmitted only over encrypted channels. Reviewers should confirm that session tokens are stored in secure, HttpOnly, and SameSite cookies where appropriate, and that session identifiers are regenerated after authentication events such as login, privilege elevation, or password changes to prevent session fixation attacks. The code should properly enforce session expiration through both idle and absolute timeouts, invalidate sessions during logout, and revoke sessions when accounts are disabled or credentials are changed.
Additionally, reviewers should examine protections against session hijacking, cross-site request forgery (CSRF), token replay, and concurrent session abuse. Logging and monitoring should capture authentication and session-related events without exposing sensitive session identifiers.
Finally, special attention should be given to distributed architectures and APIs to ensure that session state, token validation, refresh token handling (if applicable), and revocation mechanisms remain consistent across all services and trust boundaries.
The authentication library the application uses to interact with the IdP should also provide all the mechanisms needed for secure session management.
Identity: Unauthenticated Session Management
Every application has at least a brief period prior to a user being authenticated. Understand how this works for the application being reviewed.
Check out my “Understanding Unauthenticated Traffic: How Applications and APIs Represent The Unauthenticated User” blog post on this topic.
Identity: User Definitions
How are users defined in the Identity Provider? LDAP, Active Directory, database, IDaaS solution, etc.
Does the application maintain a user profile within its database that extends the user definition?
Are there multiple user communities that use the application?
Check out “Keeping Your APIs Secure for Multiple User Types”.
PKI
- How are key-pairs secured and accessed (X.509, JWKS, etc)? Ideally, the answer to this ties into the secure secrets storage pattern discussed earlier.
- How are truststores and trust chains managed?
- How are key-pairs issued / renewed? Ideally, this is a known pattern that is used throughout your organization.
- Who is responsible for PKI concerns? Again, ideally, this is abstracted away from the application developers.
Encryption: Inflight
All communication paths should be encrypted regardless of whether it is over an internal network or public internet. Generally, this means TLS v1.2 or better (more-and-more TLS v1.3 or better).
More sensitive data may require field-level or message-level encryption.
Post-Quantum Encryption algorithms are becoming more common. Your organization should have a path towards this migration.
Encryption: At Rest
All data should be stored on encrypted file systems at a minimum.
More sensitive data should be encrypted at the table, column, or relevant level.
AES256 should be used at a minimum. The encryption key should be stored using a secure secrets management solution — discussed earlier.
See my “Datastore Security Requirements” blog post for more details.
Manual Reviews vs Automated Reviews
The strongest security programs combine both automated and manual / human-review approaches.
Automated Analysis
Tools can quickly identify:
- Known vulnerable libraries
- Hard-coded secrets
- Common coding errors
- Unsafe API usage
Some examples include:
- Static Application Security Testing (SAST)
- Software Composition Analysis (SCA)
- Secret scanning tools
Human Review
Human reviewers excel at finding:
- Logic flaws
- Authorization mistakes
- Design weaknesses
- Architectural risks
- Abuse scenarios
Neither approach is sufficient on its own.
Secure Code Reviews in DevSecOps
Modern organizations increasingly integrate security reviews directly into development workflows.
Common practices include:
- Pull Request (PR) security reviews
- Security-focused review checklists
- Automated scanning in CI/CD pipelines
- Threat modeling during development
- Security champions within engineering teams
The goal is to identify vulnerabilities while code changes are still small and easy to fix.
AI-Assisted Code Reviews
Artificial intelligence is increasingly being used to assist security reviews.
Modern AI tools can:
- Identify insecure coding patterns
- Explain potential vulnerabilities
- Suggest remediation options
- Generate secure coding recommendations
However, AI tools should be treated as assistants rather than decision-makers.
Security teams should still validate findings and understand the broader business context surrounding the code.
Try not to succumb to AI psychosis.
Best Practices for Effective Secure Code Reviews
Organizations should consider the following practices:
- Review security-sensitive code first.
- Use standardized security checklists.
- Integrate automated scanning into development pipelines.
- Train developers on common vulnerability classes.
- Focus on high-risk application components.
- Verify authorization and authentication logic carefully.
- Review dependency and supply chain risks.
- Document findings and remediation actions.
- Re-review fixes before deployment.
- Treat security as a continuous process rather than a one-time event.
Final Thoughts
Secure code reviews remain one of the most effective techniques for reducing software vulnerabilities. While automated tools can rapidly identify many common issues, human reviewers provide the contextual understanding necessary to uncover logic flaws, authorization weaknesses, and architectural risks that machines often miss.
By incorporating secure code reviews into the software development lifecycle, organizations can detect vulnerabilities earlier, reduce remediation costs, strengthen their security posture, and ultimately deliver more resilient applications.
In today’s threat landscape, secure code reviews are no longer optional; it is a fundamental component of modern software security.
The most successful security programs focus on secure design, dependency management, automated testing, code review, and operational security rather than relying on language choice alone. Modern languages reduce certain classes of vulnerabilities, but no language can compensate for poor security practices. OWASP’s language-specific guidance provides an excellent starting point for development teams building secure applications in 2026.
Notes
- AI / GenAI / ChatGPT / etc were not used to generate the text of this article.
- ChatGPT was used to generate the images.
- I used em dashes in my writing before the current GenAI wave was a thing. Not planning on changing now.
- Names have been changed to protect the guilty.
- None of the hostnames or users used in examples actually exist.
- Feel free to post any comments or suggestions below.
- If this was useful to you, please click “clap” on medium.com.
Originally published on Medium.