vora-sonnet's picture
Upload folder using huggingface_hub
0d3f7cc verified
Raw
History Blame Contribute Delete
7.82 kB
"""Security agent implementation."""
from __future__ import annotations
import json
import logging
from typing import Any
from hermes.agents.base.agent import BaseAgent
from hermes.core.types import AgentStrategy, ToolCall, ToolResult
from hermes.tools.base.registry import tool_registry
logger = logging.getLogger(__name__)
class SecurityAgent(BaseAgent):
"""Agent specialized in security analysis and vulnerability scanning."""
def __init__(self, llm_provider: Any | None = None) -> None:
super().__init__(
agent_type="security",
strategy=AgentStrategy.REACT,
tools=["security_scanner", "github_repo_reader", "file_reader"],
llm_provider=llm_provider,
)
async def plan(self, task: str) -> list[str]:
"""Create security analysis plan."""
return [
f"Scan for security vulnerabilities: {task}",
"Review code for secrets and sensitive data exposure",
"Check dependency security and configuration",
"Analyze authentication and authorization patterns",
"Compile security assessment report",
]
async def think(self, task: str, observations: list[str]) -> dict[str, Any]:
"""Reason about security approach using LLM."""
observations_text = "\n".join(f"- {obs[:500]}" for obs in observations) if observations else "None yet."
prompt = f"""You are a security analysis agent. Your task: {task}
Available tools (CHOOSE ONE):
1. security_scanner — Scan code for vulnerabilities and secrets
Required: {{"action": "scan_directory", "path": "."}}
Actions: scan_code (scan single file), scan_directory (scan folder)
Optional: {{"code": "code string to scan"}}
2. github_repo_reader — Read GitHub repository files
Required: {{"action": "get_readme", "owner": "owner_name", "repo": "repo_name"}}
Actions: get_readme, list_files, read_file, get_repo
3. file_reader — Read a local file
Required: {{"action": "read", "path": "file/path.txt"}}
Previous observations:
{observations_text}
What should be your NEXT action? Choose the most appropriate tool.
Respond in JSON format ONLY:
{{"reasoning": "why this tool", "tool": "tool_name", "arguments": {{"key": "value"}}, "done": false}}
Rules:
- Include ALL required arguments for the tool you choose
- If you have enough information, set "done": true and "tool": "none"
- Do NOT make up tool names — use ONLY the 3 tools listed above"""
response = await self._call_llm([{"role": "user", "content": prompt}])
parsed = self._parse_json_response(response)
if parsed and "tool" in parsed:
parsed.setdefault("reasoning", "")
parsed.setdefault("arguments", {})
parsed.setdefault("done", False)
return parsed
# Fallback: default to security scan
return {
"reasoning": f"Starting security scan of current directory for: {task}",
"tool": "security_scanner",
"arguments": {"action": "scan_directory", "path": "."},
"done": False,
}
async def act(self, thought: dict[str, Any]) -> ToolCall:
"""Execute security action based on LLM decision."""
tool_name = thought.get("tool", "security_scanner")
arguments = thought.get("arguments", {})
valid_tools = ["security_scanner", "github_repo_reader", "file_reader"]
if tool_name not in valid_tools:
tool_name = "security_scanner"
# Ensure required arguments for each tool
if tool_name == "security_scanner":
if "action" not in arguments:
arguments["action"] = "scan_directory"
if "path" not in arguments:
arguments["path"] = "."
elif tool_name == "github_repo_reader":
if "action" not in arguments:
arguments["action"] = "get_readme"
if "owner" not in arguments:
arguments["owner"] = ""
if "repo" not in arguments:
arguments["repo"] = ""
elif tool_name == "file_reader":
if "action" not in arguments:
arguments["action"] = "read"
if "path" not in arguments:
arguments["path"] = "README.md"
return ToolCall(tool_name=tool_name, arguments=arguments)
async def observe(self, result: ToolResult) -> str:
"""Observe security results using LLM to extract key findings."""
if not hasattr(result, "success") or not result.success:
return f"Tool execution failed: {result}"
output = result.output if hasattr(result, "output") else str(result)
output_text = json.dumps(output, default=str)[:3000] if not isinstance(output, str) else output[:3000]
prompt = f"""Extract key security findings from this scan result.
Focus on: secrets, vulnerabilities, risk level, critical issues.
Provide a concise summary (2-3 sentences max).
Tool result:
{output_text}
Security findings:"""
response = await self._call_llm([{"role": "user", "content": prompt}])
if response and not response.startswith("[LLM unavailable"):
return response.strip()
# Fallback: parse structure
if isinstance(output, dict):
summary = output.get("summary", {})
if isinstance(summary, dict):
secrets = summary.get("total_secrets", 0)
vulns = summary.get("total_vulnerabilities", 0)
risk = summary.get("risk_level", "unknown")
return f"Security scan: {secrets} secrets, {vulns} vulnerabilities, risk level: {risk}"
return f"Security result: {str(output)[:800]}"
return f"Got result: {str(output)[:800]}"
async def synthesize(self, task: str) -> str:
"""Synthesize security findings using LLM."""
observations = self.state.observations
if not observations:
return f"Security analysis completed for: {task}. No vulnerabilities found."
observations_text = "\n\n".join(f"Finding {i+1}: {obs}" for i, obs in enumerate(observations[:10]))
prompt = f"""You are a security analysis agent synthesizing findings for:
Task: {task}
Security findings:
{observations_text}
Please synthesize these into a comprehensive security assessment:
1. Critical vulnerabilities found
2. Overall risk assessment
3. Recommendations for remediation
4. Security best practices to implement
Assessment:"""
response = await self._call_llm([{"role": "user", "content": prompt}])
if response and not response.startswith("[LLM unavailable"):
return response.strip()
summary = f"Security Assessment for: {task}\n\n"
summary += f"Analyzed {len(observations)} security findings.\n\n"
for i, obs in enumerate(observations[:5], 1):
summary += f"Finding {i}: {obs[:300]}\n\n"
return summary
async def scan_repo(self, owner: str, repo: str) -> dict[str, Any]:
"""Scan a GitHub repository for security issues."""
tool = tool_registry.get("github_repo_reader")
if tool:
result = await tool.execute(action="get_readme", owner=owner, repo=repo)
return result if isinstance(result, dict) else {"result": str(result)}
return {"error": "GitHub tool not available"}
async def scan_path(self, path: str) -> dict[str, Any]:
"""Scan a local path for security issues."""
tool = tool_registry.get("security_scanner")
if tool:
result = await tool.execute(action="scan_directory", path=path)
return result if isinstance(result, dict) else {"result": str(result)}
return {"error": "Security scanner not available"}