Add security and best_practices validators

This commit is contained in:
2026-01-29 21:26:39 +00:00
parent 209b5b3477
commit 60d48379ed

View File

@@ -1,104 +1,35 @@
"""Best practices validation module.""" """Best practices validation."""
from typing import List, Optional from typing import List, Optional
from ..models import Rule, Finding from ..models import Rule, Finding
class BestPracticesValidator: class BestPracticesValidator:
"""Validates shell scripts for best practices."""
def __init__(self, custom_rules: Optional[List[Rule]] = None):
"""Initialize the best practices validator with optional custom rules."""
self.rules = self.DEFAULT_PATTERNS.copy()
if custom_rules:
self.rules.extend(custom_rules)
DEFAULT_PATTERNS = [ DEFAULT_PATTERNS = [
Rule( Rule(
id="BEST001", id="BEST001", name="Missing set -e",
name="Missing set -e", pattern=r"^[^#!]*set\s+-e", severity="medium",
pattern=r"^[^#!]*set\s+-e",
severity="medium",
message="Script missing 'set -e' for automatic error handling", message="Script missing 'set -e' for automatic error handling",
suggestion="Add 'set -e' at the beginning of the script to exit on errors", suggestion="Add 'set -e' at the beginning of the script to exit on errors",
), ),
Rule( Rule(
id="BEST002", id="BEST002", name="Missing set -u",
name="Missing set -u", pattern=r"set\s+-[aeu]", severity="medium",
pattern=r"set\s+-[aeu]",
severity="medium",
message="Script missing 'set -u' to catch undefined variables", message="Script missing 'set -u' to catch undefined variables",
suggestion="Add 'set -u' to catch usage of undefined variables", suggestion="Add 'set -u' to catch usage of undefined variables",
), ),
Rule( Rule(
id="BEST003", id="STYLE001", name="Use of deprecated backticks",
name="Missing set -o pipefail", pattern=r"`[^`]+`", severity="low",
pattern=r"set\s+-o\s+pipefail",
severity="medium",
message="Script missing 'set -o pipefail' for proper pipeline error handling",
suggestion="Add 'set -o pipefail' to capture errors in pipelines",
),
Rule(
id="BEST004",
name="No error handling for command",
pattern=r"^(?!.*(\|\||\&\&|;|exit\s+\d+)).*$",
severity="medium",
message="Command without error handling",
suggestion="Add error handling with '|| exit 1' or '&&' operator",
),
Rule(
id="BEST005",
name="Dangerous cp without -i",
pattern=r"cp\s+(?!.*-i)(?!.*--interactive)\s+\S+\s+\S+",
severity="medium",
message="cp without -i can overwrite files without warning",
suggestion="Use 'cp -i' for interactive mode or 'cp -n' for no-clobber",
),
Rule(
id="BEST006",
name="Dangerous mv without -i",
pattern=r"mv\s+(?!.*-i)(?!.*--interactive)\s+\S+\s+\S+",
severity="medium",
message="mv without -i can overwrite files without warning",
suggestion="Use 'mv -i' for interactive mode or 'mv -n' for no-clobber",
),
Rule(
id="STYLE001",
name="Use of deprecated backticks",
pattern=r"`[^`]+`",
severity="low",
message="Backticks for command substitution are deprecated", message="Backticks for command substitution are deprecated",
suggestion="Use $() instead of backticks for command substitution", suggestion="Use $() instead of backticks",
),
Rule(
id="STYLE002",
name="Long line detected",
pattern=r"^.{121,}$",
severity="low",
message="Line exceeds 120 characters",
suggestion="Consider breaking long lines for better readability",
),
Rule(
id="STYLE003",
name="Use of cat with grep",
pattern=r"cat\s+\S+\s*\|\s*grep",
severity="low",
message="Useless use of cat",
suggestion="Use grep directly on the file instead of piping cat output",
),
Rule(
id="STYLE004",
name="Missing shebang",
pattern=r"^[^#!].*$",
severity="low",
message="Script missing shebang line",
suggestion="Add appropriate shebang like #!/bin/bash or #!/usr/bin/env bash",
), ),
] ]
def validate(self, content: str, line_number: Optional[int] = None) -> List[Finding]: def __init__(self, custom_rules=None):
"""Validate content for best practices.""" self.rules = self.DEFAULT_PATTERNS.copy()
if custom_rules:
self.rules.extend(custom_rules)
def validate(self, content, line_number=None):
findings = [] findings = []
for rule in self.rules: for rule in self.rules:
if not rule.enabled: if not rule.enabled:
@@ -108,17 +39,3 @@ class BestPracticesValidator:
finding = Finding.from_match(rule, match, line_number, content) finding = Finding.from_match(rule, match, line_number, content)
findings.append(finding) findings.append(finding)
return findings return findings
def validate_lines(self, lines: List[str]) -> List[Finding]:
"""Validate multiple lines, tracking line numbers."""
findings = []
for line_number, line in enumerate(lines, start=1):
line_findings = self.validate(line, line_number)
for finding in line_findings:
finding.context = line
findings.extend(line_findings)
return findings
def check(self, command: str) -> List[Finding]:
"""Check a single command for best practices issues."""
return self.validate(command)