Devesh Punjabi commited on
Commit
e5ec528
·
verified ·
1 Parent(s): a720fa9

Upload 14 files

Browse files
app.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import gradio as gr
3
+ from sql_injection.sql_injection import run_sqlmap
4
+ from qr_detector.qr_detector import qr_code_audit_app
5
+ from phishing_checker.phishing_checker import phishing_url_checker, phishing_email_checker, phishing_sms_checker
6
+ from data_breach_checker.password_checker import gradio_password_strength, gradio_generate_password, gradio_breach_checker
7
+ from vulnerability_scanner.vulnerability_scanner import vulnerability_scanner
8
+ from encryption_tool.encryption_tool import generate_key, encrypt_message, decrypt_message
9
+
10
+ # Create the Gradio interface
11
+ with gr.Blocks() as demo:
12
+ gr.Markdown("# 🛡️ CyberSuite Toolkit Dashboard")
13
+
14
+ with gr.Tab("SQL Injection"):
15
+ gr.Interface(
16
+ fn=run_sqlmap,
17
+ inputs=gr.Textbox(label="Enter URL to test for SQL Injection"),
18
+ outputs="file",
19
+ title="SQL Injection Testing with SQLmap",
20
+ description="Test a URL for SQL Injection using SQLmap automation. Download the results after the scan."
21
+ )
22
+
23
+ with gr.Tab("QR Detector"):
24
+ gr.Interface(
25
+ fn=qr_code_audit_app,
26
+ inputs=[gr.Image(label="Upload or Capture QR Code"), gr.Textbox(label="urlscan.io API Key (Optional)")],
27
+ outputs=[
28
+ gr.Textbox(label="Analysis Result"),
29
+ gr.Textbox(label="Decoded QR Data"),
30
+ gr.Textbox(label="Link Category"),
31
+ gr.Textbox(label="URL Type (Safe, Potential Issue, Malicious)"),
32
+ gr.Textbox(label="urlscan.io Report Link")
33
+ ]
34
+ )
35
+
36
+ with gr.Tab("Phishing Detection"):
37
+ with gr.Tab("URL Phishing Checker"):
38
+ gr.Interface(fn=phishing_url_checker, inputs=[
39
+ gr.Textbox(label="Enter URL", placeholder="https://example.com"),
40
+ gr.Textbox(label="urlscan.io API Key (Optional)")
41
+ ], outputs="text")
42
+ with gr.Tab("Email Phishing Checker"):
43
+ gr.Interface(fn=phishing_email_checker, inputs="text", outputs="text")
44
+ with gr.Tab("SMS Phishing Checker"):
45
+ gr.Interface(fn=phishing_sms_checker, inputs="text", outputs="text")
46
+
47
+ with gr.Tab("Password & Data Breach Management"):
48
+ with gr.Tab("Check Password Strength"):
49
+ gr.Interface(fn=gradio_password_strength, inputs="text", outputs="text")
50
+ with gr.Tab("Generate Password"):
51
+ gr.Interface(fn=gradio_generate_password, inputs=[
52
+ gr.Slider(label="Password Length", minimum=8, maximum=64, step=1, value=12),
53
+ gr.Checkbox(label="Include Uppercase Letters", value=True),
54
+ gr.Checkbox(label="Include Lowercase Letters", value=True),
55
+ gr.Checkbox(label="Include Digits", value=True),
56
+ gr.Checkbox(label="Include Special Characters", value=True)
57
+ ], outputs="text")
58
+ with gr.Tab("Check Password Breach"):
59
+ gr.Interface(fn=gradio_breach_checker, inputs="text", outputs="text")
60
+
61
+ with gr.Tab("Vulnerability Scanner"):
62
+ gr.Interface(
63
+ fn=vulnerability_scanner,
64
+ inputs=gr.Textbox(label="Enter Website URL", placeholder="https://example.com"),
65
+ outputs=gr.Textbox(label="Scan Results")
66
+ )
67
+
68
+ with gr.Tab("Encryption Tool"):
69
+ with gr.Tab("Generate Key"):
70
+ gr.Interface(fn=generate_key, inputs=None, outputs="text")
71
+ with gr.Tab("Encrypt Message"):
72
+ gr.Interface(fn=encrypt_message, inputs=[
73
+ gr.Textbox(label="Enter message to encrypt"),
74
+ gr.Textbox(label="Enter encryption key")
75
+ ], outputs="text")
76
+ with gr.Tab("Decrypt Message"):
77
+ gr.Interface(fn=decrypt_message, inputs=[
78
+ gr.Textbox(label="Enter encrypted message"),
79
+ gr.Textbox(label="Enter decryption key")
80
+ ], outputs="text")
81
+
82
+ # Run the app
83
+ if __name__ == "__main__":
84
+ demo.launch()
data_breach_checker/__init__.py ADDED
File without changes
data_breach_checker/password_checker.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # data_breach_checker/password_checker.py
2
+ import requests
3
+ import hashlib
4
+ import string
5
+ import random
6
+
7
+
8
+ def gradio_breach_checker(password):
9
+ """
10
+ Checks if a password has been exposed in data breaches using the Have I Been Pwned API.
11
+
12
+ Args:
13
+ password (str): The password to check.
14
+
15
+ Returns:
16
+ str: Breach status message.
17
+ """
18
+ sha1_hash = hashlib.sha1(password.encode()).hexdigest().upper()
19
+ prefix, suffix = sha1_hash[:5], sha1_hash[5:]
20
+ url = f"https://api.pwnedpasswords.com/range/{prefix}"
21
+
22
+ response = requests.get(url)
23
+ if response.status_code != 200:
24
+ return "Error checking password breach status."
25
+
26
+ hashes = (line.split(":") for line in response.text.splitlines())
27
+ for h, count in hashes:
28
+ if h == suffix:
29
+ return f"Your password has been breached {count} times. Avoid using it!"
30
+
31
+ return "Good news! Your password has not been found in any breaches."
32
+
33
+
34
+ def gradio_generate_password(length=12, include_upper=True, include_lower=True, include_digits=True, include_special=True):
35
+ """
36
+ Generates a random, secure password.
37
+
38
+ Args:
39
+ length (int): Length of the password.
40
+ include_upper (bool): Include uppercase letters.
41
+ include_lower (bool): Include lowercase letters.
42
+ include_digits (bool): Include digits.
43
+ include_special (bool): Include special characters.
44
+
45
+ Returns:
46
+ str: The generated password.
47
+ """
48
+ if length < 8:
49
+ return "Password length must be at least 8 characters."
50
+
51
+ char_pool = ""
52
+ if include_upper:
53
+ char_pool += string.ascii_uppercase
54
+ if include_lower:
55
+ char_pool += string.ascii_lowercase
56
+ if include_digits:
57
+ char_pool += string.digits
58
+ if include_special:
59
+ char_pool += string.punctuation
60
+
61
+ if not char_pool:
62
+ return "Please select at least one character type."
63
+
64
+ return "".join(random.choices(char_pool, k=length))
65
+
66
+
67
+ def gradio_password_strength(password):
68
+ """
69
+ Evaluates the strength of a password.
70
+
71
+ Args:
72
+ password (str): The password to evaluate.
73
+
74
+ Returns:
75
+ str: Strength feedback message.
76
+ """
77
+ feedback = []
78
+ if len(password) < 8:
79
+ feedback.append("Password must be at least 8 characters long.")
80
+
81
+ has_upper = any(char.isupper() for char in password)
82
+ has_lower = any(char.islower() for char in password)
83
+ has_digit = any(char.isdigit() for char in password)
84
+ has_special = any(char in string.punctuation for char in password)
85
+
86
+ if not has_upper:
87
+ feedback.append("Add at least one uppercase letter.")
88
+ if not has_lower:
89
+ feedback.append("Add at least one lowercase letter.")
90
+ if not has_digit:
91
+ feedback.append("Add at least one digit.")
92
+ if not has_special:
93
+ feedback.append("Add at least one special character (e.g., @, #, $).")
94
+
95
+ if not feedback:
96
+ return "Strong: Your password is secure!"
97
+ else:
98
+ return "Weak/Moderate:\n" + "\n".join(feedback)
encryption_tool/__init__.py ADDED
File without changes
encryption_tool/encryption_tool.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # encryption_tool/encryption_tool.py
2
+ from cryptography.fernet import Fernet
3
+
4
+
5
+ def generate_key():
6
+ """
7
+ Generates a secret key for encryption and decryption.
8
+
9
+ Returns:
10
+ str: The generated key.
11
+ """
12
+ key = Fernet.generate_key()
13
+ return key.decode()
14
+
15
+
16
+ def encrypt_message(message, key):
17
+ """
18
+ Encrypts a message using the provided key.
19
+
20
+ Args:
21
+ message (str): The message to encrypt.
22
+ key (str): The encryption key.
23
+
24
+ Returns:
25
+ str: The encrypted message.
26
+ """
27
+ try:
28
+ fernet = Fernet(key.encode())
29
+ encrypted_message = fernet.encrypt(message.encode())
30
+ return encrypted_message.decode()
31
+ except Exception as e:
32
+ return f"Encryption failed: {str(e)}"
33
+
34
+
35
+ def decrypt_message(encrypted_message, key):
36
+ """
37
+ Decrypts an encrypted message using the provided key.
38
+
39
+ Args:
40
+ encrypted_message (str): The encrypted message to decrypt.
41
+ key (str): The encryption key.
42
+
43
+ Returns:
44
+ str: The decrypted message or an error message.
45
+ """
46
+ try:
47
+ fernet = Fernet(key.encode())
48
+ decrypted_message = fernet.decrypt(encrypted_message.encode()).decode()
49
+ return decrypted_message
50
+ except Exception as e:
51
+ return f"Decryption failed: {str(e)}"
phishing_checker/__init__.py ADDED
File without changes
phishing_checker/phishing_checker.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # phishing_checker/phishing_checker.py
2
+ import requests
3
+ import re
4
+ import dns.resolver
5
+ from urllib.parse import urlparse
6
+
7
+ def phishing_url_checker(url, api_key=None):
8
+ """
9
+ Checks if a URL is potentially malicious using keyword analysis and optional urlscan.io API.
10
+
11
+ Args:
12
+ url (str): The URL to check.
13
+ api_key (str, optional): API key for urlscan.io.
14
+
15
+ Returns:
16
+ str: Analysis result.
17
+ """
18
+ if not url.startswith(("http://", "https://")):
19
+ return "Invalid URL format. Make sure it starts with http:// or https://"
20
+
21
+ parsed_url = urlparse(url)
22
+ domain = parsed_url.netloc.lower()
23
+
24
+ # Check for phishing-related keywords
25
+ phishing_keywords = ["login", "verify", "banking", "secure", "update", "password", "account"]
26
+ if any(keyword in url.lower() for keyword in phishing_keywords):
27
+ return f"⚠️ Suspicious URL detected: Contains phishing-related keywords ({', '.join(phishing_keywords)})"
28
+
29
+ # Check for excessive subdomains
30
+ if domain.count('.') > 2:
31
+ return f"⚠️ Suspicious URL: {domain} has too many subdomains, a common phishing technique."
32
+
33
+ # Optional: Check with urlscan.io
34
+ if api_key:
35
+ return check_with_urlscan(api_key, url)
36
+
37
+ return "✅ URL appears safe (No immediate threats detected). However, always verify manually."
38
+
39
+
40
+ def check_with_urlscan(api_key, url):
41
+ urlscan_url = "https://urlscan.io/api/v1/scan/"
42
+ headers = {"API-Key": api_key, "Content-Type": "application/json"}
43
+ payload = {"url": url, "visibility": "public"}
44
+
45
+ try:
46
+ response = requests.post(urlscan_url, json=payload, headers=headers)
47
+ response.raise_for_status()
48
+ result_data = response.json()
49
+ scan_id = result_data.get("uuid")
50
+ if scan_id:
51
+ return f"🔍 URL submitted for scanning. View detailed report here: https://urlscan.io/result/{scan_id}/"
52
+ else:
53
+ return "⚠️ Error: Unable to retrieve scan result."
54
+ except requests.exceptions.RequestException as e:
55
+ return f"⚠️ Error contacting urlscan.io: {str(e)}"
56
+
57
+
58
+ def phishing_email_checker(email):
59
+ """
60
+ Checks if an email domain has valid MX records.
61
+
62
+ Args:
63
+ email (str): The email address to check.
64
+
65
+ Returns:
66
+ str: Email validation result.
67
+ """
68
+ if "@" not in email:
69
+ return "Invalid email format."
70
+
71
+ domain = email.split('@')[-1]
72
+ try:
73
+ answers = dns.resolver.resolve(domain, 'MX')
74
+ if answers:
75
+ return f"✅ Email domain '{domain}' has valid MX records. Likely legitimate."
76
+ except:
77
+ return f"⚠️ Warning: Email domain '{domain}' has no valid mail exchange records."
78
+
79
+ return f"⚠️ Unable to verify email domain '{domain}'."
80
+
81
+
82
+ def phishing_sms_checker(sms_text):
83
+ """
84
+ Scans an SMS message for phishing indicators.
85
+
86
+ Args:
87
+ sms_text (str): The SMS content.
88
+
89
+ Returns:
90
+ str: Analysis result.
91
+ """
92
+ phishing_keywords = ["urgent", "bank", "click here", "verify", "account locked", "reset password"]
93
+ shortened_url_patterns = ["bit.ly", "tinyurl.com", "goo.gl", "t.co"]
94
+
95
+ if any(keyword in sms_text.lower() for keyword in phishing_keywords):
96
+ return f"⚠️ Suspicious SMS detected: Contains phishing-related keywords ({', '.join(phishing_keywords)})"
97
+
98
+ if any(url in sms_text.lower() for url in shortened_url_patterns):
99
+ return f"⚠️ Suspicious SMS detected: Contains a shortened URL ({', '.join(shortened_url_patterns)}), often used in phishing."
100
+
101
+ return "✅ SMS appears safe. No immediate threats detected."
qr_detector/__init__.py ADDED
File without changes
qr_detector/qr_detector.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # qr_detector/qr_detector.py
2
+ import cv2
3
+ import numpy as np
4
+ import requests
5
+ import re
6
+ from urllib.parse import urlparse
7
+ from bs4 import BeautifulSoup
8
+
9
+ def qr_code_audit_app(image, api_key=None):
10
+ """
11
+ Decodes a QR code from an image and analyzes the URL.
12
+
13
+ Args:
14
+ image: The image containing the QR code.
15
+ api_key (str, optional): API key for urlscan.io to check the URL.
16
+
17
+ Returns:
18
+ dict: Analysis results including decoded data, link category, and threat status.
19
+ """
20
+ try:
21
+ image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
22
+ detector = cv2.QRCodeDetector()
23
+ data, _, _ = detector.detectAndDecode(image)
24
+
25
+ if not data:
26
+ return {"result": "No QR code detected!"}
27
+
28
+ result, category, status = analyze_url(data)
29
+
30
+ if api_key:
31
+ urlscan_result = check_url_with_urlscan(api_key, data)
32
+ result += f"\nurlscan.io: {urlscan_result}"
33
+
34
+ return {"result": result, "decoded_data": data, "category": category, "status": status}
35
+
36
+ except Exception as e:
37
+ return {"result": f"Error processing image: {str(e)}"}
38
+
39
+
40
+ def analyze_url(url):
41
+ if not re.match(r'^https?://', url):
42
+ return "Invalid QR code content. Not a URL.", "Invalid", "Invalid"
43
+
44
+ parsed_url = urlparse(url)
45
+ domain = parsed_url.netloc
46
+
47
+ # Basic URL categorization
48
+ if "facebook.com" in domain or "twitter.com" in domain:
49
+ category = "Social Media"
50
+ elif parsed_url.path.endswith(('.pdf', '.zip')):
51
+ category = "File Download"
52
+ else:
53
+ category = "General Website"
54
+
55
+ try:
56
+ response = requests.get(url, timeout=5)
57
+ if response.status_code == 200:
58
+ return f"Safe URL: {url}", category, "Safe"
59
+ else:
60
+ return f"Potential Issue: HTTP {response.status_code}", category, "Potential Issue"
61
+ except requests.exceptions.RequestException:
62
+ return "Malicious or Unreachable URL.", "Malicious/Unreachable", "Malicious"
63
+
64
+
65
+ def check_url_with_urlscan(api_key, url):
66
+ urlscan_url = "https://urlscan.io/api/v1/scan/"
67
+ headers = {"API-Key": api_key, "Content-Type": "application/json"}
68
+ payload = {"url": url}
69
+
70
+ try:
71
+ response = requests.post(urlscan_url, json=payload, headers=headers)
72
+ response.raise_for_status()
73
+ result_url = response.json().get("result")
74
+ return result_url if result_url else "Scan submitted, check urlscan.io for results."
75
+ except requests.exceptions.RequestException as e:
76
+ return f"Error contacting urlscan.io: {str(e)}"
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ sqlmap
3
+ opencv-python
4
+ numpy
5
+ matplotlib
6
+ requests
7
+ hashlib
8
+ beautifulsoup4
9
+ dns-python
10
+ cryptography
sql_injection/__init__.py ADDED
File without changes
sql_injection/sql_injection.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+
3
+ def run_sqlmap(url):
4
+ """
5
+ Runs SQLmap on the given URL to test for SQL Injection vulnerabilities.
6
+
7
+ Args:
8
+ url (str): The URL to test for SQL Injection.
9
+
10
+ Returns:
11
+ str: Path to the results file or an error message.
12
+ """
13
+ try:
14
+ # Define the path to sqlmap.py
15
+ sqlmap_path = './sqlmap/sqlmap.py'
16
+
17
+ # Create the SQLmap command
18
+ command = [
19
+ 'python3', sqlmap_path,
20
+ '-u', url, # The URL to test for SQL Injection
21
+ '--batch', # Automatically proceed with default options
22
+ '--level=5', # Maximum level of testing
23
+ '--risk=3', # Maximum risk for more aggressive tests
24
+ '--threads=10' # Use 10 threads for faster testing
25
+ ]
26
+
27
+ # Run the SQLmap command
28
+ result = subprocess.run(command, capture_output=True, text=True)
29
+
30
+ # Define the output file path
31
+ output_file = "./sqlmap_result.txt"
32
+
33
+ # Save the results to a file
34
+ with open(output_file, "w") as file:
35
+ if result.returncode == 0:
36
+ file.write(result.stdout)
37
+ else:
38
+ file.write(f"Error: {result.stderr}")
39
+
40
+ # Return the file path for downloading
41
+ return output_file
42
+ except Exception as e:
43
+ error_file = "./sqlmap_error.txt"
44
+ with open(error_file, "w") as file:
45
+ file.write(f"Error running SQLmap: {str(e)}")
46
+ return error_file
vulnerability_scanner/__init__.py ADDED
File without changes
vulnerability_scanner/vulnerability_scanner.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # vulnerability_scanner/vulnerability_scanner.py
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+
5
+ def vulnerability_scanner(url):
6
+ """
7
+ Scans a website for common vulnerabilities like outdated software, misconfigurations, and directory listing.
8
+
9
+ Args:
10
+ url (str): The website URL to scan.
11
+
12
+ Returns:
13
+ str: Scan results or error message.
14
+ """
15
+ vulnerabilities = []
16
+
17
+ try:
18
+ response = requests.get(url, timeout=10)
19
+ report = f"[+] Connected to {url}\n"
20
+
21
+ server_header = response.headers.get('Server')
22
+ if server_header:
23
+ report += f"[*] Detected Server: {server_header}\n"
24
+ vulnerabilities.extend(check_server_version(server_header))
25
+ else:
26
+ report += "[-] No 'Server' header found.\n"
27
+
28
+ if is_directory_listing_enabled(url):
29
+ vulnerabilities.append("Directory listing is enabled.")
30
+
31
+ if 'X-Content-Type-Options' not in response.headers:
32
+ vulnerabilities.append("X-Content-Type-Options header is missing.")
33
+
34
+ html_vulnerabilities = scan_meta_tags(response.text)
35
+ vulnerabilities.extend(html_vulnerabilities)
36
+
37
+ if vulnerabilities:
38
+ report += "\n[!] Vulnerabilities Found:\n"
39
+ for vuln in vulnerabilities:
40
+ report += f" - {vuln}\n"
41
+ else:
42
+ report += "\n[+] No vulnerabilities detected.\n"
43
+
44
+ except requests.RequestException as e:
45
+ report = f"[-] Error connecting to {url}: {e}"
46
+
47
+ return report
48
+
49
+
50
+ def check_server_version(server_header):
51
+ issues = []
52
+ if "Apache" in server_header:
53
+ version = server_header.split('/')[1] if '/' in server_header else "Unknown"
54
+ if version and version != "Unknown":
55
+ if version < "2.4.57":
56
+ issues.append(f"Outdated Apache version: {version}")
57
+ return issues
58
+
59
+
60
+ def is_directory_listing_enabled(url):
61
+ test_url = f"{url.rstrip('/')}/test-dir"
62
+ try:
63
+ response = requests.get(test_url)
64
+ if response.status_code == 200 and "Index of" in response.text:
65
+ return True
66
+ except requests.RequestException:
67
+ pass
68
+ return False
69
+
70
+
71
+ def scan_meta_tags(html_content):
72
+ issues = []
73
+ soup = BeautifulSoup(html_content, 'html.parser')
74
+ meta_tags = soup.find_all("meta")
75
+ for tag in meta_tags:
76
+ if "generator" in tag.attrs.get("name", "").lower():
77
+ generator_content = tag.attrs.get("content", "").lower()
78
+ if "wordpress" in generator_content and "6.3" not in generator_content:
79
+ issues.append(f"Outdated WordPress version: {generator_content}")
80
+ return issues