Upload 5 files
Browse filesThis is a VM security build integrated with an obliterated uncensored local and private AI agent.
- README.md +53 -3
- agent_loop.py +61 -0
- mcp_server.py +77 -0
- request.json +5 -0
- tools_manifest.json +12 -0
README.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Autonomous Security Agent
|
| 2 |
+
|
| 3 |
+
A self-contained security agent built with Qwen 2.5-7B running locally via LM Studio on Kali Linux. The agent can autonomously execute security tools, analyze results, and take action through an MCP (Model Context Protocol) server.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Local LLM Backend** β Qwen 2.5-7B served via LM Studio at `192.168.0.39:1234`
|
| 8 |
+
- **Autonomous Tool Execution** β Runs security tools (nmap, masscan) through MCP
|
| 9 |
+
- **Agent Loop** β Continuous reasoning and decision-making
|
| 10 |
+
- **MCP Server** β Tool chain execution with `run_masscan`, `run_nmap`, `write_file`, `read_file`
|
| 11 |
+
|
| 12 |
+
## Components
|
| 13 |
+
|
| 14 |
+
- `agent_loop.py` β Main agent reasoning loop
|
| 15 |
+
- `mcp_server.py` β Tool execution server
|
| 16 |
+
- `tools_manifest.json` β Tool definitions
|
| 17 |
+
- `request.json` β Sample request format
|
| 18 |
+
|
| 19 |
+
## Security Setup
|
| 20 |
+
|
| 21 |
+
### Firewall Configuration
|
| 22 |
+
|
| 23 |
+
- **Outbound**: All traffic allowed
|
| 24 |
+
- **Inbound**: All traffic blocked (default deny)
|
| 25 |
+
- **IDS**: Suricata for behavioral alerting
|
| 26 |
+
|
| 27 |
+
### Network Security
|
| 28 |
+
|
| 29 |
+
- TOR integration for privacy
|
| 30 |
+
- Local-only LLM inference (no external API calls)
|
| 31 |
+
- MCP server bound to localhost only
|
| 32 |
+
|
| 33 |
+
## Installation & Setup
|
| 34 |
+
|
| 35 |
+
1. Install Kali Linux with Suricata
|
| 36 |
+
2. Install LM Studio and load Qwen 2.5-7B
|
| 37 |
+
3. Configure firewall rules (see docs/firewall-setup.md)
|
| 38 |
+
4. Clone this repository
|
| 39 |
+
5. Install Python dependencies
|
| 40 |
+
6. Run the agent: `python agent_loop.py`
|
| 41 |
+
|
| 42 |
+
## Documentation
|
| 43 |
+
|
| 44 |
+
See the `docs/` folder for:
|
| 45 |
+
|
| 46 |
+
- Detailed setup instructions
|
| 47 |
+
- Firewall rule examples
|
| 48 |
+
- Suricata configuration
|
| 49 |
+
- MCP server setup
|
| 50 |
+
|
| 51 |
+
## License
|
| 52 |
+
|
| 53 |
+
MIT
|
agent_loop.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
OLLAMA_URL = "http://192.168.0.39:1234/v1/chat/completions"
|
| 5 |
+
MCP_URL = "http://localhost:8000"
|
| 6 |
+
|
| 7 |
+
def call_model(goal):
|
| 8 |
+
payload = {
|
| 9 |
+
"model": "qwen",
|
| 10 |
+
"messages": [
|
| 11 |
+
{
|
| 12 |
+
"role": "user",
|
| 13 |
+
"content": (
|
| 14 |
+
"Generate ONLY JSON. Valid tools: run_command, run_masscan, run_nmap, write_file, read_file.\n"
|
| 15 |
+
"Example: {\"chain\": [{\"tool\": \"run_masscan\", \"target\": \"192.168.1.100\", \"ports\": \"1-443\"}]}\n"
|
| 16 |
+
"Goal: " + goal
|
| 17 |
+
)
|
| 18 |
+
}
|
| 19 |
+
],
|
| 20 |
+
"temperature": 0.05
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
response = requests.post(OLLAMA_URL, json=payload, timeout=30)
|
| 25 |
+
content = response.json()["choices"][0]["message"]["content"]
|
| 26 |
+
content = content.replace("```json", "").replace("```", "").strip()
|
| 27 |
+
return json.loads(content)
|
| 28 |
+
except Exception as e:
|
| 29 |
+
print(f"Error parsing: {e}")
|
| 30 |
+
return {"chain": []}
|
| 31 |
+
|
| 32 |
+
def execute_chain(chain):
|
| 33 |
+
print("\nEXECUTING:\n")
|
| 34 |
+
for i, step in enumerate(chain, 1):
|
| 35 |
+
print(f"{i}. {step}")
|
| 36 |
+
try:
|
| 37 |
+
result = requests.post(MCP_URL, json=step, timeout=30)
|
| 38 |
+
print(f"Result: {result.json()}\n")
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"Error: {e}\n")
|
| 41 |
+
|
| 42 |
+
def main():
|
| 43 |
+
print("INTELLIGENT SECURITY AGENT\n")
|
| 44 |
+
while True:
|
| 45 |
+
goal = input("Goal: ").strip()
|
| 46 |
+
if goal.lower() == "exit":
|
| 47 |
+
break
|
| 48 |
+
if not goal:
|
| 49 |
+
continue
|
| 50 |
+
|
| 51 |
+
data = call_model(goal)
|
| 52 |
+
chain = data.get("chain", [])
|
| 53 |
+
|
| 54 |
+
if chain:
|
| 55 |
+
print(f"\nChain: {chain}")
|
| 56 |
+
execute_chain(chain)
|
| 57 |
+
else:
|
| 58 |
+
print("No chain generated")
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
main()
|
mcp_server.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import subprocess, json, sys
|
| 3 |
+
from flask import Flask, request, jsonify
|
| 4 |
+
import logging
|
| 5 |
+
app = Flask(__name__)
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
SUPPORTED_TOOLS = ["run_command", "run_masscan", "run_nmap", "run_netstat", "run_sqlmap", "run_nikto", "run_hydra", "run_searchsploit", "run_curl", "run_wget", "write_file", "read_file"]
|
| 9 |
+
PRIVILEGED_TOOLS = {"masscan", "nmap", "arp-scan", "wireshark", "tcpdump", "iptables", "ip6tables", "ufw", "hashcat", "airmon-ng", "aircrack-ng", "hydra", "metasploit", "burpsuite"}
|
| 10 |
+
class ToolExecutor:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.execution_log = []
|
| 13 |
+
self.error_recovery_attempts = {}
|
| 14 |
+
def execute_tool(self, tool, params):
|
| 15 |
+
if tool == "run_command": return self._run_command(params.get("command", ""))
|
| 16 |
+
elif tool == "run_masscan": return self._run_masscan(params.get("target", ""), params.get("ports", "1-65535"), params.get("rate", "1000"))
|
| 17 |
+
elif tool == "run_nmap": return self._run_nmap(params.get("target", ""), params.get("flags", "-sV"))
|
| 18 |
+
elif tool == "run_netstat": return self._run_netstat(params.get("flags", "-tuln"))
|
| 19 |
+
elif tool == "write_file": return self._write_file(params.get("filename", ""), params.get("content", ""))
|
| 20 |
+
elif tool == "read_file": return self._read_file(params.get("filename", ""))
|
| 21 |
+
return {"status": "error", "error_type": "unsupported_tool", "message": f"Tool '{tool}' not supported"}
|
| 22 |
+
def _execute_command(self, command, retry_with_sudo=False):
|
| 23 |
+
if retry_with_sudo and not command.strip().startswith("sudo"): command = f"sudo {command}"
|
| 24 |
+
try:
|
| 25 |
+
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=300)
|
| 26 |
+
if result.returncode == 0: return {"status": "success", "stdout": result.stdout.strip(), "stderr": result.stderr.strip()}
|
| 27 |
+
else:
|
| 28 |
+
stderr = result.stderr.lower()
|
| 29 |
+
if "permission denied" in stderr or "operation not permitted" in stderr:
|
| 30 |
+
if not retry_with_sudo: return self._execute_command(command, retry_with_sudo=True)
|
| 31 |
+
return {"status": "error", "error_type": "permission_denied", "message": result.stderr}
|
| 32 |
+
elif "not found" in stderr: return {"status": "error", "error_type": "command_not_found", "message": result.stderr}
|
| 33 |
+
else: return {"status": "error", "error_type": "command_failed", "message": result.stderr if result.stderr else result.stdout}
|
| 34 |
+
except subprocess.TimeoutExpired: return {"status": "error", "error_type": "timeout", "message": "Command timed out"}
|
| 35 |
+
except Exception as e: return {"status": "error", "error_type": "execution_error", "message": str(e)}
|
| 36 |
+
def _run_command(self, command):
|
| 37 |
+
if not command: return {"status": "error", "error_type": "invalid_params", "message": "No command"}
|
| 38 |
+
result = self._execute_command(command)
|
| 39 |
+
self.execution_log.append({"tool": "run_command", "result": result})
|
| 40 |
+
return result
|
| 41 |
+
def _run_masscan(self, target, ports, rate):
|
| 42 |
+
if not target: return {"status": "error", "error_type": "invalid_params", "message": "No target"}
|
| 43 |
+
command = f"masscan {target} -p {ports} --rate {rate}"
|
| 44 |
+
result = self._execute_command(command)
|
| 45 |
+
self.execution_log.append({"tool": "run_masscan", "result": result})
|
| 46 |
+
return result
|
| 47 |
+
def _run_nmap(self, target, flags):
|
| 48 |
+
if not target: return {"status": "error", "error_type": "invalid_params", "message": "No target"}
|
| 49 |
+
command = f"nmap {flags} {target}"
|
| 50 |
+
result = self._execute_command(command)
|
| 51 |
+
self.execution_log.append({"tool": "run_nmap", "result": result})
|
| 52 |
+
return result
|
| 53 |
+
def _run_netstat(self, flags):
|
| 54 |
+
command = f"netstat {flags}"
|
| 55 |
+
result = self._execute_command(command)
|
| 56 |
+
self.execution_log.append({"tool": "run_netstat", "result": result})
|
| 57 |
+
return result
|
| 58 |
+
def _write_file(self, filename, content):
|
| 59 |
+
if not filename: return {"status": "error", "message": "No filename"}
|
| 60 |
+
try:
|
| 61 |
+
with open(filename, 'w') as f: f.write(content)
|
| 62 |
+
return {"status": "success", "message": f"File written", "filename": filename}
|
| 63 |
+
except Exception as e: return {"status": "error", "message": str(e)}
|
| 64 |
+
def _read_file(self, filename):
|
| 65 |
+
if not filename: return {"status": "error", "message": "No filename"}
|
| 66 |
+
try:
|
| 67 |
+
with open(filename, 'r') as f: content = f.read()
|
| 68 |
+
return {"status": "success", "filename": filename, "content": content}
|
| 69 |
+
except Exception as e: return {"status": "error", "message": str(e)}
|
| 70 |
+
executor = ToolExecutor()
|
| 71 |
+
@app.route('/', methods=['POST'])
|
| 72 |
+
def execute():
|
| 73 |
+
try:
|
| 74 |
+
data = request.get_json()
|
| 75 |
+
if not data:ββββββββββββββββ
|
| 76 |
+
|
| 77 |
+
|
request.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tool": "write_file",
|
| 3 |
+
"filename": "auto_ai.txt",
|
| 4 |
+
"content": "This was created automatically"
|
| 5 |
+
}
|
tools_manifest.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tools": [
|
| 3 |
+
{
|
| 4 |
+
"name": "write_file",
|
| 5 |
+
"description": "Writes text content to a file on the local system",
|
| 6 |
+
"input": {
|
| 7 |
+
"filename": "string",
|
| 8 |
+
"content": "string"
|
| 9 |
+
}
|
| 10 |
+
}
|
| 11 |
+
]
|
| 12 |
+
}
|