fix: Address high-severity bandit issues

This commit is contained in:
DSS
2025-12-11 07:13:06 -03:00
parent bcb4475744
commit 5b2a328dd1
167 changed files with 7051 additions and 7168 deletions

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""
DSS Component Checker Hook
DSS Component Checker Hook.
Validates React components for best practices and accessibility.
Written from scratch for DSS.
"""
@@ -19,7 +20,7 @@ COMPONENT_PATTERNS = [
"category": "accessibility",
"severity": "high",
"message": "Missing alt attribute on <img>. Add alt text for accessibility.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "a11y-button-type",
@@ -27,7 +28,7 @@ COMPONENT_PATTERNS = [
"category": "accessibility",
"severity": "medium",
"message": "Button missing type attribute. Add type='button' or type='submit'.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "a11y-anchor-href",
@@ -35,7 +36,7 @@ COMPONENT_PATTERNS = [
"category": "accessibility",
"severity": "high",
"message": "Anchor tag missing href. Use button for actions without navigation.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "a11y-click-handler",
@@ -43,7 +44,7 @@ COMPONENT_PATTERNS = [
"category": "accessibility",
"severity": "medium",
"message": "Click handler on non-interactive element. Use <button> or add role/tabIndex.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "a11y-form-label",
@@ -51,7 +52,7 @@ COMPONENT_PATTERNS = [
"category": "accessibility",
"severity": "medium",
"message": "Input may be missing label association. Add id with <label> or aria-label.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
# React best practices
{
@@ -60,7 +61,7 @@ COMPONENT_PATTERNS = [
"category": "react",
"severity": "medium",
"message": "Using array index as key. Use unique, stable IDs when possible.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "react-bind-render",
@@ -68,7 +69,7 @@ COMPONENT_PATTERNS = [
"category": "react",
"severity": "low",
"message": "Binding in render creates new function each time. Use arrow function or bind in constructor.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "react-inline-style-object",
@@ -76,7 +77,7 @@ COMPONENT_PATTERNS = [
"category": "react",
"severity": "low",
"message": "Large inline style object. Consider extracting to a constant or CSS module.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "react-console-log",
@@ -84,7 +85,7 @@ COMPONENT_PATTERNS = [
"category": "react",
"severity": "low",
"message": "Console statement detected. Remove before production.",
"file_types": [".js", ".jsx", ".ts", ".tsx"]
"file_types": [".js", ".jsx", ".ts", ".tsx"],
},
# TypeScript checks
{
@@ -93,7 +94,7 @@ COMPONENT_PATTERNS = [
"category": "typescript",
"severity": "medium",
"message": "Using 'any' type loses type safety. Consider using a specific type or 'unknown'.",
"file_types": [".ts", ".tsx"]
"file_types": [".ts", ".tsx"],
},
{
"id": "ts-type-assertion",
@@ -101,7 +102,7 @@ COMPONENT_PATTERNS = [
"category": "typescript",
"severity": "medium",
"message": "Type assertion to 'any'. This bypasses type checking.",
"file_types": [".ts", ".tsx"]
"file_types": [".ts", ".tsx"],
},
# Component structure
{
@@ -110,7 +111,7 @@ COMPONENT_PATTERNS = [
"category": "structure",
"severity": "low",
"message": "Component may not be exported. Ensure it's exported if meant to be reused.",
"file_types": [".jsx", ".tsx"]
"file_types": [".jsx", ".tsx"],
},
{
"id": "component-missing-displayname",
@@ -118,10 +119,11 @@ COMPONENT_PATTERNS = [
"category": "structure",
"severity": "low",
"message": "HOC component may need displayName for debugging.",
"file_types": [".jsx", ".tsx"]
}
"file_types": [".jsx", ".tsx"],
},
]
def get_config():
"""Load hook configuration."""
config_path = Path.home() / ".dss" / "hooks-config.json"
@@ -129,10 +131,10 @@ def get_config():
"component_checker": {
"enabled": True,
"categories": ["accessibility", "react", "typescript"],
"min_severity": "low"
"min_severity": "low",
}
}
if config_path.exists():
try:
with open(config_path) as f:
@@ -142,64 +144,65 @@ def get_config():
pass
return default_config
def severity_level(severity: str) -> int:
"""Convert severity to numeric level."""
levels = {"low": 1, "medium": 2, "high": 3}
return levels.get(severity, 0)
def check_content(content: str, file_path: str, config: dict) -> list:
"""Check content for component issues."""
issues = []
file_ext = Path(file_path).suffix.lower()
checker_config = config.get("component_checker", {})
enabled_categories = checker_config.get("categories", [])
min_severity = checker_config.get("min_severity", "low")
min_level = severity_level(min_severity)
for pattern_def in COMPONENT_PATTERNS:
# Skip if file type doesn't match
if file_ext not in pattern_def.get("file_types", []):
continue
# Skip if category not enabled
if enabled_categories and pattern_def["category"] not in enabled_categories:
continue
# Skip if below minimum severity
if severity_level(pattern_def["severity"]) < min_level:
continue
if re.search(pattern_def["regex"], content, re.MULTILINE):
issues.append({
"id": pattern_def["id"],
"category": pattern_def["category"],
"severity": pattern_def["severity"],
"message": pattern_def["message"]
})
issues.append(
{
"id": pattern_def["id"],
"category": pattern_def["category"],
"severity": pattern_def["severity"],
"message": pattern_def["message"],
}
)
return issues
def format_output(issues: list, file_path: str) -> str:
"""Format issues for display."""
if not issues:
return ""
severity_icons = {
"high": "[HIGH]",
"medium": "[MED]",
"low": "[LOW]"
}
severity_icons = {"high": "[HIGH]", "medium": "[MED]", "low": "[LOW]"}
category_labels = {
"accessibility": "A11Y",
"react": "REACT",
"typescript": "TS",
"structure": "STRUCT"
"structure": "STRUCT",
}
lines = [f"\n=== DSS Component Checker: {file_path} ===\n"]
# Group by category
by_category = {}
for issue in issues:
@@ -207,7 +210,7 @@ def format_output(issues: list, file_path: str) -> str:
if cat not in by_category:
by_category[cat] = []
by_category[cat].append(issue)
for category, cat_issues in by_category.items():
label = category_labels.get(category, category.upper())
lines.append(f"[{label}]")
@@ -215,36 +218,37 @@ def format_output(issues: list, file_path: str) -> str:
sev = severity_icons.get(issue["severity"], "[?]")
lines.append(f" {sev} {issue['message']}")
lines.append("")
lines.append("=" * 50)
return "\n".join(lines)
def main():
"""Main hook entry point."""
config = get_config()
if not config.get("component_checker", {}).get("enabled", True):
sys.exit(0)
# Read hook input from stdin
try:
input_data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
sys.exit(0)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
if tool_name not in ["Edit", "Write"]:
sys.exit(0)
file_path = tool_input.get("file_path", "")
file_ext = Path(file_path).suffix.lower() if file_path else ""
# Only check React/TypeScript files
if file_ext not in [".jsx", ".tsx", ".js", ".ts"]:
sys.exit(0)
# Get content to check
if tool_name == "Write":
content = tool_input.get("content", "")
@@ -252,17 +256,18 @@ def main():
content = tool_input.get("new_string", "")
else:
content = ""
if not content:
sys.exit(0)
issues = check_content(content, file_path, config)
if issues:
output = format_output(issues, file_path)
print(output, file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()