cloning over all the old files
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import os
|
||||
import requests
|
||||
import whois
|
||||
from docx import Document
|
||||
from fpdf import FPDF
|
||||
import datetime
|
||||
|
||||
def investigate_website(url):
|
||||
"""Investigate the website to find the country of operation and past security breaches."""
|
||||
try:
|
||||
# Perform WHOIS lookup to determine country
|
||||
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||
whois_data = whois.whois(domain)
|
||||
country = whois_data.get("country", "Unknown")
|
||||
|
||||
# Check for breaches using Have I Been Pwned API (requires API key)
|
||||
hibp_api_url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{domain}"
|
||||
headers = {"hibp-api-key": "YOUR_API_KEY_HERE"} # Replace with your HIBP API key
|
||||
breaches = []
|
||||
|
||||
try:
|
||||
breach_response = requests.get(hibp_api_url, headers=headers)
|
||||
if breach_response.status_code == 200:
|
||||
breaches = breach_response.json()
|
||||
elif breach_response.status_code == 404:
|
||||
breaches = "No breaches found."
|
||||
else:
|
||||
breaches = f"Error: {breach_response.status_code}"
|
||||
except Exception as e:
|
||||
breaches = f"Error fetching breaches: {e}"
|
||||
|
||||
return {
|
||||
"country": country,
|
||||
"breaches": breaches,
|
||||
"whois_data": str(whois_data)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"country": "Error determining country",
|
||||
"breaches": f"Error fetching breaches: {e}",
|
||||
"whois_data": f"Error: {e}"
|
||||
}
|
||||
|
||||
def analyze_headers(url):
|
||||
"""Perform a header analysis to identify security weaknesses."""
|
||||
try:
|
||||
response = requests.get(url)
|
||||
headers = response.headers
|
||||
|
||||
# Security headers to check
|
||||
security_headers = {
|
||||
"Content-Security-Policy": "Missing or insufficient Content-Security-Policy",
|
||||
"Strict-Transport-Security": "Missing Strict-Transport-Security",
|
||||
"X-Content-Type-Options": "Missing X-Content-Type-Options",
|
||||
"X-Frame-Options": "Missing X-Frame-Options",
|
||||
"X-XSS-Protection": "Missing or insufficient X-XSS-Protection",
|
||||
"Referrer-Policy": "Missing Referrer-Policy",
|
||||
"Permissions-Policy": "Missing Permissions-Policy"
|
||||
}
|
||||
|
||||
weaknesses = []
|
||||
for header, issue in security_headers.items():
|
||||
if header not in headers:
|
||||
weaknesses.append(issue)
|
||||
|
||||
grade = "A" if not weaknesses else "C" if len(weaknesses) < 3 else "F"
|
||||
|
||||
return {
|
||||
"grade": grade,
|
||||
"weaknesses": weaknesses,
|
||||
"headers": dict(headers)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"grade": "Error",
|
||||
"weaknesses": [f"Error analyzing headers: {e}"],
|
||||
"headers": f"Error: {e}"
|
||||
}
|
||||
|
||||
def save_results_to_files(data, output_dir, domain):
|
||||
"""Save investigation results to docx, pdf, and markdown files."""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Generate unique filenames using timestamp and domain
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
# Ensure the domain is a valid filename
|
||||
domain_safe = "".join(c if c.isalnum() else "_" for c in domain)
|
||||
base_filename = f"{domain_safe}_{timestamp}"
|
||||
docx_path = os.path.join(output_dir, f"{base_filename}.docx")
|
||||
pdf_path = os.path.join(output_dir, f"{base_filename}.pdf")
|
||||
md_path = os.path.join(output_dir, f"{base_filename}.md")
|
||||
|
||||
# Create a Word document
|
||||
doc = Document()
|
||||
doc.add_heading("Website Security Analysis", level=1)
|
||||
|
||||
doc.add_heading("1. Website Investigation", level=2)
|
||||
doc.add_paragraph(f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||
doc.add_paragraph(f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||
|
||||
doc.add_heading("2. Header Analysis", level=2)
|
||||
doc.add_paragraph(f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||
if data['headers'].get('weaknesses'):
|
||||
doc.add_paragraph("Weaknesses:")
|
||||
for weakness in data['headers']['weaknesses']:
|
||||
doc.add_paragraph(f"- {weakness}", style="List Bullet")
|
||||
|
||||
# Add Appendix
|
||||
doc.add_heading("Appendix", level=1)
|
||||
doc.add_heading("Full WHOIS Data", level=2)
|
||||
doc.add_paragraph(data['investigation'].get('whois_data', 'N/A'))
|
||||
|
||||
doc.add_heading("Full Header Analysis", level=2)
|
||||
doc.add_paragraph(str(data['headers'].get('headers', 'N/A')))
|
||||
|
||||
doc.add_heading("Full Breach Data", level=2)
|
||||
doc.add_paragraph(str(data['investigation'].get('breaches', 'N/A')))
|
||||
|
||||
doc.save(docx_path)
|
||||
|
||||
# Create a Markdown file
|
||||
with open(md_path, "w") as md_file:
|
||||
md_file.write("# Website Security Analysis\n\n")
|
||||
md_file.write("## 1. Website Investigation\n")
|
||||
md_file.write(f"- Country of Operation: {data['investigation'].get('country', 'N/A')}\n")
|
||||
md_file.write(f"- Security Breaches: {data['investigation'].get('breaches', 'N/A')}\n\n")
|
||||
md_file.write("## 2. Header Analysis\n")
|
||||
md_file.write(f"- Security Grade: {data['headers'].get('grade', 'N/A')}\n")
|
||||
if data['headers'].get('weaknesses'):
|
||||
md_file.write("- Weaknesses:\n")
|
||||
for weakness in data['headers']['weaknesses']:
|
||||
md_file.write(f" - {weakness}\n")
|
||||
|
||||
md_file.write("\n# Appendix\n\n")
|
||||
md_file.write("## Full WHOIS Data\n")
|
||||
md_file.write(f"```\n{data['investigation'].get('whois_data', 'N/A')}\n```\n")
|
||||
|
||||
md_file.write("## Full Header Analysis\n")
|
||||
md_file.write(f"```\n{data['headers'].get('headers', 'N/A')}\n```\n")
|
||||
|
||||
md_file.write("## Full Breach Data\n")
|
||||
md_file.write(f"```\n{data['investigation'].get('breaches', 'N/A')}\n```\n")
|
||||
|
||||
# Create a PDF file using FPDF
|
||||
pdf = FPDF()
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
pdf.add_page()
|
||||
pdf.set_font("Arial", size=12)
|
||||
|
||||
pdf.cell(200, 10, txt="Website Security Analysis", ln=True, align='C')
|
||||
|
||||
pdf.ln(10)
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "1. Website Investigation", ln=True)
|
||||
|
||||
pdf.set_font("Arial", size=12)
|
||||
pdf.multi_cell(0, 10, f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||
pdf.multi_cell(0, 10, f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||
|
||||
pdf.ln(5)
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "2. Header Analysis", ln=True)
|
||||
|
||||
pdf.set_font("Arial", size=12)
|
||||
pdf.multi_cell(0, 10, f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||
if data['headers'].get('weaknesses'):
|
||||
pdf.cell(0, 10, "Weaknesses:", ln=True)
|
||||
for weakness in data['headers']['weaknesses']:
|
||||
pdf.cell(0, 10, f"- {weakness}", ln=True)
|
||||
|
||||
pdf.add_page()
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Appendix", ln=True)
|
||||
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Full WHOIS Data", ln=True)
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.multi_cell(0, 10, data['investigation'].get('whois_data', 'N/A'))
|
||||
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Full Header Analysis", ln=True)
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.multi_cell(0, 10, str(data['headers'].get('headers', 'N/A')))
|
||||
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Full Breach Data", ln=True)
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.multi_cell(0, 10, str(data['investigation'].get('breaches', 'N/A')))
|
||||
|
||||
pdf.output(pdf_path)
|
||||
|
||||
print(f"Results saved to: {docx_path}, {md_path}, {pdf_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# User inputs
|
||||
website_url = input("Enter the website URL (including https://): ").strip()
|
||||
default_directory = os.getcwd()
|
||||
print(f"Default save directory: {default_directory}")
|
||||
use_default = input("Do you want to use the default directory? (y/n): ").strip().lower()
|
||||
|
||||
if use_default == 'y':
|
||||
output_directory = default_directory
|
||||
else:
|
||||
output_directory = input("Enter the directory to save results: ").strip()
|
||||
|
||||
# Extract domain name for unique file naming
|
||||
domain = website_url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||
|
||||
# Perform analysis
|
||||
investigation_results = investigate_website(website_url)
|
||||
header_analysis_results = analyze_headers(website_url)
|
||||
|
||||
# Combine results
|
||||
results = {
|
||||
"investigation": investigation_results,
|
||||
"headers": header_analysis_results
|
||||
}
|
||||
|
||||
# Save to files
|
||||
save_results_to_files(results, output_directory, domain)
|
||||
@@ -0,0 +1,220 @@
|
||||
import os
|
||||
import requests
|
||||
import whois
|
||||
from docx import Document
|
||||
from fpdf import FPDF
|
||||
import datetime
|
||||
|
||||
def investigate_website(url):
|
||||
"""Investigate the website to find the country of operation and past security breaches."""
|
||||
try:
|
||||
# Perform WHOIS lookup to determine country
|
||||
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||
whois_data = whois.whois(domain)
|
||||
country = whois_data.get("country", "Unknown")
|
||||
|
||||
# Check for breaches using Have I Been Pwned API (requires API key)
|
||||
hibp_api_url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{domain}"
|
||||
headers = {"hibp-api-key": "YOUR_API_KEY_HERE"} # Replace with your HIBP API key
|
||||
breaches = []
|
||||
|
||||
try:
|
||||
breach_response = requests.get(hibp_api_url, headers=headers)
|
||||
if breach_response.status_code == 200:
|
||||
breaches = breach_response.json()
|
||||
elif breach_response.status_code == 404:
|
||||
breaches = "No breaches found."
|
||||
else:
|
||||
breaches = f"Error: {breach_response.status_code}"
|
||||
except Exception as e:
|
||||
breaches = f"Error fetching breaches: {e}"
|
||||
|
||||
return {
|
||||
"country": country,
|
||||
"breaches": breaches,
|
||||
"whois_data": str(whois_data)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"country": "Error determining country",
|
||||
"breaches": f"Error fetching breaches: {e}",
|
||||
"whois_data": f"Error: {e}"
|
||||
}
|
||||
|
||||
def analyze_headers(url):
|
||||
"""Perform a header analysis to identify security weaknesses."""
|
||||
try:
|
||||
response = requests.get(url)
|
||||
headers = response.headers
|
||||
|
||||
# Security headers to check
|
||||
security_headers = {
|
||||
"Content-Security-Policy": "Missing or insufficient Content-Security-Policy",
|
||||
"Strict-Transport-Security": "Missing Strict-Transport-Security",
|
||||
"X-Content-Type-Options": "Missing X-Content-Type-Options",
|
||||
"X-Frame-Options": "Missing X-Frame-Options",
|
||||
"X-XSS-Protection": "Missing or insufficient X-XSS-Protection",
|
||||
"Referrer-Policy": "Missing Referrer-Policy",
|
||||
"Permissions-Policy": "Missing Permissions-Policy"
|
||||
}
|
||||
|
||||
weaknesses = []
|
||||
for header, issue in security_headers.items():
|
||||
if header not in headers:
|
||||
weaknesses.append(issue)
|
||||
|
||||
grade = "A" if not weaknesses else "C" if len(weaknesses) < 3 else "F"
|
||||
|
||||
return {
|
||||
"grade": grade,
|
||||
"weaknesses": weaknesses,
|
||||
"headers": dict(headers)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"grade": "Error",
|
||||
"weaknesses": [f"Error analyzing headers: {e}"],
|
||||
"headers": f"Error: {e}"
|
||||
}
|
||||
|
||||
def save_results_to_files(data, output_dir, domain):
|
||||
"""Save investigation results to docx, pdf, and markdown files."""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Generate unique filenames using timestamp and domain
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
# Ensure the domain is a valid filename
|
||||
domain_safe = "".join(c if c.isalnum() else "_" for c in domain)
|
||||
base_filename = f"{domain_safe}_{timestamp}"
|
||||
docx_path = os.path.join(output_dir, f"{base_filename}.docx")
|
||||
pdf_path = os.path.join(output_dir, f"{base_filename}.pdf")
|
||||
md_path = os.path.join(output_dir, f"{base_filename}.md")
|
||||
|
||||
# Create a Word document
|
||||
doc = Document()
|
||||
doc.add_heading("Website Security Analysis", level=1)
|
||||
|
||||
doc.add_heading("1. Website Investigation", level=2)
|
||||
doc.add_paragraph(f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||
doc.add_paragraph(f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||
|
||||
doc.add_heading("2. Header Analysis", level=2)
|
||||
doc.add_paragraph(f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||
if data['headers'].get('weaknesses'):
|
||||
doc.add_paragraph("Weaknesses:")
|
||||
for weakness in data['headers']['weaknesses']:
|
||||
doc.add_paragraph(f"- {weakness}", style="List Bullet")
|
||||
|
||||
# Add Appendix
|
||||
doc.add_heading("Appendix", level=1)
|
||||
doc.add_heading("Full WHOIS Data", level=2)
|
||||
doc.add_paragraph(data['investigation'].get('whois_data', 'N/A'))
|
||||
|
||||
doc.add_heading("Full Header Analysis", level=2)
|
||||
doc.add_paragraph(str(data['headers'].get('headers', 'N/A')))
|
||||
|
||||
doc.add_heading("Full Breach Data", level=2)
|
||||
doc.add_paragraph(str(data['investigation'].get('breaches', 'N/A')))
|
||||
|
||||
doc.save(docx_path)
|
||||
|
||||
# Create a Markdown file
|
||||
with open(md_path, "w") as md_file:
|
||||
md_file.write("# Website Security Analysis\n\n")
|
||||
md_file.write("## 1. Website Investigation\n")
|
||||
md_file.write(f"- Country of Operation: {data['investigation'].get('country', 'N/A')}\n")
|
||||
md_file.write(f"- Security Breaches: {data['investigation'].get('breaches', 'N/A')}\n\n")
|
||||
md_file.write("## 2. Header Analysis\n")
|
||||
md_file.write(f"- Security Grade: {data['headers'].get('grade', 'N/A')}\n")
|
||||
if data['headers'].get('weaknesses'):
|
||||
md_file.write("- Weaknesses:\n")
|
||||
for weakness in data['headers']['weaknesses']:
|
||||
md_file.write(f" - {weakness}\n")
|
||||
|
||||
md_file.write("\n# Appendix\n\n")
|
||||
md_file.write("## Full WHOIS Data\n")
|
||||
md_file.write(f"```\n{data['investigation'].get('whois_data', 'N/A')}\n```\n")
|
||||
|
||||
md_file.write("## Full Header Analysis\n")
|
||||
md_file.write(f"```\n{data['headers'].get('headers', 'N/A')}\n```\n")
|
||||
|
||||
md_file.write("## Full Breach Data\n")
|
||||
md_file.write(f"```\n{data['investigation'].get('breaches', 'N/A')}\n```\n")
|
||||
|
||||
# Create a PDF file using FPDF
|
||||
pdf = FPDF()
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
pdf.add_page()
|
||||
pdf.set_font("Arial", size=12)
|
||||
|
||||
pdf.cell(200, 10, txt="Website Security Analysis", ln=True, align='C')
|
||||
|
||||
pdf.ln(10)
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "1. Website Investigation", ln=True)
|
||||
|
||||
pdf.set_font("Arial", size=12)
|
||||
pdf.multi_cell(0, 10, f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||
pdf.multi_cell(0, 10, f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||
|
||||
pdf.ln(5)
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "2. Header Analysis", ln=True)
|
||||
|
||||
pdf.set_font("Arial", size=12)
|
||||
pdf.multi_cell(0, 10, f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||
if data['headers'].get('weaknesses'):
|
||||
pdf.cell(0, 10, "Weaknesses:", ln=True)
|
||||
for weakness in data['headers']['weaknesses']:
|
||||
pdf.cell(0, 10, f"- {weakness}", ln=True)
|
||||
|
||||
pdf.add_page()
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Appendix", ln=True)
|
||||
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Full WHOIS Data", ln=True)
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.multi_cell(0, 10, data['investigation'].get('whois_data', 'N/A'))
|
||||
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Full Header Analysis", ln=True)
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.multi_cell(0, 10, str(data['headers'].get('headers', 'N/A')))
|
||||
|
||||
pdf.set_font("Arial", style="B", size=12)
|
||||
pdf.cell(0, 10, "Full Breach Data", ln=True)
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.multi_cell(0, 10, str(data['investigation'].get('breaches', 'N/A')))
|
||||
|
||||
pdf.output(pdf_path)
|
||||
|
||||
print(f"Results saved to: {docx_path}, {md_path}, {pdf_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# User inputs
|
||||
website_url = input("Enter the website URL (including https://): ").strip()
|
||||
default_directory = os.getcwd()
|
||||
print(f"Default save directory: {default_directory}")
|
||||
use_default = input("Do you want to use the default directory? (y/n): ").strip().lower()
|
||||
|
||||
if use_default == 'y':
|
||||
output_directory = default_directory
|
||||
else:
|
||||
output_directory = input("Enter the directory to save results: ").strip()
|
||||
|
||||
# Extract domain name for unique file naming
|
||||
domain = website_url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||
|
||||
# Perform analysis
|
||||
investigation_results = investigate_website(website_url)
|
||||
header_analysis_results = analyze_headers(website_url)
|
||||
|
||||
# Combine results
|
||||
results = {
|
||||
"investigation": investigation_results,
|
||||
"headers": header_analysis_results
|
||||
}
|
||||
|
||||
# Save to files
|
||||
save_results_to_files(results, output_directory, domain)
|
||||
@@ -0,0 +1,79 @@
|
||||
import pandas as pd
|
||||
import os
|
||||
import whois
|
||||
import socket
|
||||
import requests
|
||||
from ipwhois import IPWhois
|
||||
|
||||
def aggregate_csv_to_excel(folder_path, output_excel):
|
||||
with pd.ExcelWriter(output_excel, engine='xlsxwriter') as writer:
|
||||
for filename in os.listdir(folder_path):
|
||||
if filename.endswith('.csv'):
|
||||
file_path = os.path.join(folder_path, filename)
|
||||
df = pd.read_csv(file_path)
|
||||
|
||||
# Filter relevant columns
|
||||
columns = ['timestamp', 'client_ip', 'server_ip', 'server_port', 'direction', 'total_bytes', 'country', 'flow_state']
|
||||
if 'dns' in df.columns:
|
||||
columns.append('dns')
|
||||
df = df[columns]
|
||||
|
||||
# Investigate each server IP
|
||||
df['whois_info'] = df['server_ip'].apply(get_whois_info)
|
||||
df['geo_info'] = df['server_ip'].apply(get_geo_info)
|
||||
df['virus_total'] = df['server_ip'].apply(check_virus_total)
|
||||
df['dns_lookup'] = df['server_ip'].apply(dns_lookup)
|
||||
|
||||
# Write to Excel
|
||||
sheet_name = os.path.splitext(filename)[0]
|
||||
df.to_excel(writer, sheet_name=sheet_name, index=False)
|
||||
|
||||
def get_whois_info(ip):
|
||||
try:
|
||||
w = whois.whois(ip)
|
||||
return f"Registrar: {w.registrar}, Country: {w.country}"
|
||||
except Exception as e:
|
||||
return f"WHOIS Error: {str(e)}"
|
||||
|
||||
def get_geo_info(ip):
|
||||
try:
|
||||
obj = IPWhois(ip)
|
||||
res = obj.lookup_rdap()
|
||||
return f"Country: {res['asn_country_code']}, ASN: {res['asn']}"
|
||||
except Exception as e:
|
||||
return f"Geo Error: {str(e)}"
|
||||
|
||||
def check_virus_total(ip):
|
||||
try:
|
||||
url = f"https://www.virustotal.com/api/v3/ip_addresses/{ip}"
|
||||
headers = {"x-apikey": "your-api-key-here"} # Replace with your VirusTotal API key
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return f"Reputation: {data.get('data', {}).get('attributes', {}).get('reputation', 'N/A')}"
|
||||
else:
|
||||
return f"VT Error: {response.status_code}"
|
||||
except Exception as e:
|
||||
return f"VT Error: {str(e)}"
|
||||
|
||||
def dns_lookup(ip):
|
||||
try:
|
||||
return socket.gethostbyaddr(ip)[0]
|
||||
except socket.herror:
|
||||
return "DNS Lookup Error"
|
||||
|
||||
def main():
|
||||
current_dir = os.getcwd()
|
||||
user_input = input(f"Do you want to use the current directory ({current_dir}) to pull CSV files? (yes/no): ").strip().lower()
|
||||
|
||||
if user_input == 'yes':
|
||||
folder_path = current_dir
|
||||
else:
|
||||
folder_path = input("Please enter the directory where the CSV files are located: ").strip()
|
||||
|
||||
output_excel = os.path.join(current_dir, 'aggregated_investigation.xlsx')
|
||||
aggregate_csv_to_excel(folder_path, output_excel)
|
||||
print(f"Aggregated investigation saved to {output_excel}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,340 @@
|
||||
import datetime
|
||||
from docx import Document
|
||||
from docx2pdf import convert
|
||||
|
||||
def create_assessment_doc(data):
|
||||
"""Creates a Word document from the assessment data."""
|
||||
|
||||
document = Document()
|
||||
document.add_heading('Software/Vendor Security Assessment Request', 0)
|
||||
|
||||
# 1. Requestor Information
|
||||
document.add_heading('1. Requestor Information', level=1)
|
||||
document.add_paragraph(f"Name: {data['requestor_name']}")
|
||||
document.add_paragraph(f"Department: {data['department']}")
|
||||
document.add_paragraph(f"Email Address: {data['email']}")
|
||||
document.add_paragraph(f"Phone Number: {data['phone']}")
|
||||
|
||||
# 2. Request Type
|
||||
document.add_heading('2. Request Type', level=1)
|
||||
document.add_paragraph(data['request_type'])
|
||||
|
||||
# 3. Deployment Model
|
||||
document.add_heading('3. Deployment Model', level=1)
|
||||
document.add_paragraph(data['deployment_model'])
|
||||
|
||||
# 4. Software/Vendor Information
|
||||
document.add_heading('4. Software/Vendor Information', level=1)
|
||||
document.add_paragraph(f"Software/Vendor Name: {data['vendor_name']}")
|
||||
document.add_paragraph(f"Software Name: {data['software_name']}")
|
||||
document.add_paragraph(f"Software/Vendor Website: {data['vendor_website']}")
|
||||
document.add_paragraph(f"Software/Vendor Contact Information: {data['vendor_contact']}")
|
||||
document.add_paragraph(f"Brief Description of Software/Vendor and its Purpose: {data['vendor_description']}")
|
||||
|
||||
# 5. Intended Use
|
||||
document.add_heading('5. Intended Use', level=1)
|
||||
document.add_paragraph(f"Describe how the software/vendor will be used within the company: {data['intended_use']}")
|
||||
document.add_paragraph(f"Specify the departments or teams that will be using the software/vendor: {data['departments_teams']}")
|
||||
document.add_paragraph(f"Identify any critical business processes or data that will be impacted by the software/vendor: {data['critical_processes_data']}")
|
||||
|
||||
# 6. Prerequisites
|
||||
document.add_heading('6. Prerequisites', level=1)
|
||||
document.add_paragraph(f"Operating System Requirements: {data['os_requirements']}")
|
||||
document.add_paragraph(f"Hardware Requirements (e.g., RAM, storage space): {data['hardware_requirements']}")
|
||||
document.add_paragraph(f"Network Connectivity (e.g., bandwidth, VPN): {data['network_connectivity']}")
|
||||
document.add_paragraph(f"Software Dependencies (e.g., libraries, frameworks): {data['software_dependencies']}")
|
||||
document.add_paragraph(f"User Access Requirements (e.g., specific roles, permissions): {data['user_access_requirements']}")
|
||||
document.add_paragraph(f"Other Prerequisites: {data['other_prerequisites']}")
|
||||
|
||||
# 7. Technical Information
|
||||
document.add_heading('7. Technical Information', level=1)
|
||||
document.add_paragraph(f"Hosting Environment (e.g., cloud provider, on-premise data center): {data['hosting_environment']}")
|
||||
document.add_paragraph(f"Network Requirements (e.g., ports, protocols, firewall rules): {data['network_requirements']}")
|
||||
document.add_paragraph(f"Data Storage Requirements (e.g., location, encryption, data retention policies): {data['data_storage_requirements']}")
|
||||
document.add_paragraph(f"Integration with Existing Systems (if any): {data['integration_existing_systems']}")
|
||||
document.add_paragraph(f"API Information (if applicable): {data['api_information']}")
|
||||
|
||||
# 8. Security Requirements
|
||||
document.add_heading('8. Security Requirements', level=1)
|
||||
document.add_paragraph(f"Data Security - How will data be protected in transit and at rest?: {data['data_protection']}")
|
||||
document.add_paragraph(f"Data Security - Does the software/vendor comply with relevant data privacy regulations (e.g., GDPR, CCPA)?: {data['data_privacy_compliance']}")
|
||||
document.add_paragraph(f"Data Security - What data encryption methods are used?: {data['data_encryption_methods']}")
|
||||
document.add_paragraph(f"Access Control - How will access to the software/vendor be granted and managed?: {data['access_control_management']}")
|
||||
document.add_paragraph(f"Access Control - Does the software/vendor support multi-factor authentication?: {data['multi_factor_authentication']}")
|
||||
document.add_paragraph(f"Access Control - What user roles and permissions are available?: {data['user_roles_permissions']}")
|
||||
document.add_paragraph(f"Vulnerability Management - Does the software/vendor have a vulnerability disclosure program?: {data['vulnerability_disclosure']}")
|
||||
document.add_paragraph(f"Vulnerability Management - How are security patches and updates managed?: {data['security_patch_management']}")
|
||||
document.add_paragraph(f"Vulnerability Management - Are regular security assessments and penetration testing conducted?: {data['security_assessments']}")
|
||||
document.add_paragraph(f"Compliance - Does the software/vendor comply with relevant industry security standards (e.g., ISO 27001, SOC 2)?: {data['industry_security_standards']}")
|
||||
document.add_paragraph(f"Compliance - Are there any third-party security certifications or audits available?: {data['third_party_certifications']}")
|
||||
document.add_paragraph(f"Business Continuity and Disaster Recovery - What are the software/vendor's business continuity and disaster recovery plans?: {data['business_continuity_plans']}")
|
||||
document.add_paragraph(f"Business Continuity and Disaster Recovery - What are the recovery time objectives (RTOs) and recovery point objectives (RPOs)?: {data['rto_rpo']}")
|
||||
|
||||
# 9. Privacy Requirements
|
||||
document.add_heading('9. Privacy Requirements', level=1)
|
||||
document.add_paragraph(f"Data Collection - What personal data will be collected by the software/vendor?: {data['personal_data_collected']}")
|
||||
document.add_paragraph(f"Data Collection - What is the purpose of collecting this data?: {data['data_collection_purpose']}")
|
||||
document.add_paragraph(f"Data Collection - Will data be shared with third parties? If so, with whom and for what purpose?: {data['data_sharing']}")
|
||||
document.add_paragraph(f"Data Subject Rights - How does the software/vendor ensure data subject rights (e.g., access, rectification, erasure)?: {data['data_subject_rights']}")
|
||||
document.add_paragraph(f"Data Subject Rights - Does the software/vendor provide a privacy policy that is readily accessible to users?: {data['privacy_policy']}")
|
||||
|
||||
# 10. Legal and Contractual Requirements
|
||||
document.add_heading('10. Legal and Contractual Requirements', level=1)
|
||||
document.add_paragraph(f"Software License Agreement: {data['license_agreement']}")
|
||||
document.add_paragraph(f"Service Level Agreement (SLA): {data['sla']}")
|
||||
document.add_paragraph(f"Data Processing Agreement (DPA): {data['dpa']}")
|
||||
document.add_paragraph(f"Vendor Security Policies: {data['vendor_security_policies']}")
|
||||
|
||||
# 11. Risk Assessment
|
||||
document.add_heading('11. Risk Assessment', level=1)
|
||||
document.add_paragraph(f"Identify any potential security or privacy risks associated with the use of the software/vendor: {data['potential_risks']}")
|
||||
document.add_paragraph(f"Describe the risk mitigation measures that will be implemented: {data['risk_mitigation']}")
|
||||
|
||||
# 12. Approval (This section will likely be filled later)
|
||||
document.add_heading('12. Approval', level=1)
|
||||
document.add_paragraph("Security Analyst Review:")
|
||||
document.add_paragraph("Date of Review: ")
|
||||
document.add_paragraph("Security Analyst Name: ")
|
||||
document.add_paragraph("Security Assessment Findings: ")
|
||||
document.add_paragraph("Approval Status (Approved, Conditionally Approved, Rejected): ")
|
||||
document.add_paragraph("Management Approval (if required):")
|
||||
document.add_paragraph("Date of Approval: ")
|
||||
document.add_paragraph("Approving Manager Name: ")
|
||||
|
||||
# 13. Supporting Documentation
|
||||
document.add_heading('13. Supporting Documentation', level=1)
|
||||
document.add_paragraph("Attach any relevant supporting documentation, such as:")
|
||||
document.add_paragraph("Vendor security questionnaires")
|
||||
document.add_paragraph("Penetration testing reports")
|
||||
document.add_paragraph("Security audit reports")
|
||||
document.add_paragraph("Privacy impact assessments")
|
||||
|
||||
return document
|
||||
|
||||
def create_markdown_file(data, md_filename): # Receive filename as argument
|
||||
"""Creates a Markdown file from the assessment data."""
|
||||
|
||||
with open(md_filename, "w") as f: # Use the provided filename
|
||||
# Add all the headings and data from the form in Markdown format
|
||||
f.write("# Software/Vendor Security Assessment Request\n\n")
|
||||
|
||||
f.write("## 1. Requestor Information\n")
|
||||
f.write(f"Name: {data['requestor_name']}\n")
|
||||
f.write(f"Department: {data['department']}\n")
|
||||
f.write(f"Email Address: {data['email']}\n")
|
||||
f.write(f"Phone Number: {data['phone']}\n")
|
||||
|
||||
f.write("\n## 2. Request Type\n")
|
||||
f.write(f"{data['request_type']}\n")
|
||||
|
||||
f.write("\n## 3. Deployment Model\n")
|
||||
f.write(f"{data['deployment_model']}\n")
|
||||
|
||||
f.write("\n## 4. Software/Vendor Information\n")
|
||||
f.write(f"Software/Vendor Name: {data['vendor_name']}\n")
|
||||
f.write(f"Software Name: {data['software_name']}\n")
|
||||
f.write(f"Software/Vendor Website: {data['vendor_website']}\n")
|
||||
f.write(f"Software/Vendor Contact Information: {data['vendor_contact']}\n")
|
||||
f.write(f"Brief Description of Software/Vendor and its Purpose: {data['vendor_description']}\n")
|
||||
|
||||
f.write("\n## 5. Intended Use\n")
|
||||
f.write(f"Describe how the software/vendor will be used within the company: {data['intended_use']}\n")
|
||||
f.write(f"Specify the departments or teams that will be using the software/vendor: {data['departments_teams']}\n")
|
||||
f.write(f"Identify any critical business processes or data that will be impacted by the software/vendor: {data['critical_processes_data']}\n")
|
||||
|
||||
f.write("\n## 6. Prerequisites\n")
|
||||
f.write(f"Operating System Requirements: {data['os_requirements']}\n")
|
||||
f.write(f"Hardware Requirements (e.g., RAM, storage space): {data['hardware_requirements']}\n")
|
||||
f.write(f"Network Connectivity (e.g., bandwidth, VPN): {data['network_connectivity']}\n")
|
||||
f.write(f"Software Dependencies (e.g., libraries, frameworks): {data['software_dependencies']}\n")
|
||||
f.write(f"User Access Requirements (e.g., specific roles, permissions): {data['user_access_requirements']}\n")
|
||||
f.write(f"Other Prerequisites: {data['other_prerequisites']}\n")
|
||||
|
||||
f.write("\n## 7. Technical Information\n")
|
||||
f.write(f"Hosting Environment (e.g., cloud provider, on-premise data center): {data['hosting_environment']}\n")
|
||||
f.write(f"Network Requirements (e.g., ports, protocols, firewall rules): {data['network_requirements']}\n")
|
||||
f.write(f"Data Storage Requirements (e.g., location, encryption, data retention policies): {data['data_storage_requirements']}\n")
|
||||
f.write(f"Integration with Existing Systems (if any): {data['integration_existing_systems']}\n")
|
||||
f.write(f"API Information (if applicable): {data['api_information']}\n")
|
||||
|
||||
f.write("\n## 8. Security Requirements\n")
|
||||
f.write(f"Data Security - How will data be protected in transit and at rest?: {data['data_protection']}\n")
|
||||
f.write(f"Data Security - Does the software/vendor comply with relevant data privacy regulations (e.g., GDPR, CCPA)?: {data['data_privacy_compliance']}\n")
|
||||
f.write(f"Data Security - What data encryption methods are used?: {data['data_encryption_methods']}\n")
|
||||
f.write(f"Access Control - How will access to the software/vendor be granted and managed?: {data['access_control_management']}\n")
|
||||
f.write(f"Access Control - Does the software/vendor support multi-factor authentication?: {data['multi_factor_authentication']}\n")
|
||||
f.write(f"Access Control - What user roles and permissions are available?: {data['user_roles_permissions']}\n")
|
||||
f.write(f"Vulnerability Management - Does the software/vendor have a vulnerability disclosure program?: {data['vulnerability_disclosure']}\n")
|
||||
f.write(f"Vulnerability Management - How are security patches and updates managed?: {data['security_patch_management']}\n")
|
||||
f.write(f"Vulnerability Management - Are regular security assessments and penetration testing conducted?: {data['security_assessments']}\n")
|
||||
f.write(f"Compliance - Does the software/vendor comply with relevant industry security standards (e.g., ISO 27001, SOC 2)?: {data['industry_security_standards']}\n")
|
||||
f.write(f"Compliance - Are there any third-party security certifications or audits available?: {data['third_party_certifications']}\n")
|
||||
f.write(f"Business Continuity and Disaster Recovery - What are the software/vendor's business continuity and disaster recovery plans?: {data['business_continuity_plans']}\n")
|
||||
f.write(f"Business Continuity and Disaster Recovery - What are the recovery time objectives (RTOs) and recovery point objectives (RPOs)?: {data['rto_rpo']}\n")
|
||||
|
||||
f.write("\n## 9. Privacy Requirements\n")
|
||||
f.write(f"Data Collection - What personal data will be collected by the software/vendor?: {data['personal_data_collected']}\n")
|
||||
f.write(f"Data Collection - What is the purpose of collecting this data?: {data['data_collection_purpose']}\n")
|
||||
f.write(f"Data Collection - Will data be shared with third parties? If so, with whom and for what purpose?: {data['data_sharing']}\n")
|
||||
f.write(f"Data Subject Rights - How does the software/vendor ensure data subject rights (e.g., access, rectification, erasure)?: {data['data_subject_rights']}\n")
|
||||
f.write(f"Data Subject Rights - Does the software/vendor provide a privacy policy that is readily accessible to users?: {data['privacy_policy']}\n")
|
||||
|
||||
f.write("\n## 10. Legal and Contractual Requirements\n")
|
||||
f.write(f"Software License Agreement: {data['license_agreement']}\n")
|
||||
f.write(f"Service Level Agreement (SLA): {data['sla']}\n")
|
||||
f.write(f"Data Processing Agreement (DPA): {data['dpa']}\n")
|
||||
f.write(f"Vendor Security Policies: {data['vendor_security_policies']}\n")
|
||||
|
||||
f.write("\n## 11. Risk Assessment\n")
|
||||
f.write(f"Identify any potential security or privacy risks associated with the use of the software/vendor: {data['potential_risks']}\n")
|
||||
f.write(f"Describe the risk mitigation measures that will be implemented: {data['risk_mitigation']}\n")
|
||||
|
||||
f.write("\n## 12. Approval\n")
|
||||
f.write("Security Analyst Review:\n")
|
||||
f.write("Date of Review: \n")
|
||||
f.write("Security Analyst Name: \n")
|
||||
f.write("Security Assessment Findings: \n")
|
||||
f.write("Approval Status (Approved, Conditionally Approved, Rejected): \n")
|
||||
f.write("Management Approval (if required):\n")
|
||||
f.write("Date of Approval: \n")
|
||||
f.write("Approving Manager Name: \n")
|
||||
|
||||
f.write("\n## 13. Supporting Documentation\n")
|
||||
f.write("Attach any relevant supporting documentation, such as:\n")
|
||||
f.write("- Vendor security questionnaires\n")
|
||||
f.write("- Penetration testing reports\n")
|
||||
f.write("- Security audit reports\n")
|
||||
f.write("- Privacy impact assessments\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""Collects assessment data from user input and generates documents."""
|
||||
|
||||
data = {}
|
||||
|
||||
# 1. Requestor Information
|
||||
data['requestor_name'] = input("Enter your name: ")
|
||||
data['department'] = input("Enter your department: ")
|
||||
data['email'] = input("Enter your email address: ")
|
||||
data['phone'] = input("Enter your phone number: ")
|
||||
|
||||
# 2. Request Type
|
||||
print("\nRequest Type:")
|
||||
print("1. New Software/Vendor")
|
||||
print("2. Existing Software/Vendor (Renewal/Upgrade)")
|
||||
print("3. Software/Vendor Decommissioning")
|
||||
choice = input("Enter your choice (1-3): ")
|
||||
request_types = {
|
||||
"1": "New Software/Vendor",
|
||||
"2": "Existing Software/Vendor (Renewal/Upgrade)",
|
||||
"3": "Software/Vendor Decommissioning"
|
||||
}
|
||||
data['request_type'] = request_types.get(choice)
|
||||
|
||||
# 3. Deployment Model
|
||||
print("\nDeployment Model:")
|
||||
print("1. SaaS (Software as a Service)")
|
||||
print("2. IaaS (Infrastructure as a Service)")
|
||||
print("3. PaaS (Platform as a Service)")
|
||||
print("4. On-Premises (Internally Hosted)")
|
||||
print("5. Hybrid")
|
||||
print("6. Cloud-Hosted")
|
||||
print("7. Other (Please Specify)")
|
||||
choice = input("Enter your choice (1-7): ")
|
||||
if choice == "7":
|
||||
data['deployment_model'] = input("Specify the deployment model: ")
|
||||
else:
|
||||
deployment_models = {
|
||||
"1": "SaaS (Software as a Service)",
|
||||
"2": "IaaS (Infrastructure as a Service)",
|
||||
"3": "PaaS (Platform as a Service)",
|
||||
"4": "On-Premises (Internally Hosted)",
|
||||
"5": "Hybrid",
|
||||
"6": "Cloud-Hosted"
|
||||
}
|
||||
data['deployment_model'] = deployment_models.get(choice)
|
||||
|
||||
# 4. Software/Vendor Information
|
||||
data['vendor_name'] = input("Enter the Software/Vendor Name: ")
|
||||
data['software_name'] = input("Enter the Software Name: ")
|
||||
data['vendor_website'] = input("Enter the Software/Vendor Website: ")
|
||||
data['vendor_contact'] = input("Enter the Software/Vendor Contact Information: ")
|
||||
data['vendor_description'] = input("Enter a Brief Description of Software/Vendor and its Purpose: ")
|
||||
|
||||
# 5. Intended Use
|
||||
data['intended_use'] = input("Describe how the software/vendor will be used within the company: ")
|
||||
data['departments_teams'] = input("Specify the departments or teams that will be using the software/vendor: ")
|
||||
data['critical_processes_data'] = input("Identify any critical business processes or data that will be impacted by the software/vendor: ")
|
||||
|
||||
# 6. Prerequisites
|
||||
data['os_requirements'] = input("List any Operating System Requirements: ")
|
||||
data['hardware_requirements'] = input("List any Hardware Requirements (e.g., RAM, storage space): ")
|
||||
data['network_connectivity'] = input("List any Network Connectivity Requirements (e.g., bandwidth, VPN): ")
|
||||
data['software_dependencies'] = input("List any Software Dependencies (e.g., libraries, frameworks): ")
|
||||
data['user_access_requirements'] = input("List any User Access Requirements (e.g., specific roles, permissions): ")
|
||||
data['other_prerequisites'] = input("List any Other Prerequisites: ")
|
||||
|
||||
# 7. Technical Information
|
||||
data['hosting_environment'] = input("Provide the Hosting Environment (e.g., cloud provider, on-premise data center): ")
|
||||
data['network_requirements'] = input("Provide the Network Requirements (e.g., ports, protocols, firewall rules): ")
|
||||
data['data_storage_requirements'] = input("Provide the Data Storage Requirements (e.g., location, encryption, data retention policies): ")
|
||||
data['integration_existing_systems'] = input("Describe the Integration with Existing Systems (if any): ")
|
||||
data['api_information'] = input("Provide API Information (if applicable): ")
|
||||
|
||||
# 8. Security Requirements
|
||||
data['data_protection'] = input("Data Security - How will data be protected in transit and at rest?: ")
|
||||
data['data_privacy_compliance'] = input("Data Security - Does the software/vendor comply with relevant data privacy regulations (e.g., GDPR, CCPA)?: ")
|
||||
data['data_encryption_methods'] = input("Data Security - What data encryption methods are used?: ")
|
||||
data['access_control_management'] = input("Access Control - How will access to the software/vendor be granted and managed?: ")
|
||||
data['multi_factor_authentication'] = input("Access Control - Does the software/vendor support multi-factor authentication?: ")
|
||||
data['user_roles_permissions'] = input("Access Control - What user roles and permissions are available?: ")
|
||||
data['vulnerability_disclosure'] = input("Vulnerability Management - Does the software/vendor have a vulnerability disclosure program?: ")
|
||||
data['security_patch_management'] = input("Vulnerability Management - How are security patches and updates managed?: ")
|
||||
data['security_assessments'] = input("Vulnerability Management - Are regular security assessments and penetration testing conducted?: ")
|
||||
data['industry_security_standards'] = input("Compliance - Does the software/vendor comply with relevant industry security standards (e.g., ISO 27001, SOC 2)?: ")
|
||||
data['third_party_certifications'] = input("Compliance - Are there any third-party security certifications or audits available?: ")
|
||||
data['business_continuity_plans'] = input("Business Continuity and Disaster Recovery - What are the software/vendor's business continuity and disaster recovery plans?: ")
|
||||
data['rto_rpo'] = input("Business Continuity and Disaster Recovery - What are the recovery time objectives (RTOs) and recovery point objectives (RPOs)?: ")
|
||||
|
||||
# 9. Privacy Requirements
|
||||
data['personal_data_collected'] = input("Data Collection - What personal data will be collected by the software/vendor?: ")
|
||||
data['data_collection_purpose'] = input("Data Collection - What is the purpose of collecting this data?: ")
|
||||
data['data_sharing'] = input("Data Collection - Will data be shared with third parties? If so, with whom and for what purpose?: ")
|
||||
data['data_subject_rights'] = input("Data Subject Rights - How does the software/vendor ensure data subject rights (e.g., access, rectification, erasure)?: ")
|
||||
data['privacy_policy'] = input("Data Subject Rights - Does the software/vendor provide a privacy policy that is readily accessible to users?: ")
|
||||
|
||||
# 10. Legal and Contractual Requirements
|
||||
data['license_agreement'] = input("Provide the Software License Agreement: ")
|
||||
data['sla'] = input("Provide the Service Level Agreement (SLA): ")
|
||||
data['dpa'] = input("Provide the Data Processing Agreement (DPA): ")
|
||||
data['vendor_security_policies'] = input("Provide the Vendor Security Policies: ")
|
||||
|
||||
# 11. Risk Assessment
|
||||
data['potential_risks'] = input("Identify any potential security or privacy risks associated with the use of the software/vendor: ")
|
||||
data['risk_mitigation'] = input("Describe the risk mitigation measures that will be implemented: ")
|
||||
|
||||
# Generate a unique timestamp
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
# Sanitize vendor/software name for filename
|
||||
vendor_name = data['vendor_name'].replace(" ", "_").replace("/", "-")
|
||||
software_name = data['software_name'].replace(" ", "_").replace("/", "-")
|
||||
|
||||
# Create and save the Word document with unique filename
|
||||
doc_filename = f"{vendor_name}_{software_name}_assessment_{timestamp}.docx"
|
||||
doc = create_assessment_doc(data)
|
||||
doc.save(doc_filename)
|
||||
print(f"Word document saved as {doc_filename}")
|
||||
|
||||
# Convert to PDF with unique filename
|
||||
pdf_filename = f"{vendor_name}_{software_name}_assessment_{timestamp}.pdf"
|
||||
convert(doc_filename, pdf_filename)
|
||||
print(f"PDF document saved as {pdf_filename}")
|
||||
|
||||
# Create Markdown file with unique filename
|
||||
md_filename = f"{vendor_name}_{software_name}_assessment_{timestamp}.md"
|
||||
create_markdown_file(data, md_filename) # Pass filename to function
|
||||
print(f"Markdown file saved as {md_filename}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user