522 lines
11 KiB
Markdown
522 lines
11 KiB
Markdown
---
|
|
name: static-analysis-integration
|
|
description: Use when integrating SAST tools (SonarQube, ESLint, Pylint, Checkstyle), setting up security scanning, configuring code quality gates, managing false positives, or building CI/CD quality pipelines - provides tool selection, configuration patterns, and quality threshold strategies
|
|
---
|
|
|
|
# Static Analysis Integration
|
|
|
|
## Overview
|
|
|
|
**Core principle:** Static analysis catches bugs,security vulnerabilities, and code quality issues before code review. Automate it in CI/CD.
|
|
|
|
**Rule:** Block merges on critical issues, warn on moderate issues, ignore noise. Configure thresholds carefully.
|
|
|
|
## Static Analysis vs Other Quality Checks
|
|
|
|
| Check Type | When | What It Finds | Speed |
|
|
|------------|------|---------------|-------|
|
|
| **Static Analysis** | Pre-commit/PR | Bugs, security, style | Fast (seconds) |
|
|
| **Unit Tests** | Every commit | Logic errors | Fast (seconds) |
|
|
| **Integration Tests** | PR | Integration bugs | Medium (minutes) |
|
|
| **Security Scanning** | PR/Nightly | Dependencies, secrets | Medium (minutes) |
|
|
| **Manual Code Review** | PR | Design, readability | Slow (hours) |
|
|
|
|
**Static analysis finds:** Null pointer bugs, SQL injection, unused variables, complexity issues
|
|
|
|
**Static analysis does NOT find:** Business logic errors, performance issues (use profiling)
|
|
|
|
---
|
|
|
|
## Tool Selection by Language
|
|
|
|
### Python
|
|
|
|
| Tool | Purpose | When to Use |
|
|
|------|---------|-------------|
|
|
| **Pylint** | Code quality, style, bugs | General-purpose, comprehensive |
|
|
| **Flake8** | Style, simple bugs | Faster than Pylint, less strict |
|
|
| **mypy** | Type checking | Type-safe codebases |
|
|
| **Bandit** | Security vulnerabilities | Security-critical code |
|
|
| **Black** | Code formatting | Enforce consistent style |
|
|
|
|
**Recommended combo:** Black (formatting) + Flake8 (linting) + mypy (types) + Bandit (security)
|
|
|
|
---
|
|
|
|
### JavaScript/TypeScript
|
|
|
|
| Tool | Purpose | When to Use |
|
|
|------|---------|-------------|
|
|
| **ESLint** | Code quality, style, bugs | All JavaScript projects |
|
|
| **TypeScript** | Type checking | Type-safe codebases |
|
|
| **Prettier** | Code formatting | Enforce consistent style |
|
|
| **SonarQube** | Security, bugs, code smells | Enterprise, comprehensive |
|
|
|
|
**Recommended combo:** Prettier (formatting) + ESLint (linting) + TypeScript (types)
|
|
|
|
---
|
|
|
|
### Java
|
|
|
|
| Tool | Purpose | When to Use |
|
|
|------|---------|-------------|
|
|
| **Checkstyle** | Code style | Enforce coding standards |
|
|
| **PMD** | Bug detection, code smells | General-purpose |
|
|
| **SpotBugs** | Bug detection | Bytecode analysis |
|
|
| **SonarQube** | Comprehensive analysis | Enterprise, dashboards |
|
|
|
|
**Recommended combo:** Checkstyle (style) + SpotBugs (bugs) + SonarQube (comprehensive)
|
|
|
|
---
|
|
|
|
## Configuration Patterns
|
|
|
|
### ESLint Configuration (JavaScript)
|
|
|
|
```javascript
|
|
// .eslintrc.js
|
|
module.exports = {
|
|
extends: [
|
|
'eslint:recommended',
|
|
'plugin:@typescript-eslint/recommended',
|
|
'plugin:security/recommended'
|
|
],
|
|
rules: {
|
|
// Error: Block merge
|
|
'no-console': 'error',
|
|
'no-debugger': 'error',
|
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
|
|
// Warning: Allow merge, but warn
|
|
'complexity': ['warn', 10],
|
|
'max-lines': ['warn', 500],
|
|
|
|
// Off: Too noisy
|
|
'no-unused-vars': 'off', // TypeScript handles this
|
|
}
|
|
};
|
|
```
|
|
|
|
**Run in CI:**
|
|
```bash
|
|
eslint src/ --max-warnings 0 # Fail if any warnings
|
|
```
|
|
|
|
---
|
|
|
|
### Pylint Configuration (Python)
|
|
|
|
```ini
|
|
# .pylintrc
|
|
[MESSAGES CONTROL]
|
|
disable=
|
|
missing-docstring, # Too noisy for small projects
|
|
too-few-public-methods, # Design choice
|
|
logging-fstring-interpolation # False positives
|
|
|
|
[DESIGN]
|
|
max-line-length=100
|
|
max-args=7
|
|
max-locals=15
|
|
|
|
[BASIC]
|
|
good-names=i,j,k,_,id,db,pk
|
|
```
|
|
|
|
**Run in CI:**
|
|
```bash
|
|
pylint src/ --fail-under=8.0 # Minimum score 8.0/10
|
|
```
|
|
|
|
---
|
|
|
|
### SonarQube Quality Gates
|
|
|
|
```yaml
|
|
# sonar-project.properties
|
|
sonar.projectKey=my-project
|
|
sonar.sources=src
|
|
sonar.tests=tests
|
|
|
|
# Quality gate thresholds
|
|
sonar.qualitygate.wait=true
|
|
sonar.coverage.exclusions=**/*_test.py,**/migrations/**
|
|
|
|
# Fail conditions
|
|
sonar.qualitygate.timeout=300
|
|
```
|
|
|
|
**Quality Gate Criteria:**
|
|
- **Blocker/Critical issues:** 0 (block merge)
|
|
- **Major issues:** < 5 (block merge)
|
|
- **Code coverage:** > 80% (warn if lower)
|
|
- **Duplicated lines:** < 3%
|
|
- **Maintainability rating:** A or B
|
|
|
|
---
|
|
|
|
## CI/CD Integration
|
|
|
|
### GitHub Actions (Python)
|
|
|
|
```yaml
|
|
# .github/workflows/static-analysis.yml
|
|
name: Static Analysis
|
|
|
|
on: [pull_request]
|
|
|
|
jobs:
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
pip install pylint flake8 mypy bandit black
|
|
|
|
- name: Check formatting
|
|
run: black --check src/
|
|
|
|
- name: Run Flake8
|
|
run: flake8 src/ --max-line-length=100
|
|
|
|
- name: Run Pylint
|
|
run: pylint src/ --fail-under=8.0
|
|
|
|
- name: Run mypy
|
|
run: mypy src/ --strict
|
|
|
|
- name: Run Bandit (security)
|
|
run: bandit -r src/ -ll # Only high severity
|
|
```
|
|
|
|
---
|
|
|
|
### GitHub Actions (JavaScript)
|
|
|
|
```yaml
|
|
# .github/workflows/static-analysis.yml
|
|
name: Static Analysis
|
|
|
|
on: [pull_request]
|
|
|
|
jobs:
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '18'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Check formatting
|
|
run: npm run format:check # prettier --check
|
|
|
|
- name: Run ESLint
|
|
run: npm run lint # eslint --max-warnings 0
|
|
|
|
- name: Run TypeScript
|
|
run: npm run typecheck # tsc --noEmit
|
|
```
|
|
|
|
---
|
|
|
|
## Managing False Positives
|
|
|
|
**Strategy: Suppress selectively, document why**
|
|
|
|
### Inline Suppression (ESLint)
|
|
|
|
```javascript
|
|
// eslint-disable-next-line no-console
|
|
console.log("Debugging production issue"); // TODO: Remove after fix
|
|
|
|
// Better: Explain WHY
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const legacyData: any = externalLibrary.getData(); // Library has no types
|
|
```
|
|
|
|
---
|
|
|
|
### File-Level Suppression (Pylint)
|
|
|
|
```python
|
|
# pylint: disable=too-many-arguments
|
|
def complex_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8):
|
|
"""Legacy API - cannot change signature for backward compatibility."""
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
### Configuration Suppression
|
|
|
|
```ini
|
|
# .pylintrc
|
|
[MESSAGES CONTROL]
|
|
disable=
|
|
fixme, # Allow TODO comments
|
|
missing-docstring # Too noisy for this codebase
|
|
```
|
|
|
|
**Rule:** Every suppression needs a comment explaining WHY.
|
|
|
|
---
|
|
|
|
## Security-Focused Static Analysis
|
|
|
|
### Bandit (Python Security)
|
|
|
|
```yaml
|
|
# .bandit.yml
|
|
exclude_dirs:
|
|
- /tests
|
|
- /migrations
|
|
|
|
tests:
|
|
- B201 # Flask debug mode
|
|
- B601 # Parameterized shell calls
|
|
- B602 # Shell injection
|
|
- B608 # SQL injection
|
|
```
|
|
|
|
**Run:**
|
|
```bash
|
|
bandit -r src/ -ll -x tests/ # Only high/medium severity
|
|
```
|
|
|
|
---
|
|
|
|
### ESLint Security Plugin (JavaScript)
|
|
|
|
```javascript
|
|
// .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'
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Code Quality Metrics
|
|
|
|
### Complexity Analysis
|
|
|
|
**Cyclomatic complexity:** Measures decision paths through code
|
|
|
|
```python
|
|
# Simple function: Complexity = 1
|
|
def add(a, b):
|
|
return a + b
|
|
|
|
# Complex function: Complexity = 5 (if/elif/else = 4 paths + 1 base)
|
|
def process_order(order):
|
|
if order.status == "pending":
|
|
return validate(order)
|
|
elif order.status == "confirmed":
|
|
return ship(order)
|
|
elif order.status == "cancelled":
|
|
return refund(order)
|
|
else:
|
|
return reject(order)
|
|
```
|
|
|
|
**Threshold:**
|
|
- **< 10:** Acceptable
|
|
- **10-20:** Consider refactoring
|
|
- **> 20:** Must refactor (untestable)
|
|
|
|
**Configure:**
|
|
```ini
|
|
# Pylint
|
|
[DESIGN]
|
|
max-complexity=10
|
|
|
|
# ESLint
|
|
complexity: ['warn', 10]
|
|
```
|
|
|
|
---
|
|
|
|
### Duplication Detection
|
|
|
|
**SonarQube duplication threshold:** < 3%
|
|
|
|
**Find duplicates (Python):**
|
|
```bash
|
|
pylint src/ --disable=all --enable=duplicate-code
|
|
```
|
|
|
|
**Find duplicates (JavaScript):**
|
|
```bash
|
|
jscpd src/ # JavaScript Copy/Paste Detector
|
|
```
|
|
|
|
---
|
|
|
|
## Anti-Patterns Catalog
|
|
|
|
### ❌ Suppressing All Warnings
|
|
|
|
**Symptom:** Config disables most rules
|
|
|
|
```javascript
|
|
// ❌ BAD
|
|
module.exports = {
|
|
rules: {
|
|
'no-console': 'off',
|
|
'no-debugger': 'off',
|
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
// ... 50 more disabled rules
|
|
}
|
|
};
|
|
```
|
|
|
|
**Why bad:** Static analysis becomes useless
|
|
|
|
**Fix:** Address root causes, suppress selectively
|
|
|
|
---
|
|
|
|
###❌ No Quality Gates
|
|
|
|
**Symptom:** Static analysis runs but doesn't block merges
|
|
|
|
```yaml
|
|
# ❌ BAD: Linting failures don't block merge
|
|
- name: Run ESLint
|
|
run: eslint src/ || true # Always succeeds!
|
|
```
|
|
|
|
**Fix:** Fail CI on critical issues
|
|
|
|
```yaml
|
|
# ✅ GOOD
|
|
- name: Run ESLint
|
|
run: eslint src/ --max-warnings 0
|
|
```
|
|
|
|
---
|
|
|
|
### ❌ Ignoring Security Warnings
|
|
|
|
**Symptom:** Security findings marked as false positives without investigation
|
|
|
|
```python
|
|
# ❌ BAD
|
|
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # nosec
|
|
```
|
|
|
|
**Why bad:** Real SQL injection vulnerability ignored
|
|
|
|
**Fix:** Fix the issue, don't suppress
|
|
|
|
```python
|
|
# ✅ GOOD
|
|
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
|
|
```
|
|
|
|
---
|
|
|
|
### ❌ Running Static Analysis Only on Main Branch
|
|
|
|
**Symptom:** Issues discovered after merge
|
|
|
|
**Fix:** Run on every PR
|
|
|
|
```yaml
|
|
on: [pull_request] # Not just 'push' to main
|
|
```
|
|
|
|
---
|
|
|
|
## Quality Dashboard Setup
|
|
|
|
### SonarQube Dashboard
|
|
|
|
**Key metrics to track:**
|
|
1. **Bugs:** Code issues likely to cause failures
|
|
2. **Vulnerabilities:** Security issues
|
|
3. **Code Smells:** Maintainability issues
|
|
4. **Coverage:** Test coverage %
|
|
5. **Duplications:** Duplicated code blocks
|
|
|
|
**Quality Gate Example:**
|
|
- Bugs (Blocker/Critical): **0**
|
|
- Vulnerabilities (Blocker/Critical): **0**
|
|
- Code Smells (Blocker/Critical): **< 5**
|
|
- Coverage on new code: **> 80%**
|
|
- Duplicated lines on new code: **< 3%**
|
|
|
|
---
|
|
|
|
## Gradual Adoption Strategy
|
|
|
|
**For legacy codebases with thousands of issues:**
|
|
|
|
### Phase 1: Baseline (Week 1)
|
|
```bash
|
|
# Run analysis, capture current state
|
|
pylint src/ > baseline.txt
|
|
|
|
# Configure to only fail on NEW issues
|
|
# (Track baseline, don't enforce on old code)
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 2: Block New Issues (Week 2)
|
|
```yaml
|
|
# Block PRs that introduce NEW issues
|
|
- name: Run incremental lint
|
|
run: |
|
|
pylint $(git diff --name-only origin/main...HEAD | grep '\.py$') --fail-under=8.0
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 3: Fix High-Priority Old Issues (Weeks 3-8)
|
|
- Security vulnerabilities first
|
|
- Bugs second
|
|
- Code smells third
|
|
|
|
---
|
|
|
|
### Phase 4: Full Enforcement (Week 9+)
|
|
```yaml
|
|
# Enforce on entire codebase
|
|
- name: Run lint
|
|
run: pylint src/ --fail-under=8.0
|
|
```
|
|
|
|
---
|
|
|
|
## Bottom Line
|
|
|
|
**Static analysis catches bugs and security issues before code review. Automate it in CI/CD with quality gates.**
|
|
|
|
- Choose tools for your language: ESLint (JS), Pylint (Python), Checkstyle (Java)
|
|
- Configure thresholds: Block critical issues, warn on moderate, ignore noise
|
|
- Run on every PR, fail CI on violations
|
|
- Manage false positives selectively with documented suppressions
|
|
- Track quality metrics: complexity, duplication, coverage
|
|
|
|
**If static analysis isn't blocking merges, you're just generating reports nobody reads. Use quality gates.**
|