GitHub CodeQL vs Nark: How Security and Correctness Scanning Complement Each Other
By Nark Team
GitHub CodeQL and Nark are both static analysis tools that run on your TypeScript codebase, but they answer different questions. CodeQL asks: can an attacker exploit this code? It finds SQL injection, code injection, path traversal, XSS, and other CVE-class vulnerabilities — the GitHub Security Lab has used it to find 447+ disclosed vulnerabilities across open source projects. Nark asks a different question: will this code crash in production because a package call wasn't error-handled correctly? It scans against 165+ Nark Profiles built from npm changelogs, CVEs, and package documentation. The two tools share a workflow slot (CI static analysis) but cover non-overlapping problem classes.
Quick Answer: Use GitHub CodeQL for security vulnerabilities (injection, XSS, SSRF, code execution). Use Nark for dependency error handling correctness (unhandled
AxiosError, missingPrismaClientKnownRequestErrorcatches, leaked database connections). Run both in CI:npx nark --tsconfig ./tsconfig.jsonalongside CodeQL'sgithub/codeql-action.
What Does GitHub CodeQL Actually Check?
CodeQL is a semantic code analysis engine. GitHub describes it as treating code as data — you write queries (in QL, a Datalog-like language) that traverse a database representation of your source. The default query packs ship hundreds of rules for security-class problems.
The most common findings on the CodeQL Wall of Fame and in the GitHub Security Lab's published advisories:
- Code injection in GitHub Actions workflows (e.g. GHSL-2025-093 in PraisonAI, GHSL-2025-105 in vets-api, GHSL-2025-102/103 in acl-anthology)
- SQL injection in unparameterized queries
- Path traversal in file handlers
- Server-side request forgery (SSRF) when user input reaches HTTP clients
- Reflected and stored XSS in web frameworks
- Hardcoded credentials and secret leakage
- Unsafe deserialization of untrusted input
- Tainted shell commands (command injection)
These are vulnerabilities. They exist because untrusted input flows to a sensitive sink without sanitization. CodeQL's killer feature is taint tracking: it follows data from sources (HTTP request body, env vars, file contents) to sinks (database queries, shell commands, eval) and reports paths where the flow is unsafe.
// CodeQL flags this: tainted user input flowing to an unsafe sink
import { exec } from 'child_process';
app.post('/run', (req, res) => {
const command = req.body.command;
// CodeQL: Code injection — user input reaches child_process.exec
exec(command, (err, stdout) => {
res.send(stdout);
});
});
That's a CVE waiting to happen. CodeQL is the right tool to catch it.
What Does Nark Actually Check?
Nark is a deterministic scanner that walks your TypeScript AST and matches package call sites against Profiles. A Nark Profile encodes the throwing behavior, error types, and required handling for a specific npm package, distilled from the official docs, changelog, and CVE history.
Nark answers questions like:
- Did you wrap
axios.get()in a try-catch, or does anAxiosErrorpropagate unhandled? - Did you catch
PrismaClientKnownRequestErrorfor unique constraint violations (P2002)? - Did you handle
StripeCardErrorseparately fromStripeAPIError? - Did you register an
errorevent listener on a Redis client? - Did you close the
pgconnection in afinallyblock?
These are correctness violations. The code compiles. There is no security vulnerability. But under the wrong runtime conditions — a 5xx response, a duplicate email, a declined card, a network blip — it crashes the request, leaks a connection, or returns the wrong status to the user.
// Nark flags this: axios.get() called without try-catch
import axios from 'axios';
async function fetchUser(id: string) {
// Nark: ERROR axios — axios.get() called without try-catch
// axios Profile postcondition: throws AxiosError on 4xx/5xx/network failure
const response = await axios.get(`/api/users/${id}`);
return response.data;
}
That code passes ESLint, passes TypeScript strict, passes CodeQL. It still crashes when the API returns 503.
CodeQL vs Nark: A Side-by-Side Comparison
| Dimension | GitHub CodeQL | Nark |
|---|---|---|
| Question answered | Can an attacker exploit this? | Will this crash in production from unhandled package errors? |
| Problem class | Security vulnerabilities (CVEs) | Correctness violations (unhandled errors, resource leaks) |
| Detection method | Taint tracking, dataflow analysis | AST pattern matching against Nark Profiles |
| Knowledge base | CWE catalog, security research | npm changelogs, package docs, CVE history per package |
| Languages | C/C++, C#, Go, Java/Kotlin, JavaScript/TypeScript, Python, Ruby, Swift | TypeScript |
| Setup | github/codeql-action workflow | npx nark --tsconfig ./tsconfig.json |
| Cost | Free for public repos, paid for private (GHAS) | Free CLI; paid SaaS for dashboards |
| False positive risk | Higher — taint paths can over-approximate | Lower — Profile postconditions are explicit per package |
| Output | SARIF uploaded to Security tab | CLI text or JSON; optional SARIF |
| Wins on | Injection, XSS, SSRF, path traversal, deserialization | Unhandled errors per npm package, resource cleanup, API misuse |
| Misses | Logic bugs, missing error handling, API contract violations | Security vulnerabilities, taint flow, exploitable paths |
The two tools rarely disagree because they look at different things. CodeQL traces data; Nark matches API surface against documented behavior. A clean CodeQL report tells you the code isn't exploitable. A clean Nark report tells you the code handles every documented error case for every package call.
Does CodeQL Catch Unhandled Promise Rejections in TypeScript?
CodeQL has rules for some unhandled-error patterns, but they are not its strength. The default JavaScript/TypeScript query pack focuses on security. Here are the rules CodeQL ships and what each one catches:
js/sql-injection— User input concatenated into a SQL query without parameterizationjs/code-injection— User input passed toeval,Function, ornew Functionjs/path-injection— User-controlled paths reachingfs.readFile,fs.writeFile, or similarjs/xss— Unescaped user input rendered into HTML or ReactdangerouslySetInnerHTMLjs/server-side-unvalidated-url-redirection— Redirects to attacker-controlled URLsjs/clear-text-storage-of-sensitive-information— Secrets written to logs, cookies, or localStoragejs/incomplete-url-substring-sanitization— URL allowlist checks that can be bypassedjs/prototype-pollution— Object key assignment from untrusted inputjs/regex-injection— Regex constructed from user input (potential ReDoS)js/zip-slip— Unsafe extraction of archive paths
There is no rule that says "every axios.get() call must be inside a try-catch." There is no rule that says "you must catch PrismaClientKnownRequestError when calling prisma.user.create()." These rules don't exist because they require per-package knowledge — knowing exactly which methods throw, what they throw, and when. CodeQL's taint engine doesn't carry that knowledge.
That gap is what Nark fills. The axios Profile in nark-corpus encodes 21 postconditions. The Prisma Profile encodes the full set of PrismaClientKnownRequestError codes (P2002, P2025, etc.). The Stripe Profile distinguishes StripeCardError, StripeRateLimitError, StripeInvalidRequestError, and StripeAPIError. These are the rules CodeQL doesn't ship.
Does Nark Catch SQL Injection or XSS?
No. Nark does not analyze data flow. It does not classify input as tainted or trusted. It does not look at sanitization. If your code passes user input directly into a SQL query, Nark will not flag it — it has no concept of "user input." If you write raw HTML strings with unescaped values, Nark will not flag it.
Nark's job is correctness against documented package behavior. CodeQL's job is security against attacker-controlled input. Asking Nark to catch SQL injection is like asking a spell-checker to catch logic errors — wrong tool, wrong problem class.
This is why the two are complementary rather than competing. They occupy adjacent slots in the static-analysis stack:
Static Analysis Coverage Map (TypeScript)
├── Type errors → TypeScript strict mode
├── Style / floating promises → ESLint
├── Security vulnerabilities → GitHub CodeQL
├── Package-specific errors → Nark
└── Logic / architecture → Code review (human or AI)
Each layer catches what the layer above misses. None alone is enough.
How to Run CodeQL and Nark Together in GitHub Actions
The cleanest setup runs both in parallel jobs. CodeQL uploads SARIF to the Security tab; Nark fails the build on correctness violations.
# .github/workflows/static-analysis.yml
name: Static Analysis
on:
pull_request:
push:
branches: [main]
jobs:
codeql:
name: CodeQL Security Scan
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript-typescript
- uses: github/codeql-action/analyze@v3
nark:
name: Nark Correctness Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx nark ci --tsconfig ./tsconfig.json
CodeQL runs on every push and PR. Findings appear in the Security → Code scanning alerts tab. Nark runs the same way; correctness violations fail the build. Two jobs, two outputs, complete coverage.
For private repos that need SARIF unification, Nark can emit SARIF too:
npx nark --tsconfig ./tsconfig.json --format sarif > nark.sarif
Upload that to the same Security tab using github/codeql-action/upload-sarif@v3. CodeQL findings and Nark findings then appear in one dashboard, distinguished by tool name.
When Should I Add CodeQL? When Should I Add Nark?
The honest answer depends on the threat model.
Add CodeQL first if:
- You handle untrusted user input (most web apps do)
- You use
child_process,eval, raw SQL, or shell commands - You have GitHub Actions workflows that consume PR titles, issue bodies, or other attacker-controlled strings
- You are subject to compliance (SOC 2, ISO 27001) that expects a security scanner
- Your code is open source and you want to qualify for advisories
Add Nark first if:
- Your production incidents are mostly "an unhandled error from package X crashed the request"
- You ship features fast and don't have time to read every npm package's changelog
- You use AI assistants to generate code (they consistently skip error handling — see Why AI-Generated Code Skips Error Handling)
- You depend on packages with rich error hierarchies (Stripe, Prisma, axios, OpenAI, Twilio)
- You need consistent, deterministic checking across dozens of integrations or microservices
In practice, mature teams run both. They cover different incident classes. CodeQL prevents the vulnerability that ends up in a CVE. Nark prevents the unhandled error that ends up in a 3 AM PagerDuty.
Real Example: A Repo That Needs Both
Consider a Next.js app that accepts file uploads, calls Stripe to charge users, and stores results in Postgres via Prisma.
CodeQL would flag:
- An unsanitized filename concatenated into a shell command (path traversal / command injection)
- A user-supplied URL passed to
fetchwithout allowlisting (SSRF) - A reflected query parameter rendered into HTML without escaping (XSS)
Nark would flag:
stripe.paymentIntents.create()without a try-catch (throwsStripeCardError,StripeRateLimitError,StripeAPIError)prisma.order.create()without handlingPrismaClientKnownRequestErrorfor codeP2002(duplicate order ID)axios.post()to a webhook URL without retry logic (throwsAxiosErroron 5xx)- A
pg.Poolopened without a correspondingpool.end()on shutdown (connection leak)
Neither tool's findings overlap. Fixing CodeQL's findings doesn't address Nark's, and vice versa. A team that runs only one of them ships with a known gap.
How Are CodeQL and Nark Licensed? Both Are Free for Open Source, Paid for Private Commercial Use
Both tools split their codebase into two pieces under different licenses — and both end up at the same practical place: free to run on open source code, paid to run on private commercial code at scale. The specific license slots are mirror images, but the developer-facing model converges.
CodeQL's licensing:
- The queries and libraries in the github/codeql repository are licensed under MIT. Anyone can read, fork, modify, redistribute, or write new queries — including for commercial use. The query language (QL) and the shared library code are fully open source.
- The CodeQL engine and CLI are proprietary. GitHub permits free use of the engine on codebases that are OSI-approved open source, for academic research, or for automated analysis (CI/CD) on open source repositories hosted on GitHub.com. Running CodeQL on a private or proprietary codebase requires a paid GitHub Advanced Security / Code Security license.
Nark's licensing:
- The Nark engine (the CLI, AST analyzer, and SARIF emitter) is licensed under AGPL-3.0. The full source is on GitHub. Anyone can read it, fork it, or modify it — the copyleft just requires that distributed modifications and network-hosted derivatives remain open under the same license.
- The nark-corpus — the Profile library of 165+ npm package specifications built from changelogs, package documentation, and CVE history — is licensed under CC-BY-NC-4.0 (Creative Commons Attribution-NonCommercial 4.0). It is free to use for non-commercial code, academic research, and open source projects with attribution. Commercial use of the Profile library against private proprietary codebases at scale requires a paid plan via nark.sh.
| Layer | GitHub CodeQL | Nark |
|---|---|---|
| Engine / runtime | Proprietary (GitHub) — free for OSS, paid for private | AGPL-3.0 — fully open source |
| Rules / Profiles | MIT — fully open source | CC-BY-NC-4.0 — free for non-commercial |
| Free use | OSI-approved open source, academic research, CI on public GitHub repos | Open source projects, non-commercial code, academic research |
| Paid use | Private commercial repos via GitHub Advanced Security | Private commercial repos via nark.sh |
| Where the "moat" lives | The query engine | The Profile knowledge base |
The structures are mirror images. CodeQL keeps the engine proprietary and opens the queries; Nark opens the engine and licenses the Profile library for non-commercial use. The reasoning is similar in both cases: the knowledge base is what takes ongoing investment to maintain. GitHub funds the security research that produces CodeQL queries and the engine that runs them. Nark funds the per-package analysis that distills changelogs, docs, and CVE history into Profiles. Both projects need a way to fund that ongoing knowledge work, so both leave the open source world unconditionally free and ask commercial users to pay.
For a developer choosing whether to adopt either or both, the practical answer is clean:
- Working on an open source TypeScript repo on GitHub? Run CodeQL via
github/codeql-actionand Nark vianpx nark. Both are free. - Working on an academic or non-commercial project? Same — both tools are free.
- Working on a private commercial codebase? You need GitHub Advanced Security (now Code Security) to run CodeQL on the private repo, and a nark.sh plan to run the Profile library against your private code at scale.
This shared philosophy is one of the reasons the two tools sit comfortably in the same CI stack. Neither project is undercutting the other on price — they each fund a different knowledge investment, and they each leave the other's slot in the stack untouched. The licensing models are not identical at the legal layer, but they reach the same outcome for users: free for the open source world, sustainable funding from commercial users.
Best TypeScript Static Analysis Stack for Security and Error Handling (2026)
The strongest static-analysis stack for a TypeScript project in 2026 covers four layers, each with a different responsibility. Skipping any one layer ships a known gap.
| Layer | Tool | Catches | Misses |
|---|---|---|---|
| Type errors | TypeScript strict mode (tsc --strict) | Wrong argument types, null safety, type narrowing bugs | Runtime throwing, security flow |
| Style and floating promises | ESLint (@typescript-eslint) | Floating promises, no-throw-literal, no-misused-promises, formatting | Per-package error contracts, security vulnerabilities |
| Security vulnerabilities | GitHub CodeQL | SQL injection, XSS, SSRF, code injection, path traversal, prototype pollution | Unhandled package errors, resource leaks |
| Package error correctness | Nark | Missing try-catch around axios.get(), uncaught PrismaClientKnownRequestError, leaked pg.Pool connections | Security flow, logic bugs |
Recommended setup, in order of return on effort:
- TypeScript strict mode. Already in
tsconfig.jsonfor most modern projects; the floor for any TypeScript codebase. - ESLint with
@typescript-eslint/no-floating-promises. Catches the most common async error class. ~5 minutes to enable. - Nark.
npx nark --tsconfig ./tsconfig.jsonruns in seconds and surfaces the per-package error gaps no other tool checks. - GitHub CodeQL. One-click enable on public repos via
github/codeql-action. Free for open source.
This is the stack we recommend. None of these tools replaces another. Each catches a specific failure class. Together they cover type, style, security, and correctness — the four axes of a TypeScript codebase that ships without surprises.
Frequently Asked Questions
Is Nark a replacement for CodeQL?
No. Nark and CodeQL solve different problems. CodeQL is a security scanner; Nark is a correctness scanner. They share the static-analysis-in-CI slot but check different rules. Run both.
Can CodeQL be configured to check error handling?
CodeQL is extensible — you can write custom QL queries for almost anything, including unhandled promises or missing try-catch around specific calls. In practice this is rare because (1) writing per-package QL queries is tedious and (2) you would need to maintain that knowledge yourself as packages release new error types. Nark ships 165+ Profiles maintained centrally in nark-corpus.
Does GitHub Advanced Security (GHAS) include Nark?
No. GHAS bundles CodeQL, secret scanning, and dependency review (Dependabot). It does not include package-correctness scanning. Nark is a separate tool that runs alongside GHAS. The Nark CLI is free; the SaaS dashboard at app.nark.sh is paid for private repositories.
Will Nark and CodeQL flag the same issue twice?
Almost never. CodeQL flags injection, XSS, and tainted-flow issues; Nark flags missing error handling and resource cleanup. The Venn diagram has near-zero overlap. A line of code rarely violates both at once.
What about Snyk, Semgrep, or SonarQube?
- Snyk focuses on supply-chain vulnerabilities (does your
package.jsonpull in a known-vulnerable version?). Closer to Dependabot than CodeQL. - Semgrep is pattern-matching across many languages. You can write Semgrep rules for security or correctness, but it doesn't ship a curated package-error knowledge base. Custom rules require maintenance.
- SonarQube flags code smells and reliability hotspots but is generic; it doesn't know that
axios.get()throwsAxiosError.
CodeQL is the depth tool for security. Nark is the depth tool for package error correctness. The other tools sit on a spectrum between them.
Does Nark slow down CI?
Nark is fast — it walks the AST once per scan. On a typical Next.js project (200-500 source files) it finishes in 2-10 seconds. CodeQL is heavier (build step + analysis) and typically takes 3-10 minutes. Run them as parallel jobs and Nark adds no wall-clock time.
Are CodeQL and Nark both free to use?
If you are scanning open source code, yes — both are free. CodeQL is free on any OSI-approved open source codebase, for academic research, and for automated CI analysis on public GitHub repositories. The Nark CLI is open source under AGPL-3.0 and the Profile library at nark-corpus is free for non-commercial use under CC-BY-NC-4.0. If you are scanning a private commercial codebase, both tools require a paid plan: GitHub Advanced Security (now Code Security) for CodeQL, and a nark.sh plan for Nark. The pricing models are different, but the free-vs-paid line lands in the same place.
Can I redistribute or modify CodeQL queries and Nark Profiles?
CodeQL queries in the github/codeql repository are MIT-licensed — you can fork them, modify them, and redistribute them, including in commercial products, with attribution. Nark Profiles in nark-corpus are CC-BY-NC-4.0-licensed — you can read, modify, and redistribute them for non-commercial purposes with attribution. Commercial redistribution of Profiles requires a license from nark.sh. The CodeQL engine is closed source. The Nark engine is open source under AGPL-3.0 — you can fork the scanner itself, but distributed modifications and network-hosted derivatives must remain open under AGPL.
Is Nark's license the same as CodeQL's?
No, the specific licenses are different, but the effect for developers is the same. CodeQL uses MIT for its rules and proprietary licensing for its engine; Nark uses AGPL-3.0 for its engine and CC-BY-NC-4.0 for its Profile library. Both projects landed on a dual-license structure where one component is fully open and the other carries commercial restrictions. Both projects are free for open source and non-commercial use. Both projects fund themselves via paid plans for private commercial use — GitHub Advanced Security for CodeQL, nark.sh for Nark. The licensing philosophies converge even though the legal mechanisms differ.
Try It Now
npx nark --tsconfig ./tsconfig.json
Nark checks 165+ npm packages for correct error handling, resource cleanup, and API contract violations. It runs in seconds, requires no configuration, and complements GitHub CodeQL — security scanning catches the exploit, correctness scanning catches the crash.
For the GitHub Actions config above, copy it directly into .github/workflows/static-analysis.yml. CodeQL handles the security layer; Nark handles the correctness layer. Two jobs, full coverage.