Compare commits

...

7 Commits

Author SHA1 Message Date
ff7bbb98d0 feat: Filter out temperature fluctuations with differences less than 5 degrees 2025-08-21 13:20:03 -05:00
57d7688c3a feat: Filter out small RTT fluctuations in LLM prompt
- Update the LLM prompt to instruct it to ignore RTT fluctuations below 10 seconds.
- Update PROGRESS.md to reflect the completion of the task.
2025-08-21 12:34:12 -05:00
83b25d81a6 feat: Add hostname resolution to Nmap scans
- Add -R flag to Nmap scan options to enable reverse DNS lookup.
- Modify the Nmap processing logic to extract and store hostnames.
- Update PROGRESS.md to reflect the completion of the task.
2025-08-21 12:32:40 -05:00
7e24379fa1 feat: Restrict alerts to a defined time window
- Add a function to check if the current time is within the alerting window (9am - 12am).
- Modify the alerting logic to only send alerts during this window.
- Update PROGRESS.md to reflect the completion of the task.
2025-08-21 12:28:08 -05:00
d03018de9b feat: Log LLM responses to console
- Add a StreamHandler to the logger to output all logs to the console.
- Log the LLM response to the console for real-time monitoring.
- Update PROGRESS.md to reflect the completion of the task.
- Fix a syntax error in monitor_agent.py.
2025-08-21 12:18:08 -05:00
f65b2d468d feat: Implement daily log rotation
- Add logging to monitor_agent.py to replace print statements.
- Configure TimedRotatingFileHandler to keep logs for the past 24 hours.
- Update .gitignore to exclude the new log file.
- Update PROGRESS.md to reflect the completion of the task.
2025-08-21 12:15:36 -05:00
e119bc7194 feat: Update baseline calculations and LLM prompts
- Change baseline calculations to use integers instead of floats to simplify data.
- Update LLM constraints and prompt for more accurate anomaly detection.
- Refine known_issues to reduce false positives.
- Update PROGRESS.md with new TODO items.
2025-08-21 12:12:15 -05:00
7 changed files with 71 additions and 37 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,4 @@ __pycache__/
monitoring_data.json
log_position.txt
auth_log_position.txt
monitor_agent.log
monitoring_agent.log

View File

@@ -1,6 +1,8 @@
## LLM Constraints and Guidelines
- Not everything is an anamoly. Err on the side of caution when selecting severity. Its ok not to report anything. You don't have to say anything if you don't want to, or don't need to.
- Please do not report on anything that is older then 24 hours.
- The server uses a custom DNS server at 192.168.2.112.
- Please think carefully on if the measured values exceed the averages by any significant margin. A few seconds, or a few degrees in difference do not mean a significant margin. Only report anomolies with delta values greater then 10.
### Important Things to Focus On:
- Security-related events such as failed login attempts, unauthorized access, or unusual network connections.

View File

@@ -60,10 +60,19 @@
36. [x] Create helper function in `data_storage.py` for calculating average metrics.
37. [x] Update `README.md` with current project status and improvements.
38. [x] Create `AGENTS.md` to document human and autonomous agents.
## Keeping track of Current Objectives
[x] Improve "high" priority detection by explicitly instructing LLM to output severity in structured JSON format.
[x] Implement dynamic contextual information (Known/Resolved Issues Feed) for LLM to improve severity detection.
## TODO
- [x] Change baseline calculations to only use integers instead of floats.
- [x] Add a log file that only keeps records for the past 24 hours.
- [x] Log all LLM responses to the console.
- [x] Reduce alerts to only happen between 9am and 12am.
- [x] Get hostnames of devices in Nmap scan.
- [x] Filter out RTT fluctuations below 10 seconds.
- [x] Filter out temperature fluctuations with differences less than 5 degrees.
- [ ] Create a list of known port numbers and their applications for the LLM to check against to see if an open port is a threat

View File

@@ -13,7 +13,7 @@ DAILY_RECAP_TIME = "20:00"
# Nmap Configuration
NMAP_TARGETS = "192.168.2.0/24"
NMAP_SCAN_OPTIONS = "-sS -T4"
NMAP_SCAN_OPTIONS = "-sS -T4 -R"
# Test Mode (True to run once and exit, False to run continuously)
TEST_MODE = False

View File

@@ -19,7 +19,7 @@ def store_data(new_data):
def _calculate_average(data, key1, key2):
"""Helper function to calculate the average of a nested key in a list of dicts."""
values = [d[key1][key2] for d in data if key1 in d and key2 in d[key1] and d[key1][key2] != "N/A"]
return sum(values) / len(values) if values else 0
return int(sum(values) / len(values)) if values else 0
def calculate_baselines():
data = load_data()

View File

@@ -13,7 +13,7 @@
},
{
"issue": "Port 62078 is open",
"resolution": "Port 62078 is used in apple devices for syncing communcation between each other. This is not an amomaly, this is expected and normal behavior used by Apple Devices to communicate."
"resolution": "This is normal behavior for Apple devices. Do not report."
},
{
"issue": "RTT averages are higher then average",

View File

@@ -12,12 +12,31 @@ import os
from datetime import datetime, timezone
import pingparsing
import nmap
import logging
from logging.handlers import TimedRotatingFileHandler
# Load configuration
import config
from syslog_rfc5424_parser import parser
# --- Logging Configuration ---
LOG_FILE = "monitoring_agent.log"
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Create a handler that rotates logs daily, keeping 1 backup
file_handler = TimedRotatingFileHandler(LOG_FILE, when="midnight", interval=1, backupCount=1)
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
# Create a handler for console output
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(file_handler)
logger.addHandler(console_handler)
LOG_POSITION_FILE = 'log_position.txt'
AUTH_LOG_POSITION_FILE = 'auth_log_position.txt'
@@ -49,10 +68,10 @@ def get_system_logs():
return {"syslog": parsed_logs}
except FileNotFoundError:
print("Error: /var/log/syslog not found.")
logger.error("/var/log/syslog not found.")
return {"syslog": []}
except Exception as e:
print(f"Error reading syslog: {e}")
logger.error(f"Error reading syslog: {e}")
return {"syslog": []}
@@ -66,7 +85,7 @@ def get_network_metrics():
result = transmitter.ping()
return ping_parser.parse(result).as_dict()
except Exception as e:
print(f"Error getting network metrics: {e}")
logger.error(f"Error getting network metrics: {e}")
return {"error": "ping command failed"}
def get_sensor_data():
@@ -74,7 +93,7 @@ def get_sensor_data():
try:
return subprocess.check_output(["sensors"], text=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("Error: 'sensors' command not found. Please install lm-sensors.")
logger.error("'sensors' command not found. Please install lm-sensors.")
return None
def get_cpu_temperature(sensors_output):
@@ -105,7 +124,6 @@ def get_gpu_temperature(sensors_output):
return {"gpu_temperature": "N/A"}
def get_login_attempts():
"""Gets system login attempts from /var/log/auth.log since the last check."""
try:
@@ -129,10 +147,10 @@ def get_login_attempts():
return {"failed_login_attempts": failed_logins}
except FileNotFoundError:
print("Error: /var/log/auth.log not found.")
logger.error("/var/log/auth.log not found.")
return {"failed_login_attempts": []}
except Exception as e:
print(f"Error reading login attempts: {e}")
logger.error(f"Error reading login attempts: {e}")
return {"failed_logins": []}
def get_nmap_scan_results():
@@ -141,7 +159,7 @@ def get_nmap_scan_results():
nm = nmap.PortScanner()
scan_options = config.NMAP_SCAN_OPTIONS
if os.geteuid() != 0 and "-sS" in scan_options:
print("Warning: Nmap -sS scan requires root privileges. Falling back to -sT.")
logger.warning("Nmap -sS scan requires root privileges. Falling back to -sT.")
scan_options = scan_options.replace("-sS", "-sT")
scan_results = nm.scan(hosts=config.NMAP_TARGETS, arguments=scan_options)
@@ -153,6 +171,7 @@ def get_nmap_scan_results():
host_info = {
"ip": host,
"status": scan_data.get("status", {}).get("state", "unknown"),
"hostname": scan_data.get("hostnames", [{}])[0].get("name", ""),
"open_ports": []
}
if "tcp" in scan_data:
@@ -168,7 +187,7 @@ def get_nmap_scan_results():
return processed_results
except Exception as e:
print(f"Error performing Nmap scan: {e}")
logger.error(f"Error performing Nmap scan: {e}")
return {"error": "Nmap scan failed"}
# --- LLM Interaction Function ---
@@ -178,7 +197,7 @@ def build_llm_prompt(data, baselines, nmap_changes, constraints, known_issues):
return f"""
**Role:** You are a dedicated and expert system administrator. Your primary role is to identify anomalies and provide concise, actionable reports.
**Instruction:** Analyze the following system and network data for any activity that appears out of place or different. Consider unusual values, errors, or unexpected patterns as anomalies. Compare the current data with the historical baseline data to identify significant deviations. Consult the known issues feed to avoid flagging resolved or expected issues. Pay special attention to the Nmap scan results for any new or unexpected open ports.
**Instruction:** Analyze the following system and network data for any activity that appears out of place or different. Consider unusual values, errors, or unexpected patterns as anomalies. Compare the current data with the historical baseline data to identify significant deviations. Consult the known issues feed to avoid flagging resolved or expected issues. Pay special attention to the Nmap scan results for any new or unexpected open ports. Pay special attention to network RTT fluctuations, but only report them as an anomaly if the fluctuation is greater than 10 seconds. Similarly, only report temperature fluctuations if the difference is greater than 5 degrees.
**Context:**
Here is the system data in JSON format for your analysis: {json.dumps(data, indent=2)}
@@ -195,7 +214,8 @@ def build_llm_prompt(data, baselines, nmap_changes, constraints, known_issues):
**Constraints and Guidelines:**
{constraints}
**Output Request:** If you find an anomaly, provide a report as a single JSON object with two keys: "severity" and "reason". The "severity" must be one of "high", "medium", "low", or "none". The "reason" must be a natural language explanation of the anomaly. Please include specific values if the anomoly has them. If no anomaly is found, return a single JSON object with "severity" set to "none" and "reason" as an empty string. Do not wrap the JSON in markdown or any other formatting.
**Output Request:** If you find an anomaly, provide a report as a single JSON object with two keys: "severity" and "reason". The "severity" must be one of "high", "medium", "low", or "none". The "reason" must be a natural language explanation of the anomaly. Please include specific values if the anomoly has them. If no anomaly is found, return a single JSON object with "severity" set to "none" and "reason" as an empty string. Do not wrap the JSON in markdown or any other formatting. Only return the JSON, and nothing else.
**Reasoning Hint:** Think step by step to come to your conclusion. This is very important.
"""
@@ -243,18 +263,20 @@ def analyze_data_with_llm(data, baselines):
end_index = sanitized_response.rfind('}')
if start_index != -1 and end_index != -1:
json_string = sanitized_response[start_index:end_index+1]
return json.loads(json_string)
llm_response = json.loads(json_string)
logger.info(f"LLM Response: {llm_response}")
return llm_response
else:
# Handle cases where the response is not valid JSON
print(f"LLM returned a non-JSON response: {sanitized_response}")
logger.warning(f"LLM returned a non-JSON response: {sanitized_response}")
return {"severity": "low", "reason": sanitized_response}
except json.JSONDecodeError as e:
print(f"Error decoding LLM response: {e}")
logger.error(f"Error decoding LLM response: {e}")
# Fallback for invalid JSON
return {"severity": "low", "reason": sanitized_response}
except Exception as e:
print(f"Error interacting with LLM: {e}")
logger.error(f"Error interacting with LLM: {e}")
return None
@@ -266,11 +288,11 @@ def send_discord_alert(message):
try:
response = webhook.execute()
if response.status_code == 200:
print("Discord alert sent successfully.")
logger.info("Discord alert sent successfully.")
else:
print(f"Error sending Discord alert: {response.status_code} - {response.content}")
logger.error(f"Error sending Discord alert: {response.status_code} - {response.content}")
except Exception as e:
print(f"Error sending Discord alert: {e}")
logger.error(f"Error sending Discord alert: {e}")
def send_google_home_alert(message):
"""Sends an alert to a Google Home speaker via Home Assistant."""
@@ -279,8 +301,8 @@ def send_google_home_alert(message):
response = ollama.generate(model="llama3.1:8b", prompt=f"Summarize the following message in a single sentence: {message}")
simplified_message = response['response'].strip()
except Exception as e:
print(f"Error summarizing message: {e}")
simplified_message = message.split('.')[0] # Take the first sentence as a fallback
logger.error(f"Error summarizing message: {e}")
simplified_.message = message.split('.')[0] # Take the first sentence as a fallback
url = f"{config.HOME_ASSISTANT_URL}/api/services/tts/speak"
headers = {
@@ -295,19 +317,24 @@ def send_google_home_alert(message):
try:
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
print("Google Home alert sent successfully.")
logger.info("Google Home alert sent successfully.")
else:
print(f"Error sending Google Home alert: {response.status_code} - {response.text}")
logger.error(f"Error sending Google Home alert: {response.status_code} - {response.text}")
except Exception as e:
print(f"Error sending Google Home alert: {e}")
logger.error(f"Error sending Google Home alert: {e}")
# --- Main Script Logic ---
def is_alerting_time():
"""Checks if the current time is within the alerting window (9am - 12am)."""
current_hour = datetime.now().hour
return 9 <= current_hour < 24
daily_events = []
def run_monitoring_cycle(nmap_scan_counter):
"""Runs a single monitoring cycle."""
print("Running monitoring cycle...")
logger.info("Running monitoring cycle...")
system_logs = get_system_logs()
network_metrics = get_network_metrics()
sensors_output = get_sensor_data()
@@ -340,7 +367,7 @@ def run_monitoring_cycle(nmap_scan_counter):
if llm_response and llm_response.get('severity') != "none":
daily_events.append(llm_response.get('reason'))
if llm_response.get('severity') == "high":
if llm_response.get('severity') == "high" and is_alerting_time():
send_discord_alert(llm_response.get('reason'))
send_google_home_alert(llm_response.get('reason'))
return nmap_scan_counter
@@ -348,7 +375,7 @@ def run_monitoring_cycle(nmap_scan_counter):
def main():
"""Main function to run the monitoring agent."""
if config.TEST_MODE:
print("Running in test mode...")
logger.info("Running in test mode...")
run_monitoring_cycle(0)
else:
nmap_scan_counter = 0
@@ -366,7 +393,3 @@ def main():
if __name__ == "__main__":
main()