Security vulnerabilities are easier to prevent than to fix. A SQL injection flaw caught in a code review costs minutes to correct. The same flaw discovered after a breach costs weeks of incident response, regulatory investigation, and remediation, plus whatever data was exfiltrated in between. Static code analysis is the discipline of finding these problems before the code runs, by examining source code structure, data flow, and patterns against known vulnerability signatures. Applied systematically, it converts security from a reactive practice into a built-in property of the development process.
The OWASP Top 10 is the authoritative catalog of the most critical web application security risks, updated by the Open Web Application Security Project based on real-world vulnerability data across thousands of applications. Every item on the list is detectable, to varying degrees, by static analysis tools. This guide maps each OWASP category to what static analysis can find, shows the vulnerable code pattern and its secure equivalent, and explains which tools apply each technique.
Find Injection Before Attackers Do
SMART TS XL traces vulnerabilities from JavaScript APIs through Java services to COBOL backends.
More InfoWhat Is Static Application Security Testing (SAST)?
Static Application Security Testing, SAST, analyzes source code, bytecode, or binary without executing the program. The analysis examines how data flows through the application, which security-sensitive operations that data reaches, and whether any path from an external input (user input, HTTP parameters, file contents, environment variables) leads to a dangerous operation (database query, system command, HTML output, cryptographic function) without appropriate validation or sanitization.
SAST is one layer in a complete application security program. Understanding where it fits requires comparing it to the alternatives:
| Approach | When It Runs | What It Finds | What It Misses |
|---|---|---|---|
| SAST (Static) | Before execution, on source code | Code-level vulnerabilities, injection patterns, cryptographic misuse, hardcoded secrets | Runtime-only vulnerabilities, configuration issues in deployment |
| DAST (Dynamic) | Against a running application | Runtime behavior, authentication flaws, server configuration issues | Code-level patterns not triggered during testing |
| SCA (Software Composition Analysis) | On dependency manifests | Known CVEs in third-party libraries | Custom code vulnerabilities |
| IAST (Interactive) | During test execution with instrumentation | Runtime data flows with high accuracy | Requires running application, slower feedback |
SAST provides the earliest feedback, it runs on code that has not yet been deployed, in the CI/CD pipeline or even in the IDE, before the vulnerability ever reaches a test environment. This earliness is its primary security value.
What Types of Threats Can Static Code Analysis Mitigate?
This is one of the most-searched questions about SAST. The direct answer:
Static code analysis can mitigate threats that manifest in source code patterns: injection vulnerabilities (SQL, command, XSS), cryptographic misuse, hardcoded credentials, insecure authentication implementations, broken access control in code logic, and data integrity failures. It cannot mitigate threats that arise from runtime configuration, network topology, or infrastructure setup, those require DAST, penetration testing, or infrastructure security scanning.
Static Analysis vs. Dynamic Analysis for OWASP Coverage
Dynamic analysis (DAST) and static analysis (SAST) find different subsets of OWASP vulnerabilities. Neither covers everything. The OWASP Web Security Testing Guide (WSTG) is the methodology framework for dynamic testing; SAST tools like CodeQL, Semgrep, and SonarQube address the source code layer.
| OWASP Top 10 Category | SAST Coverage | DAST Coverage |
|---|---|---|
| Broken Access Control | Partial, code logic gaps | Good, runtime behavior testing |
| Cryptographic Failures | Strong, algorithm detection | Weak, hard to observe from outside |
| Injection | Strong, taint analysis | Strong, active payload testing |
| Insecure Design | Partial, pattern detection | Weak, requires design knowledge |
| Security Misconfiguration | Partial, config in code | Strong, live environment testing |
| Vulnerable Components | Weak, SCA is better | Weak, SCA is better |
| Authentication Failures | Partial, hardcoded creds, weak patterns | Strong, session and auth testing |
| Data Integrity Failures | Partial, deserialization patterns | Weak, hard to detect externally |
| Logging Failures | Partial, missing log statements | Weak, hard to observe absence |
| SSRF | Strong, taint to HTTP call | Strong, active request testing |
The conclusion: SAST and DAST are complementary. For maximum OWASP coverage, run both. For budget-constrained teams starting from zero, SAST first, it provides the fastest developer feedback and the broadest injection coverage.
OWASP Top 10: What Static Analysis Finds and How
A01, Broken Access Control
Broken access control is the top OWASP risk. Static analysis addresses the code-level manifestations: missing authorization checks on functions and endpoints, object references that expose internal IDs without validation, and hardcoded role assignments that bypass intended permission structures.
What static analysis detects: Methods that handle sensitive operations without a corresponding authorization check; direct object references where the object ID comes from user input without access validation; force-browsing patterns where authenticated state is checked but object ownership is not.
java
// Vulnerable: no ownership check -- any authenticated user can access any order
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
}
// Secure: verify the order belongs to the requesting user
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId,
@AuthenticationPrincipal UserDetails user) {
Order order = orderRepository.findById(orderId).orElseThrow();
if (!order.getOwnerId().equals(user.getUserId())) {
throw new AccessDeniedException("Order does not belong to requesting user");
}
return order;
}
What static analysis cannot catch: Runtime access control failures where the logic is correct but the data used to make the decision is compromised. DAST and penetration testing are needed for these.
A02, Cryptographic Failures
Weak cryptography is reliably detectable by static analysis because the vulnerable patterns, MD5, SHA-1, DES, ECB mode, hardcoded keys, are lexically identifiable in source code.
python
# Vulnerable: MD5 for password hashing (broken algorithm)
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# Vulnerable: hardcoded encryption key
KEY = b"mysecretkey12345"
cipher = AES.new(KEY, AES.MODE_ECB) # ECB mode also vulnerable
# Secure: bcrypt for passwords, environment-sourced keys
import bcrypt, os
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Secure: AES-GCM with environment-sourced key
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = os.environ["ENCRYPTION_KEY"].encode()
aesgcm = AESGCM(key)
Static analysis rules flag: MD5/SHA-1 for security-sensitive purposes, DES/3DES/RC4/ECB mode, hardcoded cryptographic keys and secrets, HTTP instead of HTTPS for sensitive data transmission, and disabled certificate verification (verify=False in Python requests, setHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER) in Java).
A03, Injection
Injection, SQL, OS command, LDAP, XSS, template injection, is the category where interprocedural taint analysis provides its most direct value. The vulnerability requires tracking untrusted input from its source through function calls to a dangerous sink.
javascript
// Vulnerable: direct string interpolation in SQL (SQL injection)
app.get('/users', async (req, res) => {
const name = req.query.name;
const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);
res.json(result.rows);
});
// Secure: parameterized query
app.get('/users', async (req, res) => {
const name = req.query.name;
const result = await db.query('SELECT * FROM users WHERE name = $1', [name]);
res.json(result.rows);
});
php
// Vulnerable: unescaped output (XSS)
echo "Welcome, " . $_GET['username'];
// Secure: context-appropriate escaping
echo "Welcome, " . htmlspecialchars($_GET['username'], ENT_QUOTES, 'UTF-8');
csharp
// Vulnerable: command injection in C#
var process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c " + userInput;
process.Start();
// Secure: avoid shell interpretation, validate and whitelist inputs
var allowedCommands = new HashSet<string> { "report", "export" };
if (!allowedCommands.Contains(userInput))
throw new ArgumentException("Invalid command");
Tools that perform interprocedural taint analysis for injection: CodeQL (most precise), Semgrep with taint mode, Snyk Code, SonarQube with security rules.
A04, Insecure Design
Insecure design is the most difficult OWASP category for static analysis because it concerns architectural decisions rather than code patterns. Static analysis can flag the symptoms:
Missing rate limiting logic on authentication endpoints, absence of account lockout after failed attempts, business logic that skips validation steps, and functions that perform privileged operations without audit logging. These are detectable as missing patterns, static analysis that reports on what is absent rather than what is present.
Some SAST tools support custom rules that can encode organizational security design requirements: every controller method must call an authorization function, every database write must be preceded by input validation, every external API call must use a timeout. These custom rules convert design requirements into enforceable code constraints.
A05, Security Misconfiguration
Security misconfiguration in code includes: disabled security features, permissive CORS headers, missing security response headers, debug mode enabled in production, and verbose error messages that expose stack traces.
python
# Vulnerable: Flask debug mode enables interactive debugger in production
app = Flask(__name__)
app.run(debug=True) # exposes console access if error occurs
# Vulnerable: overly permissive CORS
from flask_cors import CORS
CORS(app, origins="*") # allows any origin
# Secure: environment-controlled debug, restricted CORS
import os
debug_mode = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
CORS(app, origins=os.environ.get("ALLOWED_ORIGINS", "").split(","))
app.run(debug=debug_mode)
Static analysis rules flag: debug mode set to True in source, wildcard CORS origins, missing security headers in HTTP response configurations, disabled SSL certificate verification, and default credentials in configuration files.
A06, Vulnerable and Outdated Components
This category is primarily addressed by Software Composition Analysis (SCA) rather than traditional SAST. SCA scans package.json, pom.xml, requirements.txt, and similar manifests against vulnerability databases (National Vulnerability Database, GitHub Advisory Database).
SAST contributes by identifying deprecated API usage, calls to library functions that have been superseded due to security flaws, or direct use of vulnerable patterns that even up-to-date libraries no longer recommend.
Tools specifically for A06: npm audit, pip-audit, snyk test, OWASP Dependency-Check, GitHub Dependabot, and Mend (formerly WhiteSource).
A07, Identification and Authentication Failures
Static analysis finds the code-level authentication anti-patterns: hardcoded passwords, weak password validation, session tokens generated with non-cryptographic randomness, missing session invalidation on logout, and JWT implementations that accept the none algorithm.
javascript
// Vulnerable: JWT accepting 'none' algorithm -- allows signature bypass
const decoded = jwt.verify(token, secret, { algorithms: ['HS256', 'none'] });
// Vulnerable: hardcoded admin credentials
if (username === 'admin' && password === 'admin123') {
grantAccess();
}
// Secure: algorithm whitelist, no hardcoded credentials
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'] // explicit allowlist only
});
csharp
// Vulnerable: weak random for session token generation in C#
var sessionToken = new Random().Next().ToString();
// Secure: cryptographically secure random
using var rng = RandomNumberGenerator.Create();
var bytes = new byte[32];
rng.GetBytes(bytes);
var sessionToken = Convert.ToBase64String(bytes);
A08, Software and Data Integrity Failures
This category covers insecure deserialization and unverified software updates. Static analysis detects: Java ObjectInputStream deserializing data from untrusted sources, Python pickle.loads() on external data, PHP unserialize() with user-controlled input, and YAML parsers using unsafe loaders.
python
# Vulnerable: pickle deserialization of untrusted data
import pickle
data = pickle.loads(request.data) # arbitrary code execution risk
# Vulnerable: unsafe YAML loader
import yaml
config = yaml.load(user_input) # yaml.load without Loader is unsafe
# Secure: safe alternatives
import json
data = json.loads(request.data) # JSON cannot execute code
import yaml
config = yaml.safe_load(user_input) # safe_load disables arbitrary object creation
A09, Security Logging and Monitoring Failures
Logging failures are detectable by static analysis as absent patterns: sensitive operations, authentication events, access control decisions, data modifications, that proceed without accompanying log statements. Static analysis rules can require that certain function calls always co-occur with audit log calls.
What static analysis flags: logging of passwords or tokens (logging sensitive data is also a vulnerability), exception handlers that silently swallow errors without logging, and catch blocks that log the raw exception message (which may contain sensitive data).
java
// Vulnerable: swallowed exception, no logging
try {
authenticateUser(username, password);
} catch (Exception e) {
// silent failure -- no log, no audit trail
}
// Vulnerable: logging sensitive data
log.info("User logged in with password: " + password);
// Secure: log the event, not the credential
try {
authenticateUser(username, password);
auditLog.info("Authentication success for user: {}", username);
} catch (AuthenticationException e) {
auditLog.warn("Authentication failure for user: {}", username);
throw e; // do not swallow
}
A10, Server-Side Request Forgery (SSRF)
SSRF is a strong case for interprocedural taint analysis. The vulnerability requires tracking user-controlled input through to an HTTP request, often through multiple function calls. A tool that only analyzes individual functions cannot detect SSRF when the URL construction and the HTTP call are in different functions.
python
# Vulnerable: user-controlled URL in HTTP request (SSRF)
import requests
def fetch_resource(url):
return requests.get(url).content # no validation
def api_endpoint(request):
target = request.json().get("url") # attacker controls this
return fetch_resource(target) # SSRF across function boundary
# Secure: allowlist validation before making the request
from urllib.parse import urlparse
ALLOWED_HOSTS = {"api.internal.example.com", "cdn.example.com"}
def fetch_resource(url: str) -> bytes:
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_HOSTS:
raise ValueError(f"URL host not allowed: {parsed.hostname}")
return requests.get(url, timeout=5).content
Static Code Analysis Tools for OWASP Security
The table below maps the major SAST tools to the OWASP categories they address most effectively:
| Tool | Primary Languages | OWASP Strengths | Approach |
|---|---|---|---|
| CodeQL | Java, JS/TS, Python, C/C++, Go, Ruby | A03 Injection, A10 SSRF (deep taint) | Interprocedural semantic analysis |
| Semgrep | 30+ languages | A03, A02, A07 (pattern-based + taint mode) | Pattern matching + shallow taint |
| Snyk Code | Java, JS/TS, Python, C# | A03, A07, A08 | ML-based taint analysis |
| SonarQube | 30+ languages | A02, A03, A05, A07, A09 | Rule-based + data flow |
| Checkmarx | 30+ languages | Full OWASP coverage | Interprocedural taint |
| Veracode | Java, .NET, JS, PHP | Full OWASP coverage | Bytecode + taint analysis |
| OWASP ZAP | Language-agnostic | A01, A05, A07 (runtime) | DAST, dynamic testing |
| SMART TS XL | COBOL, JCL, Java, Python, RPG, SQL | Cross-language taint, dependency risk | Cross-language structural + taint |
How SMART TS XL Addresses Security in Enterprise Codebases
Enterprise security programs face a challenge that single-language SAST tools cannot solve: the attack surface spans languages. A web application may accept user input in JavaScript, process it in Java, pass it through a message queue to a COBOL program that executes SQL against a DB2 database. The injection vulnerability exists across four language boundaries. No individual language scanner sees the complete taint path.
SMART TS XL’s static code analysis covers every language in this chain simultaneously. When untrusted input flows from a JavaScript API handler through a Java service into a COBOL program that constructs a dynamic SQL query, SMART TS XL traces that path across the full cross-language call graph, the same interprocedural taint tracking that CodeQL applies within Java, applied across Java, COBOL, and SQL together.
The application dependency mapping capability provides the security-relevant view of which components in the system interact with external inputs, which reach privileged operations, and what the complete attack surface of the system actually looks like. This architectural security view is the foundation for threat modeling, you cannot model threats against a system whose structure you do not understand.
The impact analysis capability serves security remediation programs: when a vulnerability is found in a component, impact analysis identifies every other component that depends on the vulnerable one, scoping the remediation effort accurately before any code is changed. For large legacy codebases where a COBOL program with a security flaw is included by hundreds of other programs, knowing the complete remediation scope before starting is the difference between a managed fix and a cascading incident.
For organizations managing legacy modernization programs, security analysis of the legacy codebase is a prerequisite, not an afterthought. Migrating vulnerable COBOL programs to Java produces vulnerable Java programs. SMART TS XL’s security analysis ensures that the vulnerabilities are identified and remediated as part of the modernization process, not discovered in the migrated system.
Integrating SAST Into the Development Lifecycle
Static security analysis provides its maximum value when it runs at the point of decision, where code is being written and reviewed, not after it has been deployed.
In the IDE: SonarLint, Snyk’s IDE extensions, and CodeQL’s VS Code extension surface findings inline as developers write code. A SQL injection flag that appears the moment a developer types the vulnerable pattern costs seconds to fix.
In pull requests: SAST integrated into GitHub Actions, GitLab CI, or Jenkins runs on every pull request and posts findings as inline code review comments. The developer sees the finding in context, alongside the code that caused it.
As a quality gate: SonarQube’s quality gate model blocks merges when new critical security hotspots are introduced. This makes security not an optional review step but a structural requirement of the merge process.
On a scheduled basis: Deep interprocedural analysis, CodeQL, Checkmarx, full Semgrep rule sets, is typically too slow for per-commit execution but runs nightly or weekly on the main branch, finding vulnerabilities that require full call graph analysis to detect.
The layered approach, fast pattern-based rules in the IDE and on commits, deep taint analysis on pull requests and nightly, provides both the immediacy developers need and the thoroughness security programs require.