This guide explains how to run CodeQL queries located in actions/ql/src/custom/ to detect security vulnerabilities in GitHub Actions workflows.
- CodeQL CLI installed
- CodeQL repository cloned:
https://github.com/github/codeql.git - A GitHub repository with
.github/workflows/directory to analyze - Python 3.8+ (for result processing)
macOS (via Homebrew):
brew install codeqlLinux (Ubuntu/Debian):
# Download the latest release
wget https://github.com/github/codeql-cli-releases/releases/download/v2.14.6/codeql-linux64.zip
# Or use a different version from:
# https://github.com/github/codeql-cli-releases/releases
# Extract
unzip codeql-linux64.zip
sudo mv codeql /usr/local/bin/
# Add to PATH (if needed)
export PATH=/usr/local/bin/codeql:$PATHManual Download (Any OS):
# Download from:
# https://github.com/github/codeql-cli-releases/releases
# Extract and add to PATH
unzip codeql-*.zip
export PATH=$PWD/codeql:$PATHVerify installation:
codeql --versiongit clone https://github.com/github/codeql.git ~/codeql-repoClone the GitHub repository containing workflows you want to analyze:
git clone https://github.com/your-org/your-repo.git ~/my-target-repoThis repository should have workflows in .github/workflows/ directory.
Create a database from your repository:
codeql database create ~/codeql-db/my-repo \
--language=actions \
--source-root=/path/to/your/repoExample:
codeql database create ~/codeql-db/demo \
--language=actions \
--source-root=~/my-target-repoRun all three security analysis queries:
Query 1: Trigger Events
codeql query run ~/codeql-repo/actions/ql/src/custom/TriggerExt.ql \
-d ~/codeql-db/my-repo \
--format=csv \
--output=triggers.csvQuery 2: Concurrency Settings
codeql query run ~/codeql-repo/actions/ql/src/custom/Cancel-in-Progress_Ana.ql \
-d ~/codeql-db/my-repo \
--format=csv \
--output=concurrency.csvQuery 3: File Execution Vulnerabilities
codeql query run ~/codeql-repo/actions/ql/src/custom/VulnerabilityDetection.ql \
-d ~/codeql-db/my-repo \
--format=sarif-latest \
--output=vulnerabilities.sarifView CSV results (Trigger Events and Concurrency):
cat triggers.csv
cat concurrency.csvConvert SARIF to JSON (Vulnerabilities):
python3 << 'EOF'
import json
with open('vulnerabilities.sarif', encoding='latin-1') as f:
data = json.load(f)
with open('vulnerabilities.json', 'w') as f:
json.dump(data, f, indent=2)
print("Vulnerability results converted to vulnerabilities.json")
EOFDisplay all results:
echo "=== Trigger Events ==="
cat triggers.csv
echo -e "\n=== Concurrency Settings ==="
cat concurrency.csv
echo -e "\n=== File Execution Vulnerabilities ==="
python3 << 'EOF'
import json
with open('vulnerabilities.sarif', encoding='latin-1') as f:
data = json.load(f)
results = data['runs'][0]['results']
for i, r in enumerate(results, 1):
print(f"{i}. {r['message']['text']}")
EOFPurpose: Extracts pull request trigger event
What it finds:
- Workflows with
on: pull_requesttrigger - Workflows with conditional pull request triggers
- High-risk workflows that execute on untrusted code
Results:
| Column | Meaning |
|---|---|
| WorkflowFile | Workflow filename |
| TriggerEvent | Event type (e.g., pull_request) |
| TriggerCondition | Specific trigger condition |
Run it:
codeql query run ~/codeql-repo/actions/ql/src/custom/TriggerExt.ql \
-d ~/codeql-db/my-repo \
--format=csv \
--output=triggers.csvExample Result:
WorkflowFile: ci.yml
TriggerEvent: pull_request
TriggerCondition: [pull_request]
Purpose: Extract workflow concurrency configurations
What it finds:
- Concurrency group settings
- Cancel-in-progress configurations
Results:
| Column | Meaning |
|---|---|
| WorkflowFile | Workflow filename |
| ConcurrencyGroup | Group identifier (from concurrency.group) |
| CancelInProgress | Boolean flag for cancellation behavior |
Run it:
codeql query run ~/codeql-repo/actions/ql/src/custom/Cancel-in-Progress_Ana.ql \
-d ~/codeql-db/my-repo \
--format=csv \
--output=concurrency.csvExample Result:
WorkflowFile: ci.yml
ConcurrencyGroup: ${{ github.workflow }}-${{ github.ref }}
CancelInProgress: true
Purpose: Detects repository files executed without prior modification
What it finds:
- Python scripts executed via
python script.py - Bash scripts executed via
bash script.sh - Shell scripts executed via
./script.sh - WITHOUT prior write operations in the same or previous steps
Results:
| Column | Meaning |
|---|---|
| WorkflowFile | Workflow YAML filename |
| ExecutedFile | File being executed without modification |
| Vulnerability | Description of the issue |
Run it:
codeql query run ~/codeql-repo/actions/ql/src/custom/VulnerabilityDetection.ql \
-d ~/codeql-db/my-repo \
--format=sarif-latest \
--output=vulnerabilities.sarifExample Result:
WorkflowFile: ci.yml
ExecutedFile: build.sh
Vulnerability: Executes 'build.sh' without prior write
This means build.sh is executed directly from the repository without any modification, making it vulnerable to malicious PRs that inject code into build.sh.
A file execution is considered VULNERABLE if:
- ✗ File is executed (python, bash, sh, ./)
- ✗ File was NOT modified in any previous step of the same job
- ✗ File was NOT modified before execution in the same step
A file execution is considered SAFE if:
- ✓ File is executed, AND
- ✓ File was modified/generated before execution (either in previous step or earlier in same step)
Example safe patterns:
steps:
- run: echo "echo 'hello'" > script.sh # File created
- run: bash script.sh # Then executedExample vulnerable pattern:
steps:
- run: bash script.sh # Directly from repo, no prior modification#!/bin/bash
# Configuration
TARGET_REPO=~/my-target-repo
CODEQL_REPO=~/codeql-repo
DB_PATH=~/codeql-db/analysis
# 1. Create database
echo "Creating CodeQL database..."
codeql database create $DB_PATH \
--language=actions \
--source-root=$TARGET_REPO
# 2. Run Query 1: Trigger Events
echo "Running trigger event analysis..."
codeql query run $CODEQL_REPO/actions/ql/src/custom/TriggerExt.ql \
-d $DB_PATH \
--format=csv \
--output=triggers.csv
# 3. Run Query 2: Concurrency Settings
echo "Running concurrency analysis..."
codeql query run $CODEQL_REPO/actions/ql/src/custom/Cancel-in-Progress_Ana.ql \
-d $DB_PATH \
--format=csv \
--output=concurrency.csv
# 4. Run Query 3: File Execution Vulnerabilities
echo "Running file execution vulnerability detection..."
codeql query run $CODEQL_REPO/actions/ql/src/custom/VulnerabilityDetection.ql \
-d $DB_PATH \
--format=sarif-latest \
--output=vulnerabilities.sarif
# 5. Convert SARIF to JSON
echo "Converting results..."
python3 << 'EOF'
import json
with open('vulnerabilities.sarif', encoding='latin-1') as f:
data = json.load(f)
with open('vulnerabilities.json', 'w') as f:
json.dump(data, f, indent=2)
EOF
# 6. Display summary
echo -e "\n========== ANALYSIS RESULTS =========="
echo -e "\n--- Trigger Events ---"
head -5 triggers.csv
echo -e "\n--- Concurrency Settings ---"
head -5 concurrency.csv
echo -e "\n--- File Execution Vulnerabilities ---"
python3 << 'EOF'
import json
try:
with open('vulnerabilities.sarif', encoding='latin-1') as f:
data = json.load(f)
results = data['runs'][0]['results']
print(f"Found {len(results)} vulnerabilities")
for r in results[:5]:
print(f" - {r['message']['text']}")
except:
print("No vulnerabilities found")
EOF
echo -e "\n========== FILES CREATED =========="
echo "triggers.csv - Pull request trigger analysis"
echo "concurrency.csv - Concurrency configuration analysis"
echo "vulnerabilities.sarif - File execution vulnerabilities (SARIF format)"
echo "vulnerabilities.json - File execution vulnerabilities (JSON format)"Run the script:
chmod +x analyze.sh
./analyze.sh