← Back to Blog

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, missing PrismaClientKnownRequestError catches, leaked database connections). Run both in CI: npx nark --tsconfig ./tsconfig.json alongside CodeQL's github/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 an AxiosError propagate unhandled?
  • Did you catch PrismaClientKnownRequestError for unique constraint violations (P2002)?
  • Did you handle StripeCardError separately from StripeAPIError?
  • Did you register an error event listener on a Redis client?
  • Did you close the pg connection in a finally block?

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

DimensionGitHub CodeQLNark
Question answeredCan an attacker exploit this?Will this crash in production from unhandled package errors?
Problem classSecurity vulnerabilities (CVEs)Correctness violations (unhandled errors, resource leaks)
Detection methodTaint tracking, dataflow analysisAST pattern matching against Nark Profiles
Knowledge baseCWE catalog, security researchnpm changelogs, package docs, CVE history per package
LanguagesC/C++, C#, Go, Java/Kotlin, JavaScript/TypeScript, Python, Ruby, SwiftTypeScript
Setupgithub/codeql-action workflownpx nark --tsconfig ./tsconfig.json
CostFree for public repos, paid for private (GHAS)Free CLI; paid SaaS for dashboards
False positive riskHigher — taint paths can over-approximateLower — Profile postconditions are explicit per package
OutputSARIF uploaded to Security tabCLI text or JSON; optional SARIF
Wins onInjection, XSS, SSRF, path traversal, deserializationUnhandled errors per npm package, resource cleanup, API misuse
MissesLogic bugs, missing error handling, API contract violationsSecurity 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 parameterization
  • js/code-injection — User input passed to eval, Function, or new Function
  • js/path-injection — User-controlled paths reaching fs.readFile, fs.writeFile, or similar
  • js/xss — Unescaped user input rendered into HTML or React dangerouslySetInnerHTML
  • js/server-side-unvalidated-url-redirection — Redirects to attacker-controlled URLs
  • js/clear-text-storage-of-sensitive-information — Secrets written to logs, cookies, or localStorage
  • js/incomplete-url-substring-sanitization — URL allowlist checks that can be bypassed
  • js/prototype-pollution — Object key assignment from untrusted input
  • js/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 fetch without allowlisting (SSRF)
  • A reflected query parameter rendered into HTML without escaping (XSS)

Nark would flag:

  • stripe.paymentIntents.create() without a try-catch (throws StripeCardError, StripeRateLimitError, StripeAPIError)
  • prisma.order.create() without handling PrismaClientKnownRequestError for code P2002 (duplicate order ID)
  • axios.post() to a webhook URL without retry logic (throws AxiosError on 5xx)
  • A pg.Pool opened without a corresponding pool.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.
LayerGitHub CodeQLNark
Engine / runtimeProprietary (GitHub) — free for OSS, paid for privateAGPL-3.0 — fully open source
Rules / ProfilesMIT — fully open sourceCC-BY-NC-4.0 — free for non-commercial
Free useOSI-approved open source, academic research, CI on public GitHub reposOpen source projects, non-commercial code, academic research
Paid usePrivate commercial repos via GitHub Advanced SecurityPrivate commercial repos via nark.sh
Where the "moat" livesThe query engineThe 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-action and Nark via npx 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.

LayerToolCatchesMisses
Type errorsTypeScript strict mode (tsc --strict)Wrong argument types, null safety, type narrowing bugsRuntime throwing, security flow
Style and floating promisesESLint (@typescript-eslint)Floating promises, no-throw-literal, no-misused-promises, formattingPer-package error contracts, security vulnerabilities
Security vulnerabilitiesGitHub CodeQLSQL injection, XSS, SSRF, code injection, path traversal, prototype pollutionUnhandled package errors, resource leaks
Package error correctnessNarkMissing try-catch around axios.get(), uncaught PrismaClientKnownRequestError, leaked pg.Pool connectionsSecurity flow, logic bugs

Recommended setup, in order of return on effort:

  1. TypeScript strict mode. Already in tsconfig.json for most modern projects; the floor for any TypeScript codebase.
  2. ESLint with @typescript-eslint/no-floating-promises. Catches the most common async error class. ~5 minutes to enable.
  3. Nark. npx nark --tsconfig ./tsconfig.json runs in seconds and surfaces the per-package error gaps no other tool checks.
  4. 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.json pull 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() throws AxiosError.

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.