Add security and best_practices validators
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user