3 min read
GitHub Code Scanning: Finding Vulnerabilities Before They Ship
GitHub Code Scanning automatically finds security vulnerabilities and coding errors in your code. Let’s explore how to set it up and get the most out of it.
Understanding Code Scanning
Code scanning uses CodeQL, a semantic code analysis engine, to find security vulnerabilities, bugs, and other errors in your codebase.
Basic Setup
Enable code scanning with a simple workflow:
# .github/workflows/codeql.yml
name: "CodeQL Analysis"
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 8 * * 1' # Every Monday at 8 AM
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
Advanced Configuration
Configure CodeQL with a custom config file:
# .github/codeql/codeql-config.yml
name: "Security and Quality"
queries:
- uses: security-extended
- uses: security-and-quality
- uses: ./custom-queries # Local custom queries
paths:
- src
- lib
paths-ignore:
- test
- '**/*.test.js'
- node_modules
query-filters:
- exclude:
id: js/unused-local-variable
- exclude:
tags contain: /maintainability/
Reference it in your workflow:
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml
Writing Custom CodeQL Queries
Create queries for your specific security needs:
// queries/hardcoded-credentials.ql
/**
* @name Hardcoded credentials
* @description Hardcoded passwords or API keys in source code
* @kind problem
* @problem.severity error
* @security-severity 8.5
* @precision high
* @id custom/hardcoded-credentials
* @tags security
* external/cwe/cwe-798
*/
import javascript
class HardcodedCredential extends DataFlow::Node {
HardcodedCredential() {
exists(StringLiteral s |
this.asExpr() = s and
s.getValue().regexpMatch("(?i).*(password|api.?key|secret|token).*=.*['\"][^'\"]{8,}['\"].*")
)
}
}
from HardcodedCredential cred
select cred, "Potential hardcoded credential found"
Integrating with Third-Party Tools
Run additional scanners alongside CodeQL:
name: Security Scan
on: [push, pull_request]
jobs:
codeql:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
- uses: github/codeql-action/analyze@v2
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
Handling Alerts
import requests
def get_code_scanning_alerts(owner, repo, token):
url = f"https://api.github.com/repos/{owner}/{repo}/code-scanning/alerts"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json"
}
response = requests.get(url, headers=headers, params={"state": "open"})
alerts = response.json()
critical_alerts = [a for a in alerts if a['rule']['security_severity_level'] == 'critical']
print(f"Total open alerts: {len(alerts)}")
print(f"Critical alerts: {len(critical_alerts)}")
for alert in critical_alerts:
print(f"\nRule: {alert['rule']['description']}")
print(f"File: {alert['most_recent_instance']['location']['path']}")
print(f"Line: {alert['most_recent_instance']['location']['start_line']}")
return alerts
Branch Protection Rules
Require code scanning to pass:
# Use GitHub API or UI to set branch protection
name: Enforce Scanning
on:
pull_request:
branches: [main]
jobs:
check-alerts:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
script: |
const alerts = await github.rest.codeScanning.listAlertsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.ref,
state: 'open'
});
const critical = alerts.data.filter(
a => a.rule.security_severity_level === 'critical'
);
if (critical.length > 0) {
core.setFailed(`PR has ${critical.length} critical vulnerabilities`);
}
Code scanning is essential for modern secure development practices.