What Is Code Scanning (CodeQL) in GitHub?
A thorough, zero-prior-knowledge walkthrough of how GitHub automatically searches your code for security vulnerabilities — before an attacker finds them first.
Imagine a building inspector who doesn’t just check whether a building looks finished, but actually walks through it asking, “if someone pushed on this door in exactly the wrong way, could they get in without a key? If this wiring got wet, would it start a fire?” That inspector isn’t looking for obvious, visible problems — they’re looking for specific, well-known patterns of weakness that experience has shown lead to real disasters. Code scanning with CodeQL plays exactly this role for software: instead of just checking that code runs, it actively searches for well-known patterns of security weakness hidden inside how the code is written. In this guide, we’ll build this idea up completely from zero — what code scanning is, why it exists, exactly how CodeQL manages to “understand” code well enough to spot these problems, and how teams use it effectively.
1Core Concepts: What Code Scanning Actually Is
What it is: Code scanning is a GitHub feature that automatically analyzes a project’s source code to find security vulnerabilities and coding errors, without anyone needing to actually run the software. CodeQL is the specific analysis engine GitHub built to power this — a tool that treats source code almost like a searchable database, letting it ask precise questions about how data moves through a program to uncover genuinely dangerous patterns.
Why it exists: Many serious security vulnerabilities aren’t obvious typos or crashes — they’re subtle patterns in how a program handles data, such as taking text directly from a user and passing it, unchecked, into a database command. A human reviewer, even a careful one, can easily miss these patterns buried across a large codebase, especially when the dangerous data has passed through several functions before reaching the risky point. Code scanning exists to catch exactly this category of subtle, easy-to-miss vulnerability automatically, every time code changes.
Where it’s used: Code scanning with CodeQL is used across projects handling anything sensitive — web applications processing user data, backend services handling authentication, and any software where a security flaw could have real consequences. It integrates directly into the same pull-request workflow covered in a related guide on code review, surfacing findings right where developers are already working.
Think of code scanning like a spell-checker, but for security instead of spelling — except instead of just checking each word in isolation, it understands grammar well enough to notice, “this sentence says the door is both locked and wide open at the same time,” a contradiction only visible once you understand how the pieces connect. CodeQL doesn’t just look at individual lines of code; it understands how data flows from one part of a program to another, which is exactly what lets it catch vulnerabilities that only become visible once you trace that full journey.
Practical example: A web application accepts a search term typed by a visitor and uses it to build a database query. If that search term is inserted directly into the query without being properly handled, a malicious visitor could type something crafted to manipulate the database itself — a well-known category of vulnerability called SQL injection. CodeQL can trace this exact path, from the point user input enters the program to the point it reaches the database command, and flag it automatically before the code is even merged.
2The Problem & Motivation Behind Code Scanning
For decades, a huge share of serious security breaches have traced back to the same handful of well-understood vulnerability patterns, appearing again and again across different projects, languages, and companies — not because developers are careless, but because these patterns are genuinely easy to introduce accidentally, especially in large, fast-moving codebases with many contributors. Manual code review, while valuable, is inherently limited by human attention and time: a reviewer skimming a large pull request can reasonably catch an obviously wrong calculation, but tracing exactly how one specific piece of untrusted data flows through a dozen intervening functions before reaching a dangerous operation is a very different, much more exhausting kind of checking.
CodeQL was developed specifically to make this kind of deep, pattern-based tracing automatic and repeatable. Rather than relying on any single person remembering every known vulnerability pattern and manually checking for each one in every review, CodeQL encodes that knowledge into reusable, precise queries that run consistently, every time, across the entire codebase.
CodeQL doesn’t just scan text for suspicious-looking keywords the way a simple find-and-replace search might. It builds a structured, database-like representation of what the code actually does — its logic, its data flow, its relationships between different parts — and then runs genuine queries against that representation, similar in spirit to how you’d query a database of information, but the “data” being queried is the code itself.
The core problem code scanning solves: well-known, dangerous coding patterns keep slipping into real software because reliably tracing them by hand, across a large and constantly changing codebase, exceeds what manual review alone can consistently catch. Automated, repeatable analysis closes that gap.
3How CodeQL “Understands” Code
CodeQL’s central idea is treating source code as data that can be queried, rather than just text to be read line by line. When CodeQL analyzes a codebase, it first builds what’s essentially a detailed database representing the code’s structure — every function, every variable, every place data is assigned, passed along, or used. This database captures not just what the code looks like, but how its pieces actually relate to and affect each other.
Security researchers and GitHub’s own team then write queries — precise, reusable questions — against that database, describing exactly what a specific vulnerability pattern looks like structurally. A query might, in effect, ask: “is there any path where data starts at a point where a user can control it, and ends at a database command, without passing through any step that properly checks or cleans it along the way?” Running that query against the database representation of a real codebase finds every place, if any, where that exact dangerous pattern actually occurs.
built from your project’s source
each targeting a known vulnerability pattern
supported by dedicated query sets
This approach is what allows CodeQL to catch vulnerabilities that depend on tracing data across multiple functions and files — something a simple, surface-level text search could never reliably do, since the dangerous pattern only becomes visible once you follow the full path data actually takes through the program.
4Internal Working: What Happens During a Scan
flowchart LR
A["Code scanning is triggered (push or pull request)"] --> B["CodeQL builds a database representing the code"]
B --> C["Security queries run against that database"]
C --> D{"Any dangerous patterns found?"}
D -->|No| E["Scan passes, no alerts raised"]
D -->|Yes| F["Alerts created, shown on the pull request or Security tab"]
F --> G["Developer reviews, fixes, or dismisses each alert"]
Step by step, in plain language: first, code scanning is triggered — commonly configured to run automatically on every push and every pull request, similar to how automated tests run, as covered in a related guide on GitHub Actions. Second, CodeQL analyzes the relevant source code and builds its structured database representation, capturing the code’s logic and data relationships. Third, a curated set of security queries — each targeting a specific, well-known vulnerability pattern — runs against that database. Fourth, if any query finds a genuine match, CodeQL creates an alert describing exactly what was found: the type of vulnerability, the specific file and line involved, and, crucially, the full path the dangerous data takes through the code, from where it enters to where it becomes risky. Finally, these alerts appear directly on the relevant pull request, alongside a dedicated Security tab for the whole repository, where a developer can review each one, fix the underlying issue, or — if the alert turns out not to genuinely apply in this specific case — formally dismiss it with a documented reason.
Because scans run automatically on every pull request, a vulnerability can be caught and fixed before the code is ever merged — meaning it may never actually reach a shared branch or a deployed environment at all, rather than being discovered later, after it’s already running somewhere real.
5Data Flow & Lifecycle of a Single Alert
A Risky Pattern Is Introduced
A developer, working on a new feature, writes code that passes user-supplied text into a sensitive operation without properly validating it first.
Pull Request Is Opened
The developer opens a pull request with this change, as covered in a related guide on code review, triggering the repository’s configured checks.
Code Scanning Runs Automatically
CodeQL analyzes the changed code as part of the pull request’s checks, exactly as described in Chapter 4.
An Alert Appears on the Pull Request
The dangerous data-flow pattern is detected, and an alert appears directly on the pull request, describing the issue and the exact path the data takes.
Developer Investigates and Fixes
The developer reviews the alert, confirms it’s a genuine issue, and updates the code to properly validate the input before it reaches the sensitive operation.
Re-Scan Confirms Resolution
Code scanning runs again automatically on the updated code, confirms the dangerous pattern is gone, and the alert is automatically closed.
Notice how this entire cycle happens before the change is ever merged into the shared branch — the vulnerability is caught, investigated, and fixed within the normal pull-request process, rather than requiring a separate, after-the-fact security review process.
6Code Scanning vs. Dependency Scanning vs. Secret Scanning
Code scanning (CodeQL), the subject of this guide, analyzes the code your own team actually wrote, looking for vulnerability patterns in your own logic. Dependency scanning (sometimes discussed alongside a feature called Dependabot) is different — it doesn’t analyze your code’s logic at all, but instead checks the external libraries and packages your project relies on against a database of already-known, publicly disclosed vulnerabilities in those specific libraries. Secret scanning is different again — it searches your code and commit history for accidentally committed sensitive values, like API keys or passwords, rather than analyzing logic or checking external libraries at all.
| Feature | What It Checks | Finds |
|---|---|---|
| Code Scanning (CodeQL) | Your own project’s source code and logic | Vulnerability patterns in how your code handles data |
| Dependency Scanning | External libraries your project depends on | Already-known vulnerabilities in those specific library versions |
| Secret Scanning | Code and commit history text content | Accidentally committed passwords, keys, and tokens |
Using only one of these three doesn’t cover the others. A project can have perfectly clean, well-written code according to CodeQL, while still relying on an outdated library with a serious, publicly known vulnerability — which only dependency scanning would catch. Comprehensive security coverage generally means using all three together, not choosing just one.
7Query Suites: Choosing How Deep to Scan
CodeQL’s queries are organized into groups called query suites, letting a team choose how broad or how deep their scanning should be. A smaller, default suite focuses on the most severe, highest-confidence vulnerability patterns — the ones most likely to represent a genuine, serious problem, with relatively few false alarms. Broader, more extensive suites include additional checks for less severe issues, or patterns that require more careful human judgment to confirm as genuinely problematic, at the cost of a higher volume of alerts to review.
Security-Extended (Common Default)
Balances catching genuinely serious, high-confidence vulnerabilities against keeping the number of alerts manageable for a typical team.
Security and Quality
Adds additional checks for general code-quality issues alongside security patterns, useful for teams wanting broader coverage beyond pure security.
Custom Query Packs
Organizations with unique, internal security concerns can write their own custom CodeQL queries, targeting patterns specific to their own codebase or industry.
Choosing the right suite is itself a trade-off worth understanding, covered more fully in Chapter 9 — broader scanning finds more, but also demands more reviewing time from the team to sort genuine findings from noise.
8Handling Alerts: Fixing, Dismissing, and Tracking
When code scanning finds a genuine issue, the expected response is usually to fix the underlying code so the dangerous data-flow pattern no longer exists — properly validating or cleaning risky input before it reaches a sensitive operation, for example. Once the fix is committed, the next scan automatically confirms the pattern is gone and closes the alert on its own, with no manual bookkeeping required.
Sometimes, though, an alert doesn’t represent a genuine, exploitable problem in its specific context — perhaps the “risky” data is actually already fully trusted for a documented reason specific to that code. For these cases, a developer can formally dismiss the alert, selecting a reason (such as “used in tests” or “false positive”) and optionally leaving a comment explaining the judgment call, creating a permanent, visible record of that decision for anyone who reviews the code later.
Tracking Security Debt Over Time
A team inheriting a large, older codebase runs code scanning for the first time and receives a substantial number of alerts spanning years of accumulated code. Rather than trying to fix everything at once, they use the Security tab to sort alerts by severity, tackling the most serious ones first while formally tracking the rest as known, prioritized work — turning an overwhelming list into a manageable, visible plan.
9Advantages, Disadvantages & Trade-offs
| Aspect | Advantage | Trade-off / Limitation |
|---|---|---|
| Consistency | Checks every single pull request the same way, every time, without fatigue | Only catches the specific patterns its queries are written to detect |
| Depth | Traces data flow across many functions and files, beyond what manual review typically covers | Can occasionally flag something that isn’t actually exploitable in its specific context (a false positive) |
| Timing | Catches issues before code is merged, often before it’s ever deployed | Doesn’t catch vulnerabilities in logic patterns it has no query for yet |
| Scalability | Scans an entire large codebase as easily as a small one | Broader query suites can generate a higher volume of alerts requiring human review time |
| Coverage | Complements manual code review with automated, tireless pattern detection | Doesn’t replace human judgment for design-level or business-logic security concerns |
The core trade-off to internalize: code scanning trades some reviewing effort — investigating and occasionally dismissing alerts — for consistent, automated coverage of well-known vulnerability patterns across an entire codebase, every single time it changes. It’s a powerful complement to human code review, not a full replacement for it.
10Security Considerations Around Code Scanning Itself
On a public repository, code scanning alerts can, depending on configuration and visibility settings, be visible to others — meaning an unfixed, publicly visible alert can act as a roadmap pointing directly at an exploitable weakness. Prioritizing genuine findings for a prompt fix, rather than letting them sit open, matters more on public, widely visible projects than almost anywhere else.
It’s worth remembering that dismissing an alert, as covered in Chapter 8, is a genuine security decision, not just a way to clear a notification — a dismissed alert that was actually a real, exploitable issue is just as dangerous as never having scanned at all, arguably more so, since it creates false confidence that the code was checked and cleared. Teams benefit from treating alert dismissal with the same care and, ideally, the same review process as any other security-relevant decision.
Finally, because code scanning is itself configured through files inside the repository — much like the workflow files covered in a related guide on GitHub Actions — changes to that configuration deserve the same review scrutiny as any other security-relevant change, since someone with the ability to quietly weaken or disable scanning could undermine the entire protection it provides without it being immediately obvious.
11Design Patterns & Anti-Patterns
The Every-Pull-Request Pattern runs code scanning automatically on every single pull request, exactly as covered in Chapter 4, catching issues at the earliest, cheapest possible point — before code is even merged.
The Prioritize-by-Severity Pattern treats scanning results as a triaged backlog rather than an all-or-nothing gate, tackling the most severe, highest-confidence findings first, especially valuable when adopting scanning on a large, pre-existing codebase with an initial backlog of alerts.
The Documented-Dismissal Pattern requires every dismissed alert to include a clear, written reason, creating an auditable trail that future reviewers — including the original developer’s future self — can understand and, if needed, revisit.
The Situation
Facing deadline pressure, a developer sees a code-scanning alert they assume is probably a false positive, and dismisses it quickly without actually tracing through the flagged data path to confirm.
Why This Becomes a Problem
An assumption isn’t verification. Some of the most dangerous vulnerabilities look, at a glance, exactly like the harmless patterns that really are false positives — the only way to tell the difference is to actually trace the specific path the tool identified.
The Better Pattern
Treat every alert as worth a genuine, if brief, investigation before dismissing it, and document the specific reasoning — not just “probably fine” — so the decision can be meaningfully reviewed later.
Common anti-patterns to avoid: the Alert Fatigue Spiral (enabling an overly broad query suite without the team capacity to review the resulting volume, leading to alerts being ignored wholesale); the Silent Dismissal (dismissing alerts without any documented reasoning, leaving no trail for later review); and the Scan-Only-Sometimes Pattern (running code scanning manually and occasionally rather than automatically on every pull request, reintroducing the exact gap in consistent coverage the tool exists to close).
12Best Practices & Common Mistakes
Enable It on Every Pull Request
Configure scanning to run automatically as part of the standard pull-request checks, not as a separate, occasional manual step.
Start With a Sensible Default Suite
Begin with a balanced query suite rather than the broadest possible option, to avoid overwhelming the team before scanning becomes a normal habit.
Investigate Before Dismissing
Actually trace the flagged data path before deciding an alert is a false positive, rather than assuming based on a quick glance.
Prioritize a Backlog by Severity
When adopting scanning on an existing codebase, tackle the most serious, highest-confidence alerts first rather than trying to fix everything at once.
Pair It With Dependency and Secret Scanning
Remember that code scanning alone doesn’t cover vulnerable dependencies or leaked credentials, as covered in Chapter 6 — use all three together for genuinely comprehensive coverage.
Common mistakes beginners make: assuming code scanning replaces the need for human code review, rather than complementing it; dismissing alerts based on a quick assumption instead of genuinely tracing the flagged path; and enabling the broadest possible query suite immediately, generating more alerts than the team has capacity to meaningfully review.
13Real-World & Industry Examples
Financial Technology Applications
A company building software that handles payment information runs code scanning on every pull request as a mandatory, blocking check, treating any high-severity alert as something that must be resolved before code can be merged, given how sensitive the data their software handles genuinely is.
Large Open-Source Frameworks
A widely used open-source framework, relied on by countless other projects, runs code scanning continuously, understanding that a vulnerability in their code could potentially affect every project built on top of it — making automated, consistent detection especially valuable at that scale of downstream impact.
Healthcare Software Platforms
A team building software that handles patient information uses code scanning alongside dependency and secret scanning together, as covered in Chapter 6, recognizing that comprehensive coverage — not just one type of automated check — is what genuinely reduces risk in an industry with strict data-protection requirements.
Small Teams Adopting Security Practices
A small startup with limited dedicated security expertise enables code scanning specifically because it encodes deep security knowledge automatically, giving a small team access to vulnerability-detection capability they couldn’t easily build or maintain manually on their own.
14Frequently Asked Questions
15Summary and Key Takeaways
What to Remember
- Code scanning with CodeQL automatically searches your own project’s source code for security vulnerability patterns, without needing to actually run the software.
- It exists because well-known vulnerability patterns are genuinely easy to introduce accidentally, and difficult to reliably catch through manual review alone across a large, changing codebase.
- CodeQL works by building a structured, database-like representation of code and running precise queries against it — letting it trace how data flows across many functions and files, not just individual lines.
- It’s distinct from dependency scanning (external libraries) and secret scanning (leaked credentials) — comprehensive coverage generally means using all three together.
- Alerts can be fixed, which closes them automatically on the next scan, or formally dismissed with a documented reason when they don’t represent a genuine issue in context.
- Dismissing an alert is a real security decision, not a housekeeping step — it deserves genuine investigation, not a quick assumption.
- Running scans automatically on every pull request catches issues at the earliest, cheapest point — often before the code is ever merged or deployed at all.