Linting analyzes source code for stylistic issues, bugs, and suspicious patterns without running it. While linters started as style and quality tools, modern linters and their security plugins catch a meaningful share of security problems — making linting a fast, low-friction first line of defense that fits naturally in pre-commit and CI.
- Consistency reduces risk — consistent, readable code is easier to review and less likely to hide bugs in unexpected places.
- Security rules catch real flaws — many linters detect dangerous functions, injection-prone patterns, weak crypto usage, insecure randomness, and hardcoded values.
- Fast feedback — linters run in seconds on a developer's machine, so they belong at pre-commit and on every pull request without meaningfully slowing the pipeline.
- Bridge to SAST — lightweight, rule-based scanners like Semgrep blur the line between linting and SAST, giving security depth at lint speed.
Run linting at every layer for maximum coverage without introducing bottlenecks:
- IDE — real-time feedback as the developer types. This is the lowest-friction catch — the developer sees the issue at the moment of creation. IDE plugins for ESLint, Bandit, Semgrep, and language servers can highlight problems inline.
- Pre-commit — block obvious issues before they enter history. Scope checks to changed files only (using
lint-stagedor Lefthook'sglobfiltering) to keep execution fast. See Pre-commit. - CI — authoritative enforcement that cannot be bypassed. Fail the build on new violations. Run the full rule set here, not just the fast subset used at pre-commit.
Enable security rule sets in language-specific linters. Do not run only style rules — security rules are where the risk is:
| Language | Linter | Security rules |
|---|---|---|
| Python | Bandit, Semgrep, Ruff | B-prefix rules (Bandit); p/python rule set (Semgrep) |
| JavaScript / TypeScript | ESLint + eslint-plugin-security, Semgrep |
no-eval, no-new-func, XSS patterns, detect-non-literal-regexp |
| Go | golangci-lint (includes gosec) | gosec rules: G101–G601 |
| Java | SpotBugs + find-sec-bugs, Semgrep | find-sec-bugs plugin detects SQL injection, XXE, SSRF |
| Ruby | Brakeman | Full Rails-aware security analysis |
| PHP | PHPCS + Security Audit, Psalm | security-audit sniffs |
| Kotlin / Android | Detekt + detekt-rules-security | Android-specific security rules |
Lint IaC files for misconfiguration:
- Dockerfile —
hadolintenforces best practices: nolatesttag,USERinstruction set, shell injection inRUNcommands. - Terraform —
tflintandtfsec/Trivycatch misconfigurations beforeterraform apply. - Kubernetes manifests —
kube-linterchecks for privilege escalation, missing resource limits, host network access. - Helm charts —
helm lint+kube-linteron rendered templates.
See IaC Scanning for deeper coverage.
Validate YAML/JSON/TOML in CI to prevent malformed or unsafe config from reaching deployment:
# .pre-commit-config.yaml entry
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-yaml
- id: check-json
- id: check-tomlSemgrep lets you write custom rules in YAML to enforce project-specific security patterns:
# semgrep-rules/no-md5.yaml
rules:
- id: no-md5-for-hashing
patterns:
- pattern: hashlib.md5(...)
message: "MD5 is cryptographically broken. Use hashlib.sha256() or better."
languages: [python]
severity: ERROR
metadata:
category: security
cwe: CWE-327Run it in CI:
semgrep --config semgrep-rules/ --error --json > semgrep-results.jsonNew projects inherit years of existing lint violations. Blocking on all of them at once is counterproductive. Manage this with baselines:
- Semgrep baseline —
semgrep --baseline-commit <sha>reports only findings introduced after the baseline commit, not the historical backlog. - ESLint — use
eslint --report-unused-disable-directivesand dedicate a sprint to clearing violations incrementally. - detect-secrets baseline — generate a baseline file with
detect-secrets scan > .secrets.baselineand commit it. New secrets are flagged against the baseline.
Treat clearing lint debt as technical debt: schedule it, track it, and do not let it grow by blocking new violations.
- Security rules disabled — style linting without security rules enabled misses the most valuable signal. Audit your
.eslintrc/pyproject.tomlto confirm security plugins are active. - Noisy rules driving suppression — if developers add
# noqa,// eslint-disable, or// noseccomments routinely, the rules are misconfigured. Tune or replace noisy rules instead of suppressing them everywhere. - Only running in CI, not at pre-commit — slow feedback loops mean developers context-switch after forgetting what the code was doing.
- Linting only changed files in CI — fine for speed, but run full-repo linting on a schedule (weekly) to catch violations in unchanged files.
- Ignoring
lint-stagedperformance — on large repos, running linters against all files at pre-commit is slow. Scope to staged files.
- Number of security-category lint violations introduced per sprint (should trend toward zero).
# noqa/// eslint-disablesuppression count per repo (rising counts indicate noise problems or avoidance).- Time from lint violation to fix (measures how actionable feedback is).
- Percentage of repos with security lint rules enabled (org-level metric).
| Level | Practice |
|---|---|
| Starting | Style linter running in CI; security rules added to at least one language linter |
| Developing | Security linting (Semgrep or equivalent) at pre-commit and CI; IaC linting for Dockerfile and Terraform; baseline managed |
| Defined | Custom Semgrep rules for project-specific patterns; IDE plugins standard in developer tooling; violations trend tracked |
| Advanced | Org-wide shared Semgrep rule library; lint results fed into ASPM for correlation with runtime findings; suppressions reviewed in security audits |
Tools1
- Bandit — Finds common security issues in Python code. Checks for dangerous calls (
exec,eval,pickle), weak crypto, SQL injection patterns, and more. Integrates with pre-commit and CI. - Brakeman — Static analysis specifically for Ruby on Rails. Detects SQL injection, XSS, mass assignment, and other Rails-specific security issues.
- ESLint — Pluggable JavaScript/TypeScript linter. Add
eslint-plugin-securityandeslint-plugin-no-unsanitizedfor security rules coveringeval, regular expression injection, and unsafe HTML insertion. - golangci-lint — Fast aggregator of Go linters. Includes
gosecfor security analysis (hardcoded credentials, SQL injection, weak crypto, file permissions). - hadolint — Dockerfile linter that enforces best practices: avoid
latesttags, set non-rootUSER, avoid shell injection inRUN. - Semgrep — Fast, multi-language static analysis using readable YAML pattern rules. Community registry contains thousands of security rules. Supports custom rules for project-specific patterns.
- SpotBugs + find-sec-bugs — SpotBugs is a Java bytecode analyzer; find-sec-bugs adds 130+ security bug patterns covering OWASP Top 10 for Java/Kotlin.
- SonarQube / SonarCloud — Code quality and security analysis across 30+ languages. Community edition is free; commercial editions add security hotspot management, portfolio reporting, and branch analysis.
- OWASP Code Review Guide
- Semgrep rule registry
- gosec — Go security checker
- OWASP Cheat Sheet — Injection Prevention
Footnotes
-
Listed in alphabetical order. ↩