Project Layout
.github/codeql/
├── codeql-config.yml # Main config (paths-ignore, packs, query-filters)
├── custom-queries/
│ ├── qlpack.yml # QL pack definition (name: kibana-custom-queries)
│ ├── codeql-pack.lock.yml
│ ├── suppression/ # Alert suppression logic
│ │ ├── AlertSuppression.ql
│ │ └── AlertSuppression.qll
│ └── <category>/ # e.g. dos/, xss/
│ ├── <RuleName>.ql # Query file
│ ├── <RuleName>.qhelp # Help docs (XML)
│ ├── <RuleName>.md # Human-readable docs
│ ├── <category>-security.qls # Query suite
│ └── <RuleName>/ # Unit test directory
│ ├── <RuleName>.qlref # Points to the .ql file (relative to qlpack root)
│ ├── <RuleName>.expected # Expected test output
│ └── test.js # Test source code
scripts/codeql/
├── quick_check.sh # Local analysis via Docker
└── codeql.dockerfile # Docker image (ubuntu + CodeQL CLI)
Running Queries Locally (Full Analysis)
Uses Docker to create a CodeQL database and run queries against real source code.
# Analyze a source directory with custom queries
bash scripts/codeql/quick_check.sh -s <source_dir> -q .github/codeql/custom-queries
# Analyze with a single query file
bash scripts/codeql/quick_check.sh -s <source_dir> -q .github/codeql/custom-queries/dos/UnboundedArrayInRoute.ql
# Custom results directory
bash scripts/codeql/quick_check.sh -s <source_dir> -r .codeql-results -q .github/codeql/custom-queries
Options:
-s <source_dir>(required for analysis): directory to scan-q <query_dir|query_file>: custom queries directory or single.qlfile-r <results_dir>: where to store DB and SARIF (default:.codeql/)-t: run unit tests instead of analysis (use with-q, no-sneeded)
Output: SARIF file at <results_dir>/database/results.sarif. If jq is installed, a colored summary prints automatically.
First run builds a Docker image (codeql-env) from scripts/codeql/codeql.dockerfile. On Apple Silicon, it runs with --platform linux/amd64 (emulation).
Running CodeQL Unit Tests
Unit tests validate that a query flags the correct lines. Each test lives in a subdirectory named after the query.
Test structure
<category>/<RuleName>/
├── <RuleName>.qlref # Reference: "category/RuleName.ql"
├── test.js # Source code with `// $ Alert` annotations
└── <RuleName>.expected # Expected output (auto-generated or hand-written)
// $ Alerton a line means the query should flag that line- Lines without
// $ Alertshould not be flagged .expectedfile has pipe-delimited format:| <location> | <message> |
Running tests via Docker
Uses the same codeql-env Docker image built by quick_check.sh (built automatically on first run).
# Run a specific test directory
bash scripts/codeql/quick_check.sh -t -q .github/codeql/custom-queries/dos/UnboundedArrayInRoute
# Run all tests in the qlpack
bash scripts/codeql/quick_check.sh -t -q .github/codeql/custom-queries
CI workflow
The codeql-pr.yml workflow automatically runs unit tests on PR. It finds all *.qlref directories and runs codeql test run against them.
Fetching Remote SARIF / Scan Results
The scripts/fetch_sarif.mjs script (relative to this skill directory) fetches CodeQL SARIF results and alerts from GitHub for a PR or branch.
# By PR number
GITHUB_TOKEN=ghp_xxx node .agents/skills/codeql/scripts/fetch_sarif.mjs 252121
# By full ref
GITHUB_TOKEN=ghp_xxx node .agents/skills/codeql/scripts/fetch_sarif.mjs refs/heads/main
Requires: GITHUB_TOKEN env var with security_events scope. Depends on @octokit/rest (already in Kibana deps).
What it does:
- Lists recent CodeQL analyses for the ref
- Fetches full SARIF JSON (with rule severity cross-referencing)
- Prints formatted results (rule, severity, message, file:line)
- Fetches code scanning alerts for the same ref
Writing a New Query
-
Create the
.qlfile in.github/codeql/custom-queries/<category>/:- Use
@id js/kibana/<descriptive-id>(must be unique) - Include
@kind problem(orpath-problemfor taint tracking) - Set
@problem.severityand@security-severity - Import
javascriptmodule - Refer to existing queries like
UnboundedArrayInRoute.qlfor patterns
- Use
-
Create a unit test directory
<category>/<RuleName>/:<RuleName>.qlrefcontaining<category>/<RuleName>.qltest.jswith annotated test cases (// $ Alertfor expected hits)- Run tests to generate
.expected— verify it matches expectations
-
Add a
.qhelp(XML) and/or.mdfor documentation -
Optionally add a
.qlsquery suite if grouping multiple queries -
Test locally with
quick_check.shagainst real Kibana source code
Inline Suppressions
Suppressions use the format // codeql[rule-id] justification text. Every suppression must include a specific justification explaining why it is safe.
Valid:
// codeql[js/path-injection] User input is validated against an allowlist before use
return fs.readFileSync(`/etc/${validatedPath}`, 'utf8');
Invalid — flag these:
- Missing justification:
// codeql[js/path-injection]with no explanation - Generic justification:
"false positive","safe","not a vulnerability"— says nothing about the actual mitigation - Incomplete justification:
"sanitized"— does not explain how or by what mechanism
Good justifications describe the concrete security mechanism: allowlist validation, DOMPurify escaping, shell-quote library, test-only code, etc.
Troubleshooting
| Issue | Fix |
|---|---|
| Docker build fails on ARM | Ensure --platform linux/amd64 is set (script handles automatically) |
qlpack.yml not found |
The script walks up from the .ql file to find it — ensure qlpack.yml exists at custom-queries/ root |
Test produces .actual file |
Diff .actual vs .expected — .actual files are gitignored |
| Query finds nothing | Check codeql-config.yml paths-ignore — test/mock dirs are excluded |
jq not found for summary |
Install jq: brew install jq |
References
- Writing CodeQL queries — official guide covering query structure, QL tutorials, and running queries