Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill covers integrating Static Application Security Testing (SAST) tools—CodeQL and Semgrep—into GitHub Actions CI/CD pipelines. It addresses configuring automated code scanning on pull requests and pushes, tuning rules to reduce false positives, uploading SARIF results to GitHub Advanced Security, and establishing quality gates that block merges when high-severity vulnerabilities are detected.
.claude/skills/integrating-sast-into-github-actions-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
Do not use for runtime vulnerability detection (use DAST instead), for scanning third-party dependencies (use SCA tools like Snyk), or for infrastructure-as-code scanning (use Checkov or tfsec).
Create a CodeQL workflow that runs on pull requests and on a weekly schedule to catch vulnerabilities in existing code.
yaml# .github/workflows/codeql-analysis.yml name: "CodeQL Analysis" on: push: branches: [main, develop] pull_request: branches: [main] schedule: - cron: '30 2 * * 1' # Weekly Monday 2:30 AM jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ['javascript', 'python'] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: security-extended,security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}"
Semgrep complements CodeQL with faster scans and support for custom pattern-based rules. Configure it to upload SARIF results to the same GitHub Security tab.
yaml# .github/workflows/semgrep.yml name: "Semgrep SAST Scan" on: pull_request: branches: [main, develop] push: branches: [main] jobs: semgrep: name: Semgrep Scan runs-on: ubuntu-latest permissions: security-events: write contents: read container: image: semgrep/semgrep:latest steps: - name: Checkout uses: actions/checkout@v4 - name: Run Semgrep run: | semgrep ci \ --config auto \ --config p/owasp-top-ten \ --config p/cwe-top-25 \ --sarif --output semgrep-results.sarif \ --severity ERROR \ --error env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: semgrep-results.sarif category: semgrep
Write organization-specific rules to catch patterns unique to your codebase, such as deprecated internal APIs or insecure configuration patterns.
yaml# .semgrep/custom-rules.yml rules: - id: hardcoded-database-url patterns: - pattern: | $DB_URL = "...$PROTO://...:...$PASS@..." message: | Hardcoded database connection string with credentials detected. Use environment variables or a secrets manager instead. languages: [python, javascript, typescript] severity: ERROR metadata: cwe: "CWE-798: Use of Hard-coded Credentials" owasp: "A07:2021 - Identification and Authentication Failures" - id: unsafe-deserialization patterns: - pattern-either: - pattern: pickle.loads(...) - pattern: yaml.load(..., Loader=yaml.Loader) - pattern: yaml.load(..., Loader=yaml.FullLoader) message: | Unsafe deserialization detected. Use safe alternatives to prevent remote code execution vulnerabilities. languages: [python] severity: ERROR metadata: cwe: "CWE-502: Deserialization of Untrusted Data" - id: missing-csrf-protection patterns: - pattern: | @app.route("...", methods=["POST"]) def $FUNC(...): ... - pattern-not-inside: | @csrf.exempt ... message: "POST endpoint may lack CSRF protection." languages: [python] severity: WARNING
Configure branch protection rules that require SAST checks to pass before merging, preventing vulnerable code from reaching production branches.
bash# Use GitHub CLI to set branch protection requiring SAST checks gh api repos/{owner}/{repo}/branches/main/protection \ --method PUT \ --field required_status_checks='{"strict":true,"contexts":["Analyze (javascript)","Analyze (python)","Semgrep Scan"]}' \ --field enforce_admins=true \ --field required_pull_request_reviews='{"required_approving_review_count":1}'
Manage false positives through CodeQL query filters and Semgrep nosemgrep annotations to maintain developer trust in scan results.
yaml# codeql-config.yml - Custom CodeQL configuration name: "Custom CodeQL Config" queries: - uses: security-extended - uses: security-and-quality - excludes: id: js/unused-local-variable paths-ignore: - '**/test/**' - '**/tests/**' - '**/vendor/**' - '**/node_modules/**' - '**/*.test.js' - '**/*.spec.py'
python# Example: Suppressing a known false positive in Semgrep import subprocess def run_safe_command(cmd_list): # nosemgrep: python.lang.security.audit.dangerous-subprocess-use result = subprocess.run(cmd_list, capture_output=True, text=True, shell=False) return result.stdout
Use the GitHub Security Overview dashboard and configure notifications for security alerts across repositories.
bash# Query SARIF results via GitHub API for reporting gh api repos/{owner}/{repo}/code-scanning/alerts \ --jq '.[] | select(.state=="open") | {rule: .rule.id, severity: .rule.security_severity_level, file: .most_recent_instance.location.path, line: .most_recent_instance.location.start_line}' # Count open alerts by severity gh api repos/{owner}/{repo}/code-scanning/alerts \ --jq '[.[] | select(.state=="open")] | group_by(.rule.security_severity_level) | map({severity: .[0].rule.security_severity_level, count: length})'
| Term | Definition | |------|------------| | SAST | Static Application Security Testing — analyzes source code without executing it to find security vulnerabilities | | SARIF | Static Analysis Results Interchange Format — standardized JSON format for expressing results from static analysis tools | | CodeQL | GitHub's semantic code analysis engine that treats code as data and queries it for vulnerability patterns | | Semgrep | Lightweight static analysis tool using pattern matching to find bugs and security issues across many languages | | Security Extended | CodeQL query suite that includes additional security queries beyond the default set for deeper analysis | | Quality Gate | Automated checkpoint that blocks code from progressing through the pipeline unless security criteria are met | | False Positive | A scan finding that incorrectly identifies secure code as vulnerable, requiring suppression or tuning |
Context: A platform team manages a monorepo containing Python microservices, TypeScript frontends, and Go infrastructure tools. Security reviews happen manually every quarter, missing vulnerabilities between reviews.
Approach:
--config auto to detect language automatically and apply relevant rulesetsPitfalls: Setting CodeQL to analyze all languages on every PR increases CI time significantly. Use path filters to trigger only relevant language scans. Semgrep's --config auto may enable rules that conflict with CodeQL findings, creating duplicate alerts.
Context: After enabling SAST, developers ignore findings because 40% are false positives, undermining the security program.
Approach:
.semgrepignore patterns for test files, generated code, and vendored dependenciesPitfalls: Over-suppressing rules to reduce noise can create blind spots. Always validate suppressions against the OWASP Top 10 and CWE Top 25 to ensure critical vulnerability classes remain covered.
SAST Pipeline Scan Report
==========================
Repository: org/web-application
Branch: feature/user-auth-refactor
Scan Date: 2026-02-23
Commit: a1b2c3d4
CodeQL Results:
Language Queries Run Findings Critical High Medium
javascript 312 4 1 2 1
python 287 2 0 1 1
Semgrep Results:
Ruleset Rules Matched Findings Errors Warnings
auto 1,847 3 1 2
owasp-top-ten 186 2 1 1
custom-rules 12 1 0 1
QUALITY GATE: FAILED
Blocking findings: 2 Critical/High severity issues
- [CRITICAL] CWE-89: SQL Injection in src/api/users.py:47
- [HIGH] CWE-79: Cross-site Scripting in src/components/Search.tsx:123
Action Required: Fix blocking findings before merge is permitted.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.