| import re |
|
|
| from app.schemas import AgentOutput, CodeChunk, Finding, Severity |
| from app.services.llm_client import LLMClient |
|
|
|
|
| SECURITY_PATTERNS = [ |
| ( |
| re.compile(r"(?i)(api[_-]?key|secret|token|password)\s*=\s*['\"][^'\"]{8,}['\"]"), |
| "Potential hardcoded secret", |
| Severity.high, |
| "A credential-like value appears to be hardcoded.", |
| "Move secrets into environment variables or a managed secret store.", |
| ), |
| ( |
| re.compile(r"(?i)verify\s*=\s*False"), |
| "TLS certificate verification disabled", |
| Severity.high, |
| "Disabling TLS verification can allow man-in-the-middle attacks.", |
| "Remove verify=False and use a trusted CA bundle if needed.", |
| ), |
| ( |
| re.compile(r"(?i)(eval|exec)\s*\("), |
| "Dynamic code execution", |
| Severity.medium, |
| "Dynamic execution can turn untrusted input into arbitrary code execution.", |
| "Replace eval/exec with explicit parsing or a constrained command map.", |
| ), |
| ] |
|
|
|
|
| class SecurityAgent: |
| name = "Security Agent" |
|
|
| def __init__(self, llm_client: LLMClient): |
| self.llm_client = llm_client |
|
|
| async def analyze(self, chunks: list[CodeChunk]) -> AgentOutput: |
| findings: list[Finding] = [] |
|
|
| for chunk in chunks: |
| findings.extend(self._scan_chunk(chunk)) |
|
|
| await self.llm_client.complete_json( |
| "You are a security code review agent. Return JSON findings only.", |
| f"Review {len(chunks)} chunks for security issues.", |
| ) |
|
|
| return AgentOutput( |
| agent_name=self.name, |
| findings=findings, |
| metadata={"chunks_scanned": len(chunks), "mode": "static-rules-plus-llm-interface"}, |
| ) |
|
|
| def _scan_chunk(self, chunk: CodeChunk) -> list[Finding]: |
| findings: list[Finding] = [] |
| lines = chunk.content.splitlines() |
|
|
| for offset, line in enumerate(lines): |
| actual_line = chunk.line_start + offset |
| for pattern, title, severity, description, fix in SECURITY_PATTERNS: |
| if pattern.search(line): |
| findings.append( |
| Finding( |
| title=title, |
| severity=severity, |
| file_path=chunk.file_path, |
| line_start=actual_line, |
| line_end=actual_line, |
| description=description, |
| why_it_matters="Attackers often search repos for exposed credentials and unsafe execution paths.", |
| suggested_fix=fix, |
| agent_source=self.name, |
| ) |
| ) |
|
|
| return findings |
|
|