Skip to content

Instantly share code, notes, and snippets.

@Melkor333
Created July 15, 2026 13:20
Show Gist options
  • Select an option

  • Save Melkor333/ade73b2b89f7033d9cff4820059e52ab to your computer and use it in GitHub Desktop.

Select an option

Save Melkor333/ade73b2b89f7033d9cff4820059e52ab to your computer and use it in GitHub Desktop.
generate-report-without-dependencies
from pathlib import Path
from datetime import date, datetime
import requests
import os
import json
# The values in the template are originally hidden as HTML comments
# This function adds onto the python string replace by adding HTML comments to the string to find
# And then replacing it
def replace_comment_string(original_string, part_to_replace, new_value):
original_string = original_string.replace(f'<!-- {part_to_replace} -->', f'{new_value}')
return original_string
def add_vulnerability_row(original_string, vulnerability):
title = vulnerability["title"]
detected_date = datetime.strptime(vulnerability["detectedAt"], "%Y-%m-%dT%H:%M:%SZ").strftime("%B %d, %Y")
severity = vulnerability["severity"].lower().capitalize()
state = vulnerability["state"]
url = vulnerability["webUrl"]
project = vulnerability["project"]["nameWithNamespace"]
projectUrl = vulnerability["project"]["webUrl"]
row = f'<tr><td class="p-2">{detected_date}</td><td class="p-2">{severity}</td><td class="p-2 w-6/12"><a class="text-blue-600 hover:text-blue-800 visited:text-purple-600" href="{url}">{title}</a></td><td class="p-2"><a class="text-blue-600 hover:text-blue-800 visited:text-purple-600" href="{projectUrl}">{project}</a></td><td class="p-2">{state}</td></tr>'
part_to_replace = '<!-- row -->'
original_string = original_string.replace(part_to_replace, row + '\n' + part_to_replace)
return original_string
def get_vulnerabilities(end_cursor = ''):
# Get The API Token From The Project CI / CD Variables
token = os.environ.get("GRAPHQL_API_TOKEN")
# Get The URL To Your GraphQL API From The Project CI / CD Variables
url = os.environ.get("GRAPHQL_API_URL")
# Get Th The Path Of The Project You Want Vulnerabilities For From The Project CI / CD Variables
project_path = os.environ.get("PROJECT_PATH")
# GraphQL Query To Get List Of Vulnerabilities For A Project
# TODO: This is a hack to insert variables into a multi line string
vulnerability_query = """
query {
group(fullPath: "<project_path>") {
vulnerabilities(state: [DETECTED, CONFIRMED]<end_cursor>) {
nodes {
detectedAt
title
severity
state
webUrl
project {
nameWithNamespace
webUrl
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
"""
vulnerability_query = vulnerability_query.replace("<project_path>", project_path)
if len(end_cursor) > 0:
vulnerability_query = vulnerability_query.replace("<end_cursor>", f', after: "{end_cursor}"')
else:
vulnerability_query = vulnerability_query.replace("<end_cursor>", '')
# Debugging
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print(f"GraphQL URL: {url}")
print(f"Project Path: {project_path}")
print("Query:")
print(vulnerability_query)
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
print("****************************************")
# Set Headers And Make THe Request
headers = {"Authorization": f'Bearer {token}'}
project_request = requests.post(url, json={'query': vulnerability_query}, headers=headers)
print(project_request.text)
# Convert The Response To JSON
json_data = json.loads(project_request.text)
vulnerability_list = json_data["data"]["group"]["vulnerabilities"]["nodes"]
has_next_page = json_data["data"]["group"]["vulnerabilities"]["pageInfo"]["hasNextPage"]
end_cursor = json_data["data"]["group"]["vulnerabilities"]["pageInfo"]["endCursor"]
return vulnerability_list, has_next_page, end_cursor
if __name__ == "__main__":
# Get The Text From The HTML Template As The String
html_template = Path('template.html').read_text()
# Update The Report Date
today = date.today()
formatted_date = today.strftime("%B %d, %Y")
html_template = replace_comment_string(html_template, "@report_date", formatted_date)
has_next_page = True
end_cursor = ''
critical_vulnerability_count = 0
high_vulnerability_count = 0
medium_vulnerability_count = 0
low_vulnerability_count = 0
info_vulnerability_count = 0
unknown_vulnerability_count = 0
critical_vulnerability_total_count = 0
high_vulnerability_total_count = 0
medium_vulnerability_total_count = 0
low_vulnerability_total_count = 0
info_vulnerability_total_count = 0
unknown_vulnerability_total_count = 0
while has_next_page:
vulnerability_response = get_vulnerabilities(end_cursor)
vulnerability_list = vulnerability_response[0]
has_next_page = vulnerability_response[1]
end_cursor = vulnerability_response[2]
for vulnerability in vulnerability_list:
# Add a vulnerability row to the table
html_template = add_vulnerability_row(html_template, vulnerability)
# Update the count
severity = vulnerability["severity"].lower().capitalize()
match severity:
case 'Critical':
critical_vulnerability_total_count+=1
case 'High':
high_vulnerability_total_count+=1
case 'Medium':
medium_vulnerability_total_count+=1
case 'Low':
low_vulnerability_total_count+=1
case 'Info':
info_vulnerability_total_count+=1
case 'Unknown':
unknown_vulnerability_total_count+=1
# We only care about unhandled vulns now
if vulnerability["state"] != "DETECTED":
continue
match severity:
case 'Critical':
critical_vulnerability_count+=1
case 'High':
high_vulnerability_count+=1
case 'Medium':
medium_vulnerability_count+=1
case 'Low':
low_vulnerability_count+=1
case 'Info':
info_vulnerability_count+=1
case 'Unknown':
unknown_vulnerability_count+=1
# Update The Count Variables
html_template = replace_comment_string(html_template, "@critical_vulnerability_count", f'{critical_vulnerability_count}/{critical_vulnerability_total_count}' )
html_template = replace_comment_string(html_template, "@high_vulnerability_count", f'{high_vulnerability_count}/{high_vulnerability_total_count}' )
html_template = replace_comment_string(html_template, "@medium_vulnerability_count", f'{medium_vulnerability_count}/{medium_vulnerability_total_count}' )
html_template = replace_comment_string(html_template, "@low_vulnerability_count", f'{low_vulnerability_count}/{low_vulnerability_total_count}')
html_template = replace_comment_string(html_template, "@info_vulnerability_count", f'{info_vulnerability_count}/{info_vulnerability_total_count}' )
html_template = replace_comment_string(html_template, "@unknown_vulnerability_count", f'{unknown_vulnerability_count}/{unknown_vulnerability_total_count}')
# Save The File As HTML
with open('report.html', 'w') as html_report:
html_report.write(html_template)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment