Packaging CodeQL queries for wider reuse
So you’ve found a security bug in your own code before anyone exploited it. You wrote a CodeQL query to catch similar instances, and you’ve deployed it to run on every pull request in your repository. Now the question is: how do you share that query so others can protect their codebases too?
The answer is to create a CodeQL pack and publish it to GitHub. CodeQL packaging is currently a beta feature, but it turns your security knowledge into something that is documented, executable, and easy to distribute. This first post in a two-part series walks through creating, publishing, and using a CodeQL query pack.
Why a query pack instead of a blog post
CodeQL’s customizability makes it a strong tool for vulnerability detection. Take the Exec call vulnerable to binary planting query as an example. It was written by the CodeQL team after discovering a real vulnerability in one of their open source repositories.
The query detects calls to the GitHub Action API ToolRunner where the argument hasn’t been sanitized by wrapping it in a call to safeWhich. It targets JavaScript code running inside GitHub Actions, a context where Windows binary planting attacks can inject a malicious executable through a pull request. The vulnerability is relatively difficult to attack, but the surface area is large across public repositories that run Actions on Windows.
A blog post warning about unqualified Windows executables is one option, but a query pack gives other developers something more useful: they can automatically scan their own code for the same pattern. The query is already protecting the repository where it was developed; publishing it as a pack lets anyone run the check in their own CI/CD pipeline.
Two kinds of CodeQL packs
If a query applies broadly across all repositories, the best home is the open source CodeQL query repository, where it runs on every pull request for any repository with code scanning enabled. Contributors can also earn a bounty in the process.
Most queries, however, are domain-specific. The binary planting query, for instance, only applies to GitHub Actions implemented in JavaScript. For those cases, a published pack is the right distribution channel. There are two types of CodeQL packs:
- Query packs contain pre-compiled queries that run directly on a CodeQL database.
- Library packs contain CodeQL libraries (
*.qllfiles) but no runnable queries. They serve as building blocks for query packs.
The rest of this post focuses on query packs.
Creating a query pack
Start with the CodeQL CLI installed and set up. Then create a qlpack.yml file, which declares the pack and its metadata. Any *.ql files in the same directory or subdirectories become part of the package.
Here’s the qlpack.yml used for the binary planting query:
name: aeisenberg/codeql-actions-queries
version: 1.0.1
dependencies:
codeql/javascript-all: ~0.0.10
Every pack needs a name property. For publication to the CodeQL registry, the name must include a scope — the part before the slash — which should be the GitHub username or organization that will own the package. Only users with proper privileges for that scope can publish to it. The name portion must be unique within the scope. A semantic version, following standard semver rules, is required for publishing.
The dependencies block lists the library packs this pack depends on, along with compatible version ranges. Each query pack must transitively depend on exactly one core language pack — such as JavaScript, C#, or Ruby — which determines the language the queries can analyze. In this example, the only dependency is codeql/javascript-all with a range of ~0.0.10, meaning any version from 0.0.10 up to (but not including) 0.1.0.
With the manifest in place, install dependencies by running codeql pack install in the pack’s root directory:
$ codeql pack install
Dependencies resolved. Installing packages...
Install location: /Users/andrew.eisenberg/.codeql/packages
Installed fresh codeql/[email protected]
Publishing to the registry
With dependencies resolved, publish the pack by running codeql pack publish from the same directory:
$ codeql pack publish
Running on packs: aeisenberg/codeql-actions-queries.
Bundling and then publishing qlpack located at '/Users/andrew.eisenberg/git-repos/codeql-actions-queries'.
Bundled qlpack created at '/var/folders/41/kxmfbgxj40dd2l_x63x9fw7c0000gn/T/codeql-docker17755193287422157173/.Docker Package Manager/codeql-actions-queries.1.0.1.tgz'.
Packaging> Package 'aeisenberg/codeql-actions-queries' will be published to registry 'https://ghcr.io/v2/' as 'aeisenberg/codeql-actions-queries'.
Packaging> Package 'aeisenberg/[email protected]' will be published locally to /Users/andrew.eisenberg/.codeql/packages/aeisenberg/codeql-actions-queries/1.0.1
Publish successful.
At the time of writing, packages are initially uploaded as private. To make yours public, navigate to the package page on GitHub, open package settings, scroll to the Danger Zone, and click Change visibility.
The published pack is then visible on the package’s GitHub page.
Running pack queries from the CLI
If you already have a CodeQL database, run queries from a published pack with the codeql database analyze command and the --download flag:
$ codeql database analyze --format=sarif-latest --output=out.sarif --download my-db aeisenberg/codeql-actions-queries@^1.0.1
The --download option tells CodeQL to fetch any packs not already cached. The version range ^1.0.0 is optional; without it, CodeQL uses the latest version. You can also list multiple packages, and the CLI runs all queries in their default query suites. To target a subset, append a colon and a path relative to the pack root:
aeisenberg/codeql-actions-queries@^1.0.1:binary-planting.ql
After the colon you can specify a single query, a directory of queries, or a query suite (.qls file).
Running pack queries in code scanning
To use a pack from GitHub code scanning, add a packs entry to the github/codeql-action/init step in your workflow:
- uses: github/codeql-action/init@v1
with:
packs:
- aeisenberg/[email protected]
languages: javascript
Note that the codeql-action does not yet support specifying a path after a colon, so you can only run the default query suite of a pack this way.
Workflow summary
Publishing a query pack involves four steps:
- Create the
qlpack.ymlfile. - Run
codeql pack installto fetch dependencies. - Write and test the queries.
- Run
codeql pack publishto share the package in the GitHub Container Registry.
On the consuming side, there are two paths:
- CLI:
codeql database analyze --download path/to/my-db aeisenberg/[email protected] - Code scanning: Add a config-file input to the
github/codeql-action/initaction and include a packs block in that config file.
The CodeQL team has published all of its standard queries as query packs and core libraries as library packs. Packs named {*}-queries contain runnable queries; packs named {*}-all are library packs used as building blocks. When creating your own query packs, add the appropriate language library pack as a dependency.
Part two of this series covers the implementation details and design decisions behind CodeQL packaging.



