Created
July 15, 2026 13:20
-
-
Save Melkor333/ade73b2b89f7033d9cff4820059e52ab to your computer and use it in GitHub Desktop.
generate-report-without-dependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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