Add performance and secrets detection rules
This commit is contained in:
119
src/rules/secrets.py
Normal file
119
src/rules/secrets.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""Hardcoded secret detection rules."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import tree_sitter
|
||||
|
||||
from src.analyzers.base import (
|
||||
Analyzer,
|
||||
Finding,
|
||||
FindingCategory,
|
||||
SeverityLevel,
|
||||
)
|
||||
|
||||
|
||||
class HardcodedSecretAnalyzer(Analyzer):
|
||||
"""Detect hardcoded secrets in source code."""
|
||||
|
||||
SECRET_PATTERNS = {
|
||||
"aws_access_key": (
|
||||
r"(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}",
|
||||
"AWS Access Key",
|
||||
),
|
||||
"aws_secret_key": (
|
||||
r"[A-Za-z0-9/+=]{40}",
|
||||
"AWS Secret Key",
|
||||
),
|
||||
"github_token": (
|
||||
r"gh[pousr]_[A-Za-z0-9_]{36,}",
|
||||
"GitHub Token",
|
||||
),
|
||||
"google_api_key": (
|
||||
r"AIza[0-9A-Za-z\\-_]{35}",
|
||||
"Google API Key",
|
||||
),
|
||||
"slack_token": (
|
||||
r"xox[baprs]-([0-9a-zA-Z]{10,48})",
|
||||
"Slack Token",
|
||||
),
|
||||
"private_key_header": (
|
||||
r"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----",
|
||||
"Private Key",
|
||||
),
|
||||
"jwt_token": (
|
||||
r"eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*",
|
||||
"JWT Token",
|
||||
),
|
||||
"password_assignment": (
|
||||
r"(?:password|passwd|pwd|secret|token|api_key|apikey)\s*[:=]\s*['\"][^'\"]+['\"]",
|
||||
"Password/Secret Assignment",
|
||||
),
|
||||
"database_url": (
|
||||
r"(?:mongodb(\+srv)?|postgres|postgresql|mysql|mssql)://[^:]+:[^@]+@",
|
||||
"Database Connection URL",
|
||||
),
|
||||
}
|
||||
|
||||
def rule_id(self) -> str:
|
||||
return "secret.hardcoded_secret"
|
||||
|
||||
def rule_name(self) -> str:
|
||||
return "Hardcoded Secret Detection"
|
||||
|
||||
def severity(self) -> SeverityLevel:
|
||||
return SeverityLevel.CRITICAL
|
||||
|
||||
def category(self) -> FindingCategory:
|
||||
return FindingCategory.SECRET
|
||||
|
||||
def analyze(
|
||||
self, source_code: str, file_path: Path, tree: tree_sitter.Tree
|
||||
) -> list[Finding]:
|
||||
findings = []
|
||||
|
||||
for pattern_id, (pattern, secret_type) in self.SECRET_PATTERNS.items():
|
||||
matches = list(re.finditer(pattern, source_code))
|
||||
for match in matches:
|
||||
if not self._is_false_positive(match.group(), source_code):
|
||||
line = source_code[: match.start()].count("\n") + 1
|
||||
findings.append(
|
||||
Finding(
|
||||
rule_id=self.rule_id(),
|
||||
rule_name=self.rule_name(),
|
||||
severity=self.severity(),
|
||||
category=self.category(),
|
||||
message=f"Potential {secret_type} found in code",
|
||||
suggestion="Move secret to environment variables or secure vault",
|
||||
file_path=file_path,
|
||||
line_number=line,
|
||||
column=match.start() - source_code[:match.start()].rfind("\n") - 1,
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
def _is_false_positive(self, match: str, source_code: str) -> bool:
|
||||
false_positive_patterns = [
|
||||
r"example\.com",
|
||||
r"localhost",
|
||||
r"test",
|
||||
r"placeholder",
|
||||
r"your_",
|
||||
r"<",
|
||||
r">",
|
||||
]
|
||||
lower_match = match.lower()
|
||||
return any(fp in lower_match for fp in false_positive_patterns)
|
||||
|
||||
def _get_line_number(self, node: tree_sitter.Node, source_code: str) -> int:
|
||||
lines = source_code.split("\n")
|
||||
start_byte = node.start_byte if hasattr(node, "start_byte") else 0
|
||||
pos = 0
|
||||
for line_num, line_text in enumerate(lines, 1):
|
||||
if pos + len(line_text) >= start_byte:
|
||||
return line_num
|
||||
pos += len(line_text) + 1
|
||||
return 1
|
||||
|
||||
def _get_column(self, node: tree_sitter.Node) -> int:
|
||||
return node.start_column if hasattr(node, "start_column") else 0
|
||||
Reference in New Issue
Block a user