Static Application Security Testing (SAST)
Integrating SAST in GitHub Actions
3 min read
Automating SAST in your CI/CD pipeline ensures every code change is scanned. Here's how to set up comprehensive SAST with GitHub Actions.
Complete SAST Workflow
# .github/workflows/sast.yml
name: SAST Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
semgrep:
name: Semgrep Scan
runs-on: ubuntu-latest
container:
image: returntocorp/semgrep
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
run: semgrep ci
env:
SEMGREP_RULES: >-
p/security-audit
p/secrets
p/owasp-top-ten
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
if: always()
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
strategy:
matrix:
language: ['javascript', 'python']
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
Breaking the Build on Findings
Configure when scans should block merges:
# Semgrep with severity thresholds
- name: Run Semgrep
run: |
semgrep ci \
--sarif --output=semgrep.sarif \
--error \
--severity ERROR
# Exit codes:
# 0 = no findings
# 1 = findings found (blocks PR if --error flag)
Pull Request Integration
Add security comments directly to PRs:
# .github/workflows/pr-security.yml
name: PR Security Review
on:
pull_request:
types: [opened, synchronize]
jobs:
security-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
id: semgrep
uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit
generateSarif: "1"
- name: Comment on PR
uses: actions/github-script@v7
if: steps.semgrep.outputs.findings != '0'
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## Security Scan Results\n\nFound security issues. Please review the Security tab.'
})
Multi-Language Configuration
Handle polyglot repositories:
jobs:
detect-languages:
runs-on: ubuntu-latest
outputs:
languages: ${{ steps.detect.outputs.languages }}
steps:
- uses: actions/checkout@v4
- name: Detect Languages
id: detect
run: |
LANGS="[]"
[ -f "package.json" ] && LANGS=$(echo $LANGS | jq '. + ["javascript"]')
[ -f "requirements.txt" ] && LANGS=$(echo $LANGS | jq '. + ["python"]')
[ -f "go.mod" ] && LANGS=$(echo $LANGS | jq '. + ["go"]')
echo "languages=$LANGS" >> $GITHUB_OUTPUT
sast:
needs: detect-languages
runs-on: ubuntu-latest
strategy:
matrix:
language: ${{ fromJson(needs.detect-languages.outputs.languages) }}
steps:
- uses: actions/checkout@v4
- name: Run Language-Specific SAST
run: semgrep --config auto --lang ${{ matrix.language }} .
Caching for Speed
Speed up scans with caching:
- name: Cache Semgrep
uses: actions/cache@v4
with:
path: ~/.semgrep
key: semgrep-${{ hashFiles('.semgrep/**') }}
- name: Cache SonarQube
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: sonar-${{ runner.os }}-${{ hashFiles('**/sonar-project.properties') }}
Security Dashboard Integration
Push results to GitHub Security tab:
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
category: sast-semgrep
Results appear in:
- Security → Code scanning alerts
- Pull requests → Checks tab
- Repository → Security → Dependabot
Next, we'll cover SAST best practices and handling false positives. :::