File size: 5,361 Bytes
92c4ae6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import mimetypes
import os
from pathlib import Path
from typing import List, Optional
from atom_security.core.models import Finding, ScanResult, SecurityRule, Severity
from atom_security.core.patterns import PatternMatcher, RuleLoader
class StaticAnalyzer:
"""
Analyzes files using regex patterns defined in signatures.yaml.
"""
def __init__(self, rules_path: Optional[Path] = None):
loader = RuleLoader(rules_path)
self.rules = loader.load_rules()
self.matcher = PatternMatcher(self.rules)
def scan_content(self, content: str, file_type: str = 'python') -> List[Finding]:
"""Scan string content for security issues."""
findings = []
for rule in self.rules:
if self._applies_to_file(rule, file_type):
matches = self.matcher.match(content, rule)
for line_num, line_content in matches:
finding = Finding(
rule_id=rule.id,
category=rule.category,
severity=rule.severity,
line_number=line_num,
line_content=line_content,
description=rule.description,
remediation=rule.remediation
)
findings.append(finding)
return findings
def scan_file(self, file_path: Path) -> List[Finding]:
"""Scan a single file for security issues."""
findings = []
try:
# Determine file type
file_type = self._detect_file_type(file_path)
if not file_type:
return []
# Skip large files (>1MB)
if file_path.stat().st_size > 1_000_000:
print(f"Skipping large file: {file_path}")
return []
content = file_path.read_text(errors='ignore')
# Check rules applicable to this file type
for rule in self.rules:
if self._applies_to_file(rule, file_type):
matches = self.matcher.match(content, rule)
for line_num, line_content in matches:
finding = Finding(
rule_id=rule.id,
category=rule.category,
severity=rule.severity,
file_path=str(file_path),
line_number=line_num,
line_content=line_content,
description=rule.description,
remediation=rule.remediation
)
findings.append(finding)
except Exception as e:
print(f"Error scanning {file_path}: {e}")
return findings
def scan_directory(self, directory: Path) -> ScanResult:
"""Scan a directory recursively."""
scan_directory = Path(directory)
all_findings = []
files_scanned = 0
import time
start_time = time.time()
# Walk directory
for root, dirs, files in os.walk(scan_directory):
# Skip hidden dirs
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
if file.startswith('.'):
continue
file_path = Path(root) / file
findings = self.scan_file(file_path)
all_findings.extend(findings)
files_scanned += 1
duration = time.time() - start_time
# Calculate summary metrics
max_severity = Severity.INFO
is_safe = True
severity_rank = {
Severity.INFO: 0,
Severity.LOW: 1,
Severity.MEDIUM: 2,
Severity.HIGH: 3,
Severity.CRITICAL: 4
}
for f in all_findings:
if severity_rank[f.severity] >= severity_rank[Severity.HIGH]:
is_safe = False
if severity_rank[f.severity] > severity_rank[max_severity]:
max_severity = f.severity
return ScanResult(
is_safe=is_safe,
max_severity=max_severity,
findings=all_findings,
scan_duration=duration,
files_scanned=files_scanned,
analyzers_run=["StaticAnalyzer"]
)
def _detect_file_type(self, file_path: Path) -> Optional[str]:
"""Map file extension to rule file_type."""
ext = file_path.suffix.lower()
if ext in ['.py', '.pyw']:
return 'python'
elif ext in ['.sh', '.bash', '.zsh']:
return 'bash'
elif ext in ['.md', '.markdown', '.txt']:
return 'markdown'
elif ext in ['.yml', '.yaml']:
return 'manifest' # Or yaml
# Binary check could be added here
return None
def _applies_to_file(self, rule: SecurityRule, file_type: str) -> bool:
"""Check if rule applies to file type."""
return file_type in rule.file_types or 'all' in rule.file_types
|