CodeQL
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 <querydir|queryfile>: custom queries directory or single .ql file
-r <results_dir>: where to store DB and SARIF (default: .codeql/)
-t: run unit tests instead of analysis (use with -q, no -s needed)
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)
// $ Alert on a line means the query should flag that line
- Lines without
// $ Alert should not be flagged
.expected file 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: GITHUBTOKEN env var with securityevents 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
.ql file in .github/codeql/custom-queries/<category>/:
- Use @id js/kibana/<descriptive-id> (must be unique) - Include @kind problem (or path-problem for taint tracking) - Set @problem.severity and @security-severity - Import javascript module - Refer to existing queries like UnboundedArrayInRoute.ql for patterns
- Create a unit test directory
<category>/<RuleName>/:
- <RuleName>.qlref containing <category>/<RuleName>.ql - test.js with annotated test cases (// $ Alert for expected hits) - Run tests to generate .expected — verify it matches expectations
- Add a
.qhelp (XML) and/or .md for documentation
- Optionally add a
.qls query suite if grouping multiple queries
- Test locally with
quick_check.sh against 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