Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gitignore +122 -0
- README.md +100 -12
- alerts_export.csv +8 -0
- app.py +88 -0
- config.py +12 -0
- enrichment.py +19 -0
- llm_classifier.py +31 -0
- log_generator.py +18 -0
- playbook.py +14 -0
- requirements.txt +4 -0
- utils.py +15 -0
.gitignore
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------------------
|
| 2 |
+
# Python
|
| 3 |
+
# ------------------------------
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.py[cod]
|
| 6 |
+
*$py.class
|
| 7 |
+
|
| 8 |
+
# C extensions
|
| 9 |
+
*.so
|
| 10 |
+
*.pyd
|
| 11 |
+
*.dll
|
| 12 |
+
|
| 13 |
+
# ------------------------------
|
| 14 |
+
# Environments
|
| 15 |
+
# ------------------------------
|
| 16 |
+
.venv/
|
| 17 |
+
venv/
|
| 18 |
+
env/
|
| 19 |
+
ENV/
|
| 20 |
+
.venv*/
|
| 21 |
+
venv*/
|
| 22 |
+
env*/
|
| 23 |
+
ENV*/
|
| 24 |
+
.python-version
|
| 25 |
+
|
| 26 |
+
# ------------------------------
|
| 27 |
+
# Distribution / packaging
|
| 28 |
+
# ------------------------------
|
| 29 |
+
.Python
|
| 30 |
+
build/
|
| 31 |
+
dist/
|
| 32 |
+
downloads/
|
| 33 |
+
eggs/
|
| 34 |
+
.eggs/
|
| 35 |
+
sdist/
|
| 36 |
+
wheels/
|
| 37 |
+
share/python-wheels/
|
| 38 |
+
*.egg-info/
|
| 39 |
+
.installed.cfg
|
| 40 |
+
*.egg
|
| 41 |
+
MANIFEST
|
| 42 |
+
pip-wheel-metadata/
|
| 43 |
+
pip-log.txt
|
| 44 |
+
pip-delete-this-directory.txt
|
| 45 |
+
|
| 46 |
+
# ------------------------------
|
| 47 |
+
# Unit test / coverage reports
|
| 48 |
+
# ------------------------------
|
| 49 |
+
htmlcov/
|
| 50 |
+
.tox/
|
| 51 |
+
.nox/
|
| 52 |
+
.coverage
|
| 53 |
+
.coverage.*
|
| 54 |
+
.cache
|
| 55 |
+
nosetests.xml
|
| 56 |
+
coverage.xml
|
| 57 |
+
*.cover
|
| 58 |
+
*.py,cover
|
| 59 |
+
.pytest_cache/
|
| 60 |
+
junit*.xml
|
| 61 |
+
|
| 62 |
+
# ------------------------------
|
| 63 |
+
# Type checkers / linters
|
| 64 |
+
# ------------------------------
|
| 65 |
+
.mypy_cache/
|
| 66 |
+
.dmypy.json
|
| 67 |
+
dmypy.json
|
| 68 |
+
.pyre/
|
| 69 |
+
.pytype/
|
| 70 |
+
.ruff_cache/
|
| 71 |
+
|
| 72 |
+
# ------------------------------
|
| 73 |
+
# PyInstaller
|
| 74 |
+
# ------------------------------
|
| 75 |
+
*.manifest
|
| 76 |
+
*.spec
|
| 77 |
+
|
| 78 |
+
# ------------------------------
|
| 79 |
+
# Jupyter
|
| 80 |
+
# ------------------------------
|
| 81 |
+
.ipynb_checkpoints/
|
| 82 |
+
|
| 83 |
+
# ------------------------------
|
| 84 |
+
# Logs and runtime files
|
| 85 |
+
# ------------------------------
|
| 86 |
+
logs/
|
| 87 |
+
*.log
|
| 88 |
+
*.pid
|
| 89 |
+
*.pid.lock
|
| 90 |
+
|
| 91 |
+
# ------------------------------
|
| 92 |
+
# Local environment variables & secrets
|
| 93 |
+
# ------------------------------
|
| 94 |
+
.env
|
| 95 |
+
.env.*
|
| 96 |
+
!.env.example
|
| 97 |
+
|
| 98 |
+
# ------------------------------
|
| 99 |
+
# Editors / IDEs / Tooling
|
| 100 |
+
# ------------------------------
|
| 101 |
+
.idea/
|
| 102 |
+
*.iml
|
| 103 |
+
.vscode/
|
| 104 |
+
.history/
|
| 105 |
+
.cursor/
|
| 106 |
+
*.code-workspace
|
| 107 |
+
|
| 108 |
+
# ------------------------------
|
| 109 |
+
# OS-specific
|
| 110 |
+
# ------------------------------
|
| 111 |
+
.DS_Store
|
| 112 |
+
Thumbs.db
|
| 113 |
+
ehthumbs.db
|
| 114 |
+
Desktop.ini
|
| 115 |
+
|
| 116 |
+
# ------------------------------
|
| 117 |
+
# Optional local data & temp
|
| 118 |
+
# ------------------------------
|
| 119 |
+
tmp/
|
| 120 |
+
temp/
|
| 121 |
+
data/
|
| 122 |
+
|
README.md
CHANGED
|
@@ -1,12 +1,100 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: actionable
|
| 3 |
+
app_file: app.py
|
| 4 |
+
sdk: gradio
|
| 5 |
+
sdk_version: 5.44.1
|
| 6 |
+
---
|
| 7 |
+
## SOC Dashboard – Live Random Alert Streaming
|
| 8 |
+
|
| 9 |
+
A lightweight SOC-style dashboard that streams synthetic security alerts, enriches them with threat intel and GeoIP, classifies them using an LLM, and surfaces recommended actions. Built with Gradio for a simple, responsive UI.
|
| 10 |
+
|
| 11 |
+
### Features
|
| 12 |
+
- **Live alert stream**: Generates up to 10 synthetic logs per session.
|
| 13 |
+
- **Enrichment**: Adds IP reputation and GeoIP context.
|
| 14 |
+
- **AI classification**: Uses a Gemini-compatible OpenAI client to categorize alerts, set priority, and suggest actions.
|
| 15 |
+
- **Export**: Saves the current session’s alerts to `alerts_export.csv`.
|
| 16 |
+
- **Playbooks**: Simulates actions (e.g., block IP, quarantine host) based on AI suggestions.
|
| 17 |
+
|
| 18 |
+
### Repository Structure
|
| 19 |
+
- `app.py`: Gradio UI and app orchestration.
|
| 20 |
+
- `config.py`: Environment loading and Gemini-compatible OpenAI client initialization.
|
| 21 |
+
- `enrichment.py`: Threat intel and GeoIP enrichment.
|
| 22 |
+
- `llm_classifier.py`: Prompting and parsing for LLM classification.
|
| 23 |
+
- `log_generator.py`: Synthetic log generation.
|
| 24 |
+
- `playbook.py`: Maps AI-recommended actions to simulated playbooks.
|
| 25 |
+
- `utils.py`: Helper utilities for cleaning/parsing model output.
|
| 26 |
+
- `alerts_export.csv`: Created after exporting from the UI.
|
| 27 |
+
|
| 28 |
+
### Requirements
|
| 29 |
+
- Python 3.9+
|
| 30 |
+
- Pip
|
| 31 |
+
|
| 32 |
+
Python dependencies are listed in `requirements.txt`:
|
| 33 |
+
- `pandas`
|
| 34 |
+
- `gradio`
|
| 35 |
+
- `python-dotenv`
|
| 36 |
+
- `openai`
|
| 37 |
+
|
| 38 |
+
### Environment Variables
|
| 39 |
+
Create a `.env` file in the project root with your Gemini API key. This project uses the OpenAI SDK pointed at Google’s Gemini-compatible endpoint.
|
| 40 |
+
|
| 41 |
+
Example `.env`:
|
| 42 |
+
```
|
| 43 |
+
GOOGLE_API_KEY=your_gemini_api_key_here
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### Installation
|
| 47 |
+
1. Clone or download this repository.
|
| 48 |
+
2. Open a terminal in the project directory.
|
| 49 |
+
3. Create and activate a virtual environment (recommended).
|
| 50 |
+
- Windows (PowerShell):
|
| 51 |
+
```powershell
|
| 52 |
+
py -m venv .venv
|
| 53 |
+
.\.venv\Scripts\Activate.ps1
|
| 54 |
+
```
|
| 55 |
+
- macOS/Linux (bash):
|
| 56 |
+
```bash
|
| 57 |
+
python3 -m venv .venv
|
| 58 |
+
source .venv/bin/activate
|
| 59 |
+
```
|
| 60 |
+
4. Install dependencies:
|
| 61 |
+
```bash
|
| 62 |
+
pip install -r requirements.txt
|
| 63 |
+
```
|
| 64 |
+
5. Create the `.env` file as shown above.
|
| 65 |
+
|
| 66 |
+
### Running Locally
|
| 67 |
+
Start the Gradio app:
|
| 68 |
+
```bash
|
| 69 |
+
python app.py
|
| 70 |
+
```
|
| 71 |
+
Gradio will print a local URL (e.g., `http://127.0.0.1:7860`). Open it in your browser.
|
| 72 |
+
|
| 73 |
+
### Using the App
|
| 74 |
+
1. Click **Start Streaming** to begin generating alerts (up to 10 per session).
|
| 75 |
+
2. Watch the table populate with enriched and classified alerts.
|
| 76 |
+
3. Click **Stop Streaming** to halt early.
|
| 77 |
+
4. Click **Export Alerts** to save the current table to `alerts_export.csv` in the project root, then download it from the UI.
|
| 78 |
+
5. Click **Run Playbooks** to simulate actions suggested by the AI; results appear in the text box.
|
| 79 |
+
|
| 80 |
+
### How It Works
|
| 81 |
+
- `log_generator.generate_random_log` produces timestamped events with random IPs and messages.
|
| 82 |
+
- `enrichment.enrich_alert` augments each log with IP reputation and GeoIP info from in-memory lookups.
|
| 83 |
+
- `llm_classifier.classify_alert` sends a structured prompt to the Gemini-compatible endpoint via the OpenAI SDK and returns `{ category, priority, action }`.
|
| 84 |
+
- `app.py` builds the session table and wires up the Gradio UI for starting/stopping, exporting, and running playbooks.
|
| 85 |
+
|
| 86 |
+
### Troubleshooting
|
| 87 |
+
- **No output / classification errors**: Verify `.env` contains a valid `GOOGLE_API_KEY` and you have network connectivity.
|
| 88 |
+
- **Package errors**: Re-create/activate the virtual environment and re-run `pip install -r requirements.txt`.
|
| 89 |
+
- **Port in use**: Set a different port when launching Gradio:
|
| 90 |
+
```python
|
| 91 |
+
# in app.py main block
|
| 92 |
+
demo.queue().launch(server_port=7861)
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
### Notes
|
| 96 |
+
- Exported CSV only includes alerts from the current session.
|
| 97 |
+
- The playbook executions are simulated; no real systems are modified.
|
| 98 |
+
- IP reputation and GeoIP data are in-memory examples for demonstration.
|
| 99 |
+
|
| 100 |
+
|
alerts_export.csv
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Timestamp,Event,Source IP,Reputation,Location,Category,Priority,Action
|
| 2 |
+
2025-09-15 22:35:39,Suspicious admin privilege escalation,185.234.219.45,malicious,Russia,System Compromise / Post-Exploitation,High,"Immediately isolate the affected system(s) from the network, initiate full incident response procedures, block the malicious source IP (185.234.219.45) at the perimeter, and begin forensic analysis to determine the scope and root cause of the privilege escalation and C2 communication."
|
| 3 |
+
2025-09-15 22:36:39,Normal login from corporate network,10.0.0.15,suspicious,China,Compromised Host,High,"Initiate incident response: Immediately isolate host 10.0.0.15 from the network, investigate the specific 'unusual activity' flagged by the honeypot, review associated login details, and begin forensic analysis to determine the scope and impact of the compromise."
|
| 4 |
+
2025-09-15 22:37:39,Excessive DNS queries from single host,185.234.219.45,malicious,Russia,Malware (Command & Control),High,"Immediately isolate the affected internal host. Block the malicious source IP (185.234.219.45) at all perimeter security devices (firewall, DNS sinkhole, IPS/IDS). Initiate a full incident response investigation on the affected host to determine the compromise vector, malware payload, scope of infection, and potential data exfiltration. Scan the internal network for other indicators of compromise related to this C2 server."
|
| 5 |
+
2025-09-15 22:38:39,Normal login from corporate network,192.168.1.10,clean,Private Network (Internal),Benign,Low,Close alert as a normal/expected event.
|
| 6 |
+
2025-09-15 22:39:39,Multiple failed login attempts,192.168.1.10,clean,Private Network (Internal),Brute Force Attempt,Medium,"Investigate the source host (192.168.1.10) to identify the system/user. Review local logs on 192.168.1.10 for any signs of compromise or unusual activity. Identify the target account(s) of the failed login attempts and check their respective authentication logs (e.g., Active Directory, application logs) for the full scope of the attempts (count, frequency, targeted usernames). Contact the identified user/owner of 192.168.1.10 to rule out user error (e.g., forgotten password, misconfigured application)."
|
| 7 |
+
2025-09-15 22:40:39,Normal login from corporate network,10.0.0.15,suspicious,China,Initial Access / Unauthorized Access Attempt,High,Initiate full incident response. Investigate potential IP spoofing or internal host compromise of 10.0.0.15. Immediately isolate the host 10.0.0.15. Review all authentication logs for success/failure related to this timestamp and identify the targeted user account. Conduct comprehensive network and endpoint forensics on 10.0.0.15. Alert Security Operations Center (SOC) team lead and relevant stakeholders.
|
| 8 |
+
2025-09-15 22:41:39,User downloaded large file from unknown domain,192.168.1.10,clean,Private Network (Internal),Benign,Low,"Initiate an investigation into the unknown domain's legitimacy and current reputation (e.g., check with additional threat intelligence sources, perform a sandbox analysis if feasible). Engage directly with the user (192.168.1.10) to understand the purpose and necessity of the large file download. Review organizational policies concerning downloads from unclassified or untrusted sources."
|
app.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
|
| 6 |
+
from enrichment import enrich_alert
|
| 7 |
+
from llm_classifier import classify_alert
|
| 8 |
+
from log_generator import generate_random_log
|
| 9 |
+
from playbook import run_playbook
|
| 10 |
+
|
| 11 |
+
stop_streaming = False
|
| 12 |
+
results_store = []
|
| 13 |
+
|
| 14 |
+
def stream_logs():
|
| 15 |
+
global stop_streaming, results_store
|
| 16 |
+
stop_streaming = False
|
| 17 |
+
results_store = []
|
| 18 |
+
|
| 19 |
+
base_time = datetime.now()
|
| 20 |
+
|
| 21 |
+
for i in range(10):
|
| 22 |
+
if stop_streaming:
|
| 23 |
+
break
|
| 24 |
+
|
| 25 |
+
log = generate_random_log(base_time, i)
|
| 26 |
+
enriched = enrich_alert(log.copy())
|
| 27 |
+
ai_result = classify_alert(enriched)
|
| 28 |
+
|
| 29 |
+
row = {
|
| 30 |
+
"Timestamp": enriched.get("timestamp"),
|
| 31 |
+
"Event": enriched.get("event"),
|
| 32 |
+
"Source IP": enriched.get("source_ip"),
|
| 33 |
+
"Reputation": enriched.get("ip_reputation"),
|
| 34 |
+
"Location": enriched.get("geo_location"),
|
| 35 |
+
"Category": ai_result.get("category"),
|
| 36 |
+
"Priority": ai_result.get("priority"),
|
| 37 |
+
"Action": ai_result.get("action"),
|
| 38 |
+
}
|
| 39 |
+
results_store.append(row)
|
| 40 |
+
|
| 41 |
+
yield pd.DataFrame(results_store)
|
| 42 |
+
time.sleep(3)
|
| 43 |
+
|
| 44 |
+
def stop_logs():
|
| 45 |
+
global stop_streaming
|
| 46 |
+
stop_streaming = True
|
| 47 |
+
return None
|
| 48 |
+
|
| 49 |
+
def export_alerts():
|
| 50 |
+
global results_store
|
| 51 |
+
if not results_store:
|
| 52 |
+
return None
|
| 53 |
+
df = pd.DataFrame(results_store)
|
| 54 |
+
export_path = "alerts_export.csv"
|
| 55 |
+
df.to_csv(export_path, index=False)
|
| 56 |
+
return export_path
|
| 57 |
+
|
| 58 |
+
def execute_playbooks():
|
| 59 |
+
global results_store
|
| 60 |
+
if not results_store:
|
| 61 |
+
return "No alerts to act on."
|
| 62 |
+
actions = [run_playbook(alert) for alert in results_store]
|
| 63 |
+
return "\n".join(actions)
|
| 64 |
+
|
| 65 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 66 |
+
gr.Markdown("## 🛡️ Actionable : SOC Dashboard – Live Alert Prioritization & Triage")
|
| 67 |
+
|
| 68 |
+
with gr.Row():
|
| 69 |
+
start_btn = gr.Button("▶ Start Streaming", variant="primary")
|
| 70 |
+
stop_btn = gr.Button("⏹ Stop Streaming", variant="stop")
|
| 71 |
+
export_btn = gr.Button("💾 Export Alerts", variant="huggingface")
|
| 72 |
+
playbook_btn = gr.Button("⚡ Run Playbooks")
|
| 73 |
+
|
| 74 |
+
output_table = gr.Dataframe(
|
| 75 |
+
headers=["Timestamp", "Event", "Source IP", "Reputation", "Location", "Category", "Priority", "Action"],
|
| 76 |
+
wrap=True
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
download_file = gr.File(label="Download Exported Alerts")
|
| 80 |
+
playbook_output = gr.Textbox(label="Playbook Execution Log", lines=8)
|
| 81 |
+
|
| 82 |
+
start_btn.click(fn=stream_logs, outputs=output_table)
|
| 83 |
+
stop_btn.click(fn=stop_logs, outputs=None)
|
| 84 |
+
export_btn.click(fn=export_alerts, outputs=download_file)
|
| 85 |
+
playbook_btn.click(fn=execute_playbooks, outputs=playbook_output)
|
| 86 |
+
|
| 87 |
+
if __name__ == "__main__":
|
| 88 |
+
demo.queue().launch()
|
config.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
from openai import OpenAI
|
| 4 |
+
|
| 5 |
+
load_dotenv(override=True)
|
| 6 |
+
|
| 7 |
+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 8 |
+
|
| 9 |
+
client = OpenAI(
|
| 10 |
+
api_key=GOOGLE_API_KEY,
|
| 11 |
+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
|
| 12 |
+
)
|
enrichment.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
threat_intel_db = {
|
| 2 |
+
"185.234.219.45": {"reputation": "malicious", "reason": "Known command & control server"},
|
| 3 |
+
"10.0.0.15": {"reputation": "suspicious", "reason": "Unusual activity flagged in honeypot"},
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
geoip_db = {
|
| 7 |
+
"192.168.1.10": "Private Network (Internal)",
|
| 8 |
+
"185.234.219.45": "Russia",
|
| 9 |
+
"10.0.0.15": "China",
|
| 10 |
+
"172.16.0.5": "Private Network (Internal)"
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
def enrich_alert(log_entry):
|
| 14 |
+
ip = log_entry.get("source_ip", "")
|
| 15 |
+
threat_info = threat_intel_db.get(ip, {"reputation": "clean", "reason": "No malicious activity reported"})
|
| 16 |
+
log_entry["ip_reputation"] = threat_info["reputation"]
|
| 17 |
+
log_entry["intel_note"] = threat_info["reason"]
|
| 18 |
+
log_entry["geo_location"] = geoip_db.get(ip, "Unknown Location")
|
| 19 |
+
return log_entry
|
llm_classifier.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from config import client
|
| 2 |
+
from utils import clean_ai_response, parse_json_safe
|
| 3 |
+
|
| 4 |
+
def classify_alert(log_entry):
|
| 5 |
+
prompt = f"""
|
| 6 |
+
You are a SOC (Security Operations Center) AI assistant.
|
| 7 |
+
Analyze the following security alert and classify it.
|
| 8 |
+
|
| 9 |
+
Alert details:
|
| 10 |
+
Timestamp: {log_entry.get('timestamp')}
|
| 11 |
+
Source IP: {log_entry.get('source_ip')}
|
| 12 |
+
Event: {log_entry.get('event')}
|
| 13 |
+
Threat Intelligence: Reputation = {log_entry.get('ip_reputation')}, Note = {log_entry.get('intel_note')}
|
| 14 |
+
GeoIP: {log_entry.get('geo_location')}
|
| 15 |
+
|
| 16 |
+
Tasks:
|
| 17 |
+
1. Categorize the attack type (e.g., brute force, malware, data exfiltration, benign).
|
| 18 |
+
2. Assign a priority (High, Medium, Low).
|
| 19 |
+
3. Suggest next action.
|
| 20 |
+
|
| 21 |
+
Return response in JSON with keys: category, priority, action.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
response = client.chat.completions.create(
|
| 25 |
+
model="gemini-2.5-flash",
|
| 26 |
+
messages=[{"role": "user", "content": prompt}],
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
raw_output = response.choices[0].message.content
|
| 30 |
+
cleaned = clean_ai_response(raw_output)
|
| 31 |
+
return parse_json_safe(cleaned)
|
log_generator.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from datetime import datetime, timedelta
|
| 3 |
+
ips = ["192.168.1.10", "185.234.219.45", "10.0.0.15", "172.16.0.5"]
|
| 4 |
+
events = [
|
| 5 |
+
"Multiple failed login attempts",
|
| 6 |
+
"Unusual outbound traffic to known bad IP",
|
| 7 |
+
"User downloaded large file from unknown domain",
|
| 8 |
+
"Normal login from corporate network",
|
| 9 |
+
"Suspicious admin privilege escalation",
|
| 10 |
+
"Excessive DNS queries from single host"
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
def generate_random_log(base_time, i):
|
| 14 |
+
return {
|
| 15 |
+
"timestamp": (base_time + timedelta(minutes=i)).strftime("%Y-%m-%d %H:%M:%S"),
|
| 16 |
+
"source_ip": random.choice(ips),
|
| 17 |
+
"event": random.choice(events)
|
| 18 |
+
}
|
playbook.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def run_playbook(alert):
|
| 2 |
+
action = alert.get("Action", "").lower()
|
| 3 |
+
ip = alert.get("Source IP", "Unknown")
|
| 4 |
+
|
| 5 |
+
if "block ip" in action:
|
| 6 |
+
return f"🔒 Simulated: Blocking IP {ip} in firewall"
|
| 7 |
+
elif "quarantine" in action:
|
| 8 |
+
return f"🛡️ Simulated: Quarantining host {ip} via EDR"
|
| 9 |
+
elif "escalate" in action or "alert" in action:
|
| 10 |
+
return f"📢 Simulated: Escalating alert for {ip} to Tier-2 SOC"
|
| 11 |
+
elif "no action" in action or "benign" in action:
|
| 12 |
+
return f"✅ Simulated: No action needed for {ip}"
|
| 13 |
+
else:
|
| 14 |
+
return f"⚙️ Simulated: Generic action executed for {ip}"
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pandas
|
| 2 |
+
gradio
|
| 3 |
+
python-dotenv
|
| 4 |
+
openai
|
utils.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
def clean_ai_response(text: str) -> str:
|
| 5 |
+
if not text:
|
| 6 |
+
return text
|
| 7 |
+
text = re.sub(r"^```[a-zA-Z]*\n?", "", text.strip())
|
| 8 |
+
text = re.sub(r"\n?```$", "", text.strip())
|
| 9 |
+
return text.strip()
|
| 10 |
+
|
| 11 |
+
def parse_json_safe(text: str) -> dict:
|
| 12 |
+
try:
|
| 13 |
+
return json.loads(text)
|
| 14 |
+
except Exception:
|
| 15 |
+
return {"category": "Unknown", "priority": "Unknown", "action": "Parsing failed"}
|