SAST Scanning
Identify security vulnerabilities in source code through static analysis.
When to Use This Skill
Use this skill when:
-
Implementing secure SDLC practices
-
Adding security gates to CI/CD
-
Automating code security reviews
-
Finding vulnerabilities before deployment
-
Meeting compliance requirements
Prerequisites
-
Source code access
-
CI/CD pipeline
-
SAST tool installation
Tool Comparison
Tool License Languages Best For
Semgrep OSS/Commercial 30+ Custom rules, speed
CodeQL Free (GitHub) 10+ Deep analysis
SonarQube OSS/Commercial 25+ Quality + Security
Bandit OSS Python Python projects
Brakeman OSS Ruby Rails apps
Semgrep
Installation
Install via pip
pip install semgrep
Or via Homebrew
brew install semgrep
Basic Usage
Run with default rules
semgrep --config auto .
Run specific rulesets
semgrep --config p/security-audit . semgrep --config p/owasp-top-ten . semgrep --config p/ci .
Scan specific languages
semgrep --config p/python . semgrep --config p/javascript .
Output formats
semgrep --config auto --json -o results.json . semgrep --config auto --sarif -o results.sarif .
Custom Rules
.semgrep/custom-rules.yaml
rules:
-
id: hardcoded-password patterns:
- pattern-either:
- pattern: password = "..."
- pattern: PASSWORD = "..."
- pattern: passwd = "..." message: Hardcoded password detected severity: ERROR languages: [python, javascript, java] metadata: cwe: "CWE-798" owasp: "A3:2017"
- pattern-either:
-
id: sql-injection patterns:
- pattern: | $QUERY = "..." + $USER_INPUT + "..." $DB.execute($QUERY) message: Potential SQL injection severity: ERROR languages: [python] metadata: cwe: "CWE-89"
-
id: insecure-random pattern: random.random() message: Use secrets module for security-sensitive randomness severity: WARNING languages: [python] fix: secrets.token_hex()
CI Configuration
.github/workflows/semgrep.yml
name: Semgrep
on: push: branches: [main] pull_request:
jobs: semgrep: runs-on: ubuntu-latest container: image: returntocorp/semgrep steps: - uses: actions/checkout@v4
- name: Run Semgrep
run: semgrep ci
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
CodeQL
Setup
.github/workflows/codeql.yml
name: CodeQL Analysis
on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 0 * * 0'
jobs: analyze: 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-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 }}"
Custom Queries
// queries/sql-injection.ql /**
- @name SQL Injection
- @description User input in SQL query
- @kind path-problem
- @problem.severity error
- @security-severity 9.0
- @precision high
- @id py/sql-injection
- @tags security */
import python import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.TaintTracking import semmle.python.security.dataflow.SqlInjectionQuery
from SqlInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink.getNode(), source, sink, "SQL injection from $@.", source.getNode(), "user input"
SonarQube
Docker Setup
docker-compose.yml
version: '3.8'
services: sonarqube: image: sonarqube:lts-community ports: - "9000:9000" environment: - SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar - SONAR_JDBC_USERNAME=sonar - SONAR_JDBC_PASSWORD=sonar volumes: - sonarqube_data:/opt/sonarqube/data - sonarqube_logs:/opt/sonarqube/logs depends_on: - db
db: image: postgres:15 environment: - POSTGRES_USER=sonar - POSTGRES_PASSWORD=sonar - POSTGRES_DB=sonar volumes: - postgresql_data:/var/lib/postgresql/data
volumes: sonarqube_data: sonarqube_logs: postgresql_data:
Scanner Configuration
sonar-project.properties
sonar.projectKey=myproject sonar.projectName=My Project sonar.projectVersion=1.0
sonar.sources=src sonar.tests=tests sonar.exclusions=/node_modules/,/vendor/
sonar.language=py sonar.python.coverage.reportPaths=coverage.xml
sonar.qualitygate.wait=true
CI Integration
GitHub Actions
-
name: SonarQube Scan uses: sonarsource/sonarqube-scan-action@master env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
-
name: Quality Gate uses: sonarsource/sonarqube-quality-gate-action@master timeout-minutes: 5 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Language-Specific Tools
Python (Bandit)
Install
pip install bandit
Run scan
bandit -r src/ -f json -o bandit-report.json
With configuration
bandit -r src/ -c bandit.yaml
bandit.yaml
skips: ['B101', 'B601'] exclude_dirs: ['tests', 'venv']
assert_used: skips: ['_test.py', '_tests.py']
JavaScript (ESLint Security)
Install
npm install eslint eslint-plugin-security --save-dev
// .eslintrc.js module.exports = { plugins: ['security'], extends: ['plugin:security/recommended'], rules: { 'security/detect-object-injection': 'error', 'security/detect-non-literal-regexp': 'warn', 'security/detect-unsafe-regex': 'error', 'security/detect-buffer-noassert': 'error', 'security/detect-eval-with-expression': 'error', 'security/detect-no-csrf-before-method-override': 'error', 'security/detect-possible-timing-attacks': 'warn' } };
Ruby (Brakeman)
Install
gem install brakeman
Run scan
brakeman -o brakeman-report.json -f json
CI configuration
brakeman --no-exit-on-warn --no-exit-on-error -o report.html
Quality Gates
SonarQube Quality Gate
{ "name": "Security Gate", "conditions": [ { "metric": "new_security_rating", "op": "GT", "error": "1" }, { "metric": "new_vulnerabilities", "op": "GT", "error": "0" }, { "metric": "new_security_hotspots_reviewed", "op": "LT", "error": "100" } ] }
Custom Gate Script
#!/bin/bash
security-gate.sh
CRITICAL=$(cat results.json | jq '[.results[] | select(.severity == "critical")] | length') HIGH=$(cat results.json | jq '[.results[] | select(.severity == "high")] | length')
echo "Critical: $CRITICAL, High: $HIGH"
if [ "$CRITICAL" -gt 0 ]; then echo "FAILED: Critical vulnerabilities found" exit 1 fi
if [ "$HIGH" -gt 5 ]; then echo "FAILED: Too many high severity vulnerabilities" exit 1 fi
echo "PASSED: Security gate" exit 0
Common Issues
Issue: Too Many False Positives
Problem: Alerts on safe code patterns Solution: Tune rules, add suppressions, use baseline
Issue: Slow Scans
Problem: SAST taking too long in CI Solution: Incremental scanning, parallel execution, exclude test files
Issue: Missing Coverage
Problem: Vulnerabilities not detected Solution: Add custom rules, combine multiple tools
Best Practices
-
Run on every PR/commit
-
Establish baseline for existing code
-
Prioritize by severity and exploitability
-
Maintain custom rules for your codebase
-
Integrate with IDE for early feedback
-
Track trends over time
-
Document false positive suppressions
-
Combine with DAST for comprehensive coverage
Related Skills
-
dast-scanning - Dynamic testing
-
dependency-scanning - Dependency vulnerabilities
-
github-actions - CI integration