Static analysis and quality gates
Wire SAST, data-flow, and policy engines into PRs with clear severities, baselines, and auditable false-positive handling.
The SKILL should list engines (Semgrep, CodeQL, SpotBugs, etc.), scan paths, language versions, and whether results are blocking, comment-only, or report-only.
For legacy noise, use baselines or introduction-time filters so new code is not mixed with unbounded debt; new rules ship with fix examples or doc links.
Engines and handling
With multiple scanners on one PR, agents should dedupe by rule ID and avoid spam comments; align with dependency and secret scanning on a shared severity model.
- High severity (injection, hard-coded secrets) should block merge by default.
- Medium/low can be advisory: Checks summaries or PR comments without failing.
- Pin engine versions and rule packs in CI; upgrades include release notes and sample diffs.
ESLint full configuration example for a TypeScript + React project:
// .eslintrc.json — TypeScript + React project recommended config
{
"env": { "browser": true, "es2022": true, "node": true },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"rules": {
// Banned items (error level → blocks CI)
"no-console": ["error", { "allow": ["warn", "error"] }],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-floating-promises": "error",
"no-eval": "error",
// Warning items (warn level → comment but not block)
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"react/prop-types": "off",
// React 18+ no longer requires import React
"react/react-in-jsx-scope": "off"
},
"ignorePatterns": ["dist/", "coverage/", "*.generated.ts"]
}
Baselines and suppressions
Baselines capture only accepted risk or scheduled paydown—each line traces to a ticket; suppressions include ticket IDs and re-review dates.
- Prefer “changed files only” or SARIF
newmarkers for new-code gates, separate from full-repo baselines. - False positives: adjust rules or path-scoped waivers—no silent global off switches.
- After true-positive fixes, remove baseline or suppression entries to avoid zombies.
SonarQube Quality Gate threshold configuration and custom ESLint rule example:
// SonarQube Quality Gate threshold config (sonar-project.properties)
sonar.projectKey=my-app
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.test.ts,**/*.spec.ts
sonar.coverage.exclusions=**/generated/**,**/migrations/**
# Quality gate thresholds (set in SonarQube UI or sonarcloud.io)
# New code quality gate (recommended, checks only this change):
# Coverage > 80% → block
# Duplication < 3% → block
# Reliability rating ≥ A → block
# Security rating ≥ A → block
# Maintainability rating ≥ A → warning (no block)
// Custom ESLint rule example (ban console.log but allow console.error)
// rules/no-console-log.js
module.exports = {
meta: {
type: "suggestion",
docs: { description: "Ban console.log (use logger.info instead)" },
schema: [],
messages: { noConsoleLog: "Use logger.info() instead of console.log()" },
},
create(context) {
return {
CallExpression(node) {
if (
node.callee.type === "MemberExpression" &&
node.callee.object.name === "console" &&
node.callee.property.name === "log"
) {
context.report({ node, messageId: "noConsoleLog" });
}
},
};
},
};
SARIF upload and gates
When uploading SARIF to GitHub Advanced Security, GitLab SAST, or internal aggregators, use fingerprints / partialFingerprints for dedupe, map CWEs, and separate true positives from rule tuning needs.
- Artifacts: one
.sarifper job; merging must not clobbertool.driveror run metadata. - Severity mapping: map engine levels to platform enums (e.g.
error/warning/note) consistent with gate thresholds. - Paths: align
originalUriBaseIdswith CI working dirs so PR annotations line up. - Gates: combine rule ID, level, and new-vs-existing; optional “fail on new” with baselines.
Pre-commit hook integration for static analysis (.pre-commit-config.yaml):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
# ESLint (only check staged files, use with lint-staged)
- repo: local
hooks:
- id: eslint
name: ESLint
entry: npx eslint --max-warnings 0
language: node
types: [javascript, jsx, ts, tsx]
pass_filenames: true
# Semgrep (local SAST; recommended full scan in CI)
- id: semgrep
name: Semgrep
entry: semgrep --config=p/typescript --error
language: python
types: [typescript]
pass_filenames: true
# CI GitHub Actions full static analysis (block vs advisory)
# - name: Run ESLint (block: error level)
# run: npx eslint . --max-warnings 0 --format json -o eslint-results.json
# continue-on-error: false # error level failure → block merge
#
# - name: Run Semgrep (advisory: comment only)
# run: semgrep --config=p/typescript --json > semgrep-results.json
# continue-on-error: true # no block, upload SARIF as comment only
github/codeql-action/upload-sarif or vendor upload steps; GitLab uses artifacts:reports:sast. The SKILL should name the action and required fields (sarif_file, category, etc.).
PR merge flow
Fixed order: scan →normalize SARIF →upload →platform dedupe →gate decision →fix true positives or route false positives through review.
[ CI: engines write SARIF 2.1.x ]
│
▼
[ Normalize: path prefix, severity map, tool metadata ]
│
▼
[ Upload: GH upload-sarif / GL artifacts:reports:sast ]
│
▼
[ Platform: dedupe + CWE / rule ID UI ]
│
┌────────┴────────┐
▼ ▼
[ Gate: new + severity ] [ Advisory: comment / Check summary, no fail ]
│ │
└────────┬────────┘
▼
[ Triage: fix code / tune rules or baseline + audit in PR ]
│
▼
[ Merge: archive rule versions; align with secret/dep scans ]
Gate checklist builder
This form only builds Markdown in the browser—no upload. Use it in PR descriptions, SKILL appendices, or platform config reviews.
Fill fields, click Generate, then Copy.
SKILL snippet
---
name: static-analysis
description: Configure static analysis gates, severities, and SARIF triage
---
# Tool conventions
- ESLint: error level blocks CI, warn level advisory comment only
- SonarQube: new code coverage > 80%, reliability/security ≥ A
- Semgrep: high-severity rules block, medium/low advisory
# Block conditions (prevent merge)
- ESLint error-level rules triggered
- SonarQube quality gate failed (new code)
- Semgrep high-severity rules (injection, hardcoded secrets)
- npm audit high-severity CVE
# Advisory conditions (comment but don't block)
- ESLint warn-level rules
- SonarQube maintainability issues
- Semgrep medium/low severity rules
# pre-commit integration
- Scan staged files only (lint-staged + pre-commit)
- Local fast check; CI does full scan
- When hook fails, prompt repair command
# Baselines and false positives
- New code uses "changed files only" gate, not mixed with historical debt
- Suppression comments must include ticket number and re-review date
- Remove corresponding baseline entry after true positive fix
# SARIF upload
- GitHub: github/codeql-action/upload-sarif
- GitLab: artifacts:reports:sast
- Align path originalUriBaseIds with CI working directory