> ## Documentation Index
> Fetch the complete documentation index at: https://qodex.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Scanners

> OpenQodex has thirteen built-in scanners. Each one runs only when the change holds a file it reads. Every downloaded scanner is pinned to one version. OpenQodex never uses a copy of a built-in scanner from your PATH.

OpenQodex has thirteen built-in scanners. Each one runs only when the change holds a file it reads. Every downloaded scanner is pinned to one version. OpenQodex never uses a copy of a built-in scanner from your `PATH`.

Pinned versions do not make findings identical on every machine. semgrep fetches its registry rule packs at run time, and OpenQodex does not pin their version.

Every scanner reads the whole changed file. OpenQodex keeps only the findings on changed lines.

## Where scanners come from

Scanners download on first use into `~/.openqodex/tools/<scanner>/<version>/`. `OPENQODEX_HOME` moves that folder.

* GitHub release files are checked against the sha256 pinned in the package before they are unpacked.
* semgrep and bandit install from PyPI through uv, into one Python 3.11 that OpenQodex manages. uv comes from your `PATH` when present. Otherwise OpenQodex downloads a pinned uv.
* oxlint installs from npm, with the npm that ships beside your Node. Package install scripts are switched off.
* brakeman and rubocop install from RubyGems with your Ruby's `gem` command.

A scanner install that takes longer than 45 seconds keeps going in the background. The report lists that scanner as installing. The scanner joins the next run. `openqodex doctor --install` installs every scanner and waits.

Installed scanners take more disk than their downloads. The eight scanners the demo needs take about 700 MB of disk on an Apple Silicon Mac. semgrep with its Python takes about 440 MB of that.

OpenQodex does not install Ruby or Go. Without them, the report lists the scanners that need them as not installed, with the reason.

## Status in the report

The report lists every selected scanner with one status. A scanner left out with `--only` or `--skip` is not listed.

* `ran`: it ran.
* `no_matching_files`: the change holds no file it reads.
* `installing`: it is downloading for the first time. It joins the next run.
* `not_installed`: it could not be installed here. The reason says why.
* `failed`: it ran and broke. The reason holds its error.
* `disabled`: `scanners.disable` names it, or `--offline` skipped it.
* `untrusted`: a custom scanner you have not approved.

A scanner problem never changes the exit code.

## semgrep

* Version: 1.94.0.
* Runs when: any file changed.
* Needs: Python 3.11, which OpenQodex downloads through uv. About 86 MB with uv and bandit, measured on Apple Silicon.
* Rules: the registry packs `p/default`, `p/security-audit` and `p/secrets`.
* Sends: semgrep fetches those rule packs from the Semgrep registry on each run. It runs with its metrics switched off. The rules are never bundled in the OpenQodex package.
* `--offline` skips it. The report lists it as disabled.

## gitleaks

* Version: 8.21.2.
* Runs when: any file changed.
* Needs: nothing. 2.9 MB on Apple Silicon, 3.0 MB on Linux x64.
* Writes: links the changed files into a temporary folder outside the repo and scans that folder. It reads the repo's `.gitleaks.toml` or `gitleaks.toml` when present.
* gitleaks writes its raw report to a temporary file outside the repo. That file holds the matched secrets. OpenQodex deletes it when the run ends.
* Sends: nothing.
* Secrets it finds are redacted from the brief, every report file and the terminal. No file OpenQodex keeps holds the secret.

## bandit

* Version: 1.9.4.
* Runs when: a `.py` or `.pyi` file changed.
* Needs: the same Python as semgrep.
* Sends: nothing.

## ruff

* Version: 0.8.4.
* Runs when: a `.py` or `.pyi` file changed.
* Needs: nothing. 9.9 MB on Apple Silicon, 11.2 MB on Linux x64.
* Reads the repo's own ruff settings. It runs with fixes and its cache switched off, so it changes no file.
* Sends: nothing.

## oxlint

* Version: 1.71.0.
* Runs when: a `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts` or `.cts` file changed.
* Needs: npm, which ships with Node. About 7.3 MB on Apple Silicon, 8.2 MB on Linux x64.
* Uses OpenQodex's own settings. A config file in the repo is not loaded.
* Sends: nothing.

## osv-scanner

* Version: 1.9.2.
* Runs when: one of these lockfiles changed: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `Cargo.lock`, `go.mod`, `go.sum`, `requirements.txt`, `Pipfile.lock`, `poetry.lock`, `Gemfile.lock`, `composer.lock`, `pom.xml`, `gradle.lockfile`, `pubspec.lock`, `mix.lock`, `conan.lock`.
* Needs: network access to osv.dev. 31.8 MB on Apple Silicon, 32.1 MB on Linux x64.
* Sends: the names and versions of the dependencies in those lockfiles, to osv.dev. It never sends code.
* `--offline` skips it. The report lists it as disabled.

## actionlint

* Version: 1.7.7.
* Runs when: a `.yml` or `.yaml` file under `.github/workflows/` changed.
* Needs: nothing. 2.0 MB on Apple Silicon, 2.1 MB on Linux x64.
* Sends: nothing.

## hadolint

* Version: 2.15.1.
* Runs when: a Dockerfile changed: `Dockerfile`, `Dockerfile.<name>` or `<name>.dockerfile`.
* Needs: nothing. 102.6 MB on Apple Silicon, 55.7 MB on Linux x64.
* Sends: nothing.

## shellcheck

* Version: 0.10.0.
* Runs when: a `.sh` or `.bash` file changed.
* Needs: `xz` to unpack the download. 7.2 MB on Apple Silicon, 2.4 MB on Linux x64.
* Sends: nothing.

## golangci-lint

* Version: 2.12.2. Its name in `.openqodex.yaml` is `golangci`.
* Runs when: a `.go` file changed. It checks the packages that hold the changed files.
* Needs: Go on your `PATH`. 14.4 MB on Apple Silicon, 15.0 MB on Linux x64.
* Uses OpenQodex's own settings, with gosec switched on. A `.golangci.yml` in the repo is not loaded. It never rewrites `go.mod` or `go.sum`.
* golangci-lint 2.12.2 is built with Go 1.26. With a newer Go on your PATH it cannot check the code. The report then lists golangci as failed, with the reason.
* Runs with the Go module proxy off. The modules the repo needs must already be in your Go module cache. Nothing is downloaded.
* Sends: nothing.

## brakeman

* Version: 6.2.1.
* Runs when: a Ruby or Rails file changed, and the repo has a `Gemfile` and an `app/` folder. The files are `.rb`, `.rake`, `.gemspec`, `.erb`, `.haml`, `.slim`, `Gemfile`, `Rakefile` and `config.ru`.
* Needs: Ruby 2.7 or newer. It installs from RubyGems.
* Uses OpenQodex's own settings. The repo's brakeman config is not loaded.
* Sends: nothing.
* Licence: the Brakeman Public Use License, which is not an open source licence. OpenQodex does not bundle brakeman. It downloads brakeman at run time onto your machine. Read the licence before you use it, or switch it off with `scanners.disable: [brakeman]`.

## rubocop

* Version: 1.69.2, with rubocop-rails 2.28.0 and rubocop-performance 1.23.0.
* Runs when: a `.rb`, `.rake` or `.gemspec` file, a `Gemfile` or a `Rakefile` changed.
* Needs: Ruby 2.7 or newer. It installs from RubyGems.
* Uses OpenQodex's own settings. A `.rubocop.yml` in the repo is not loaded, because it can load Ruby code.
* Sends: nothing.

## sqllint

* Version: part of OpenQodex.
* Runs when: a `.sql` file changed.
* Needs: nothing. It runs inside OpenQodex and downloads nothing.
* Checks Postgres migrations for common mistakes, such as a privileged function left callable by every role.
* Sends: nothing.

## Choosing scanners

* `scanners.disable` in `.openqodex.yaml` switches built-in scanners off.
* `--only` and `--skip` on `scan` and `review` pick scanners for one run.
* [`custom-scanners`](/docs/openqodex/custom-scanners) explains how to add any other scanner.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.