From 6575e0168d305b230aedc445d5b80cd6168f0a91 Mon Sep 17 00:00:00 2001 From: forefy Date: Fri, 9 Feb 2024 21:15:40 +0200 Subject: [PATCH] Add markdown report support --- .gitignore | 3 +- README.md | 14 +++++-- eburger/main.py | 4 ++ eburger/utils/logger.py | 5 ++- eburger/utils/outputs.py | 91 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 109 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 36058fe..f1b69e4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,4 @@ dist/ **/cache .vscode *[0-9][0-9].py -*eburger-output.json -*.sarif \ No newline at end of file +*eburger-output* \ No newline at end of file diff --git a/README.md b/README.md index 88bc857..8e32a43 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Template Based Smart Contracts Static Analyzer

-eBurger +eBurger

@@ -70,14 +70,21 @@ cat eburger-output.json
SARIF output -``` +```bash eburger -f MyProject/ -o sarif ```
-Focused scan of a single file and it's dependencies: +Markdown output +```bash +eburger -f MyProject/ -o markdown ``` + +
+ +Focused scan of a single file and it's dependencies: +```bash eburger -f ../ProjectToScan/src/SomeContract.sol ``` @@ -132,6 +139,7 @@ Forgot one or made a mistake? open a pull request or an issue :) - Fast learning curve for creating templates, ability to customize templates to the current ongoing audit project - Foundry and Hardhat support ❣️ - SARIF support & VSCode GUI integration +- Markdown report - Community and free support via [Discord](https://discord.gg/WaVMpBtxdB) diff --git a/eburger/main.py b/eburger/main.py index 5d4f18f..714ba55 100644 --- a/eburger/main.py +++ b/eburger/main.py @@ -32,6 +32,7 @@ calculate_nsloc, draw_nsloc_table, save_as_json, + save_as_markdown, save_as_sarif, ) from eburger.yaml_parser import process_files_concurrently @@ -246,6 +247,9 @@ def main(): if args.output == "sarif": results_path = settings.project_root / f"eburger-output.sarif" save_as_sarif(results_path, insights) + elif args.output in ["markdown", "md"]: + results_path = settings.project_root / f"eburger-output.md" + save_as_markdown(results_path, analysis_output) else: save_as_json(results_path, analysis_output) diff --git a/eburger/utils/logger.py b/eburger/utils/logger.py index 63c1892..058d84c 100644 --- a/eburger/utils/logger.py +++ b/eburger/utils/logger.py @@ -45,8 +45,11 @@ def log(type: str, message: str): print(f"[{color.Success} 🍔 Success {color.Default}] {message}") case "error": print(f"[{color.Error} Error {color.Default}] {message}") + post_error_message = " We are so sorry 😭🍔 please consider" + if not args.debug: + post_error_message += " running with --debug or" print( - " We are so sorry 😭🍔 try running with --debug or create an issue on https://github.com/forefy/eburger/issues/new?assignees=forefy&template=bug_report.md&title=Tool%20error%20report" + f"{post_error_message} creating an issue on https://github.com/forefy/eburger/issues/new?assignees=forefy&template=bug_report.md&title=Tool%20error%20report" ) sys.exit(0) case "warning": diff --git a/eburger/utils/outputs.py b/eburger/utils/outputs.py index e01a6c5..7ea32c7 100644 --- a/eburger/utils/outputs.py +++ b/eburger/utils/outputs.py @@ -1,9 +1,10 @@ # Silence tool prints +from datetime import datetime import json import os from pathlib import Path import sys -from prettytable import PrettyTable +from prettytable import PrettyTable, MARKDOWN from pygount import ProjectSummary, SourceAnalysis from eburger import settings @@ -16,6 +17,92 @@ def save_as_json(file_path: Path, json_data: dict): json.dump(json_data, outfile, indent=4) +def save_as_markdown(output_file_path: Path, json_data: dict): + md_report_root_dir = Path(output_file_path).resolve().parent + + markdown_content = "# eBurger Static Analysis Report\n\n" + markdown_content += f"This report was generated by the [eBurger](https://github.com/forefy/eburger) static analyzer on {datetime.now().strftime('%d.%m.%Y at %H:%M')}.\n\nThe results are not intended to substitute for comprehensive audits or professional assessments, and should be used only as a tool to help identify possible security vulnerabilities or insights and not for any other purpose.\n\n" + + markdown_content += "## Insights Summary\n\n" + markdown_content += "| Issue | Severity | Occurrences |\n" + markdown_content += "|-------|----------|-------------|\n" + + details_md = "" + + for issue in json_data["insights"]: + name = f"[{issue['severity']}] {issue['name']}" + severity = issue["severity"] + occurrences = len(issue["results"]) + anchor_name = ( + name.replace(" ", "-") + .replace(",", "") + .replace(".", "") + .replace("[", "") + .replace("]", "") + .lower() + ) + + # Add issue to the table with a hyperlink to its detailed description + markdown_content += ( + f"| [{name}](#{anchor_name}) | {severity} | {occurrences} |\n" + ) + + # Generate detailed description for each issue + details_md += f"\n### {name}\n{issue['description']}\n\n" + details_md += "#### Vulnerable Locations\n" + for result in issue["results"]: + try: + # Convert the file path to be absolute and normalize it + full_path = Path(result["file"]).resolve() + # Calculate the relative path safely + relative_path = full_path.relative_to(md_report_root_dir) + + line_info, column_info = ( + result["lines"] + .replace("Line ", "") + .replace("Columns ", "") + .split(" ") + ) + start_line = line_info + start_column, end_column = column_info.split("-") + location_format = ( + f"{relative_path}:{start_line}:{start_column}-{end_column}" + ) + details_md += f"- {location_format}\n" + + # Include the vulnerable code snippet + if "code" in result: + code_snippet = result["code"].replace( + "\n", "\n\t" + ) # Indent for better readability + details_md += f"\t```\n\t{code_snippet}\n\t```\n" + except ValueError as e: + log("error", f"Error processing path {result['file']}: {e}") + + details_md += "\n" + + if "action-items" in issue: + details_md += ( + "#### Action Items\n" + + "\n".join(f"- {item}" for item in issue["action-items"]) + + "\n\n" + ) + if "references" in issue: + details_md += ( + "#### References\n" + + "\n".join(f"- [{ref}]({ref})" for ref in issue["references"]) + + "\n\n" + ) + details_md += "---\n" + + markdown_content += "\n" + details_md + + markdown_content += '

Generated by eBurger

\n' + + with open(output_file_path, "w") as md_file: + md_file.write(markdown_content) + + def convert_severity_to_sarif_level(severity: str) -> str: match severity: case "High": @@ -157,7 +244,7 @@ def save_as_sarif(output_file_path: Path, insights: dict): json.dump(sarif_json, outfile, indent=4) -def calculate_nsloc() -> (list, list): +def calculate_nsloc() -> tuple[list, list]: project_summary = ProjectSummary() project_sources = [] solidity_file_or_folder = Path(args.solidity_file_or_folder)