Safeguard
AppSec

Code Quality Scan: What It Catches and Where It Stops

A code quality scan flags maintainability and reliability issues in your source, but it is not a security scan. Here is what each type finds and how to run both without noise.

Karan Patel
Platform Engineer
6 min read

A code quality scan is automated static analysis that inspects your source for maintainability, reliability, and style problems without running the program. It flags dead code, overly complex functions, duplicated blocks, missing error handling, and violations of your team's conventions. What it usually does not do well is find security vulnerabilities, which is a common and expensive misunderstanding. Quality and security overlap, but they are not the same scan, and treating one as a substitute for the other leaves real gaps.

Teams reach for a code quality scan when a codebase grows past the point where humans can review every line consistently. The scan gives you a repeatable, objective baseline so that "is this code good enough to merge" stops being an argument and becomes a check.

What a code quality scan actually measures

Most quality tools compute a handful of core signals. Cyclomatic complexity counts the independent paths through a function; a function with a complexity of 40 is nearly impossible to test or reason about. Duplication detects copy-pasted blocks that will drift out of sync. Code smells flag patterns that are not bugs today but predict bugs tomorrow, like a method with eleven parameters or a class that does six unrelated things.

Coverage is often bundled in too. A quality gate might require that new code carry at least 80 percent test coverage and introduce no new critical smells, while leaving legacy debt untouched. That "new code" focus matters, because retroactively fixing an entire codebase to pass a gate is how teams end up disabling the gate.

Popular tools in this space include SonarQube, CodeClimate, and the language-native linters: ESLint for JavaScript and TypeScript, Ruff or Pylint for Python, RuboCop for Ruby, and Checkstyle or PMD for Java.

Where a code quality scan stops

Here is the trap. A clean quality report feels like a security clearance. It is not.

A code quality scan checks how you wrote the code. It does not know that the string you concatenated into a query is user-controlled, that the dependency you imported has a known CVE, or that the endpoint you exposed has no authorization check. Those are security questions, and they need different analysis.

Consider this snippet:

def get_user(request):
    uid = request.args.get("id")
    query = f"SELECT * FROM users WHERE id = {uid}"
    return db.execute(query)

A quality linter might pass this cleanly. The function is short, readable, and has no style issues. A security scanner, by contrast, immediately flags a SQL injection risk because untrusted input flows into a query string. The fix is a parameterized query:

def get_user(request):
    uid = request.args.get("id")
    return db.execute("SELECT * FROM users WHERE id = %s", (uid,))

Clean code and safe code are different properties. You need both scans.

Quality scan vs. security scan (SAST)

The overlap causes confusion, so here is the practical distinction.

A code quality scan optimizes for maintainability. Its findings answer "will this code be hard to change or likely to break under normal use?" A security static analysis (SAST) scan optimizes for exploitability. Its findings answer "can an attacker abuse this code path?" Some tools do both, but they run different rule sets, and the security rules are the ones that catch injection, deserialization, path traversal, and hardcoded secrets.

Beyond your own source, there is a third scan that neither quality nor SAST covers well: your dependencies. Software composition analysis inventories the open source packages you pull in and checks them against vulnerability databases. Since most applications are majority third-party code, an SCA scan closes a gap that no amount of clean first-party code can address.

Running a quality scan in CI without drowning in noise

The failure mode of any static scan is alert fatigue. Turn on every rule and you generate thousands of findings, developers stop reading them, and the scan becomes decoration. Three practices keep it useful.

First, gate on new code only. Set a baseline and fail the build when a change introduces new critical issues, not when it touches a file that already had ten.

# example gate policy
quality_gate:
  new_code:
    coverage_min: 80
    duplicated_lines_pct_max: 3
    new_critical_issues: 0
    new_blocker_issues: 0

Second, tune the rule set to your language and stack. Generic rules produce generic noise. A TypeScript project does not need C-style pointer warnings.

Second, make findings actionable in the pull request itself. A comment on the exact line, with the rule and a suggested fix, gets addressed. A report in a separate dashboard gets ignored.

Making the scan matter to reviewers

A quality scan works best as a floor, not a ceiling. It should handle the mechanical checks (formatting, complexity thresholds, obvious smells) so human reviewers can spend their attention on design, correctness, and the security questions the scanner cannot answer. When the scan is the floor, code review becomes higher-value, not redundant.

Pair the quality scan with a dependency scan and a SAST pass and you cover three distinct risk surfaces: how the code is written, whether it is exploitable, and what it depends on. Skipping any one of the three leaves a category of defect entirely unmonitored. Our academy breaks down how to sequence these in a pipeline.

FAQ

Is a code quality scan the same as a security scan?

No. A quality scan finds maintainability and reliability issues (complexity, duplication, smells). A security scan (SAST) finds exploitable vulnerabilities like injection and insecure deserialization. They use different rule sets, and a clean quality report does not mean the code is secure.

What tools run code quality scans?

SonarQube and CodeClimate are common platform-level tools. Language-native linters like ESLint, Ruff, RuboCop, and Checkstyle handle per-language quality checks and often integrate into the same pipeline.

How do I stop a quality scan from generating too much noise?

Gate on new code rather than the whole codebase, tune the rule set to your stack, and surface findings as inline pull-request comments. This keeps the signal high and prevents developers from ignoring the scan.

Do I still need a dependency scanner if I run a quality scan?

Yes. Neither quality scans nor SAST reliably cover third-party code. Software composition analysis inventories your open source dependencies and matches them against known CVEs, including transitive ones, which is a separate and essential risk surface.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.