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 ..models import Rule, Finding
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 = [
Rule(
id="BEST001",
name="Missing set -e",
pattern=r"^[^#!]*set\s+-e",
severity="medium",
id="BEST001", name="Missing set -e",
pattern=r"^[^#!]*set\s+-e", severity="medium",
message="Script missing 'set -e' for automatic error handling",
suggestion="Add 'set -e' at the beginning of the script to exit on errors",
),
Rule(
id="BEST002",
name="Missing set -u",
pattern=r"set\s+-[aeu]",
severity="medium",
id="BEST002", name="Missing set -u",
pattern=r"set\s+-[aeu]", severity="medium",
message="Script missing 'set -u' to catch undefined variables",
suggestion="Add 'set -u' to catch usage of undefined variables",
),
Rule(
id="BEST003",
name="Missing set -o pipefail",
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",
id="STYLE001", name="Use of deprecated backticks",
pattern=r"`[^`]+`", severity="low",
message="Backticks for command substitution are deprecated",
suggestion="Use $() instead of backticks for command substitution",
),
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",
suggestion="Use $() instead of backticks",
),
]
def validate(self, content: str, line_number: Optional[int] = None) -> List[Finding]:
"""Validate content for best practices."""
def __init__(self, custom_rules=None):
self.rules = self.DEFAULT_PATTERNS.copy()
if custom_rules:
self.rules.extend(custom_rules)
def validate(self, content, line_number=None):
findings = []
for rule in self.rules:
if not rule.enabled:
@@ -108,17 +39,3 @@ class BestPracticesValidator:
finding = Finding.from_match(rule, match, line_number, content)
findings.append(finding)
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)