Add rules, fixes, and llm modules
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-01-30 18:03:16 +00:00
parent f99da8681b
commit 03d2bd6db1

265
config_auditor/rules.py Normal file
View File

@@ -0,0 +1,265 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional
from packaging import version
import re
@dataclass
class Issue:
severity: str
category: str
message: str
file: Path
line: Optional[int] = None
suggestion: Optional[str] = None
rule_id: Optional[str] = None
@dataclass
class RuleResult:
passed: bool
message: str
severity: str
suggestion: Optional[str] = None
class Rule(ABC):
@property
@abstractmethod
def rule_id(self) -> str:
pass
@property
@abstractmethod
def severity(self) -> str:
pass
@abstractmethod
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
pass
class DeprecatedPackageRule(Rule):
DEPRECATED_PACKAGES = {
"request": "requests",
"urllib2": "urllib.request",
"httplib": "http.client",
"StringIO": "io.StringIO",
}
@property
def rule_id(self) -> str:
return "deprecated-package"
@property
def severity(self) -> str:
return "critical"
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
deps = data.get("dependencies", {})
dev_deps = data.get("devDependencies", {})
all_deps = {**deps, **dev_deps}
for pkg in all_deps:
if pkg in self.DEPRECATED_PACKAGES:
return RuleResult(
passed=False,
message=f"Deprecated package '{pkg}' found. Use '{self.DEPRECATED_PACKAGES[pkg]}' instead.",
severity=self.severity,
suggestion=f"Replace '{pkg}' with '{self.DEPRECATED_PACKAGES[pkg]}' in dependencies"
)
return RuleResult(passed=True, message="No deprecated packages found", severity="info")
class OutdatedVersionRule(Rule):
@property
def rule_id(self) -> str:
return "outdated-version"
@property
def severity(self) -> str:
return "warning"
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
deps = data.get("dependencies", {})
dev_deps = data.get("devDependencies", {})
all_deps = {**deps, **dev_deps}
outdated = []
for pkg, ver in all_deps.items():
if ver and ver.startswith("^"):
try:
v = version.parse(ver[1:])
if v.major == 0:
outdated.append(f"{pkg}@{ver} (major version 0, may have breaking changes)")
except Exception:
pass
if outdated:
return RuleResult(
passed=False,
message=f"Potentially outdated dependencies: {', '.join(outdated)}",
severity=self.severity,
suggestion="Review these dependencies for stability concerns"
)
return RuleResult(passed=True, message="Dependencies appear up to date", severity="info")
class MissingTypeCheckingRule(Rule):
@property
def rule_id(self) -> str:
return "missing-type-checking"
@property
def severity(self) -> str:
return "warning"
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
if data.get("compilerOptions", {}).get("strict") is not True:
return RuleResult(
passed=False,
message="TypeScript strict mode is not enabled",
severity=self.severity,
suggestion='Set "compilerOptions": { "strict": true } in tsconfig.json'
)
return RuleResult(passed=True, message="TypeScript strict mode is enabled", severity="info")
class SecurityVulnerabilityRule(Rule):
VULNERABLE_PATTERNS = [
(r"\"version\":\s*\"0\.\d+\.\d+\"", "Versions starting with 0.x may have security issues"),
(r"\"private\":\s*false", "Package should not be public if marked private"),
]
@property
def rule_id(self) -> str:
return "security-vulnerability"
@property
def severity(self) -> str:
return "critical"
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
import json
content = json.dumps(data)
for pattern, message in self.VULNERABLE_PATTERNS:
if re.search(pattern, content):
return RuleResult(
passed=False,
message=message,
severity=self.severity,
suggestion="Review and update the configuration"
)
return RuleResult(passed=True, message="No obvious security vulnerabilities detected", severity="info")
class MissingScriptsRule(Rule):
@property
def rule_id(self) -> str:
return "missing-scripts"
@property
def severity(self) -> str:
return "warning"
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
scripts = data.get("scripts", {})
required_scripts = ["test", "build"]
missing = [s for s in required_scripts if s not in scripts]
if missing:
return RuleResult(
passed=False,
message=f"Missing recommended scripts: {', '.join(missing)}",
severity=self.severity,
suggestion=f"Add scripts for: {', '.join(missing)}"
)
return RuleResult(passed=True, message="Required scripts are present", severity="info")
class PythonProjectMetaRule(Rule):
@property
def rule_id(self) -> str:
return "python-project-meta"
@property
def severity(self) -> str:
return "info"
def evaluate(self, data: Dict[str, Any], file_path: Path) -> RuleResult:
issues = []
if "tool" in data:
if "poetry" in data.get("tool", {}):
poetry = data["tool"]["poetry"]
if not poetry.get("name"):
issues.append("Missing poetry project name")
if not poetry.get("version"):
issues.append("Missing poetry project version")
elif "pytest" in data.get("tool", {}):
if not data["tool"]["pytest"].get("testpaths"):
issues.append("Missing pytest testpaths")
if issues:
return RuleResult(
passed=False,
message=f"Python project issues: {', '.join(issues)}",
severity=self.severity,
suggestion="Add missing project metadata"
)
return RuleResult(passed=True, message="Python project metadata is complete", severity="info")
class RuleRegistry:
RULES = {
"json": [
DeprecatedPackageRule(),
OutdatedVersionRule(),
MissingTypeCheckingRule(),
SecurityVulnerabilityRule(),
MissingScriptsRule(),
],
"yaml": [
SecurityVulnerabilityRule(),
],
"toml": [
PythonProjectMetaRule(),
],
}
def __init__(self):
self._rules = {k: list(v) for k, v in self.RULES.items()}
def evaluate(self, format_type: str, data: Dict[str, Any], file_path: Path) -> List[Issue]:
issues = []
rules = self._rules.get(format_type, [])
for rule in rules:
result = rule.evaluate(data, file_path)
if not result.passed:
issues.append(Issue(
severity=result.severity,
category=rule.rule_id,
message=result.message,
file=file_path,
suggestion=result.suggestion,
rule_id=rule.rule_id
))
return issues
def add_rule(self, format_type: str, rule: Rule):
if format_type not in self._rules:
self._rules[format_type] = []
self._rules[format_type].append(rule)