Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 112 additions & 9 deletions scripts/vulnerability_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,39 @@
Rretrieves Dependabot issues, analyses issues, generates graphs, and generates
HTML page with Vulnerability report. Is is also possible to compare two
repositories.

# Usage:
usage: vulnerability_report.py [-h] [-v] --organization ORGANIZATION
--repository REPOSITORY [-r] [-g] [-p]
[-c COMPARISON [COMPARISON ...]]

Vulnerability report tool

options:
-h, --help show this help message and exit
-v, --verbose make it verbose
--organization ORGANIZATION
GitHub organization.
--repository REPOSITORY
GitHub repository.
-r, --retrieve-issues
Retrieve issues
-g, --generate-graphs
Generate graphs with vulnerabilities info
-p, --generate-page Generate page with vulnerabilities info
-c, --comparison COMPARISON [COMPARISON ...]
Compare two or more repositories and generate
comparison report. Multiple JSON files with Dependabot
alerts needs to be provided
"""

from argparse import ArgumentParser
from argparse import ArgumentParser, Namespace
import json

from typing import Any

type DependabotAlert = dict[str, Any]
type DependabotAlerts = list[DependabotAlert]


def create_argument_parser() -> ArgumentParser:
Expand Down Expand Up @@ -46,35 +76,107 @@ def create_argument_parser() -> ArgumentParser:
parser.add_argument(
"-r",
"--retrieve-issues",
default=True,
action="store_true",
default=False,
help="Retrieve issues",
)

parser.add_argument(
"-g",
"--generate-graphs",
default=True,
action="store_true",
default=False,
help="Generate graphs with vulnerabilities info",
)

parser.add_argument(
"-p",
"--generate-page",
default=True,
action="store_true",
default=False,
help="Generate page with vulnerabilities info",
)

parser.add_argument(
"-c",
"--comparison",
default=False,
help="Compare two repositories and generate comparison report. "
"Need to be used with --data1 and --data2 options",
required=False,
nargs="+",
default=[],
help="Compare two or more repositories and generate comparison report. "
"Multiple JSON files with Dependabot alerts needs to be provided",
)
Comment on lines 100 to 108

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

--comparison is documented/parsed but never used in execution flow.

The parser advertises comparison mode, but main() always processes only organization__repository.json. This is a contract break for the CLI option.

Also applies to: 199-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vulnerability_report.py` around lines 100 - 108, The --comparison
argument is parsed but never utilized in the main() function, breaking the
advertised CLI contract. In the main() function, add a conditional check to
determine if comparison mode is enabled by verifying if the comparison argument
contains values. When comparison mode is active, implement logic to process
multiple JSON files provided in the comparison argument instead of only
processing organization__repository.json. Ensure the current single-file
processing remains the default behavior when the comparison argument is not
provided. This will make the parser argument definition match the actual
execution flow.


return parser


def dependabot_file_name(args: Namespace) -> str:
"""Construct file name containing Dependabot alerts."""
return f"{args.organization}__{args.repository}.json"


def load_dependabot_file(filename: str) -> Any:
"""Load JSON file containing Dependabot alerts."""
with open(filename, "r") as fin:
return json.load(fin)


def has_attribute_with_value(item: DependabotAlert, attribute: str, value: str) -> bool:
"""Check if dictionary has attribute with given value."""
return bool(item[attribute] == value)


def has_deep_attribute_with_value(
item: DependabotAlert, selector: str, attribute: str, value: str
) -> bool:
"""Check if dictionary has deep attribute with given value."""
return bool(item[selector][attribute] == value)
Comment on lines +118 to +133

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate Dependabot JSON schema at load time to prevent runtime crashes.

Helpers assume keys always exist (item[attribute], item[selector][attribute]). A single malformed alert will raise KeyError/TypeError and stop report generation.

Proposed fix
 def load_dependabot_file(filename: str) -> Any:
     """Load JSON file containing Dependabot alerts."""
-    with open(filename, "r") as fin:
-        return json.load(fin)
+    with open(filename, "r", encoding="utf-8") as fin:
+        data = json.load(fin)
+    if not isinstance(data, list):
+        raise ValueError("Dependabot alerts file must contain a JSON array")
+    for idx, item in enumerate(data):
+        if not isinstance(item, dict):
+            raise ValueError(f"Alert at index {idx} must be an object")
+        if "state" not in item:
+            raise ValueError(f"Alert at index {idx} is missing 'state'")
+        sa = item.get("security_advisory")
+        if not isinstance(sa, dict) or "severity" not in sa:
+            raise ValueError(
+                f"Alert at index {idx} is missing 'security_advisory.severity'"
+            )
+    return data

Also applies to: 136-155

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vulnerability_report.py` around lines 118 - 133, The helper functions
has_attribute_with_value and has_deep_attribute_with_value directly access
nested dictionary keys without validation, which causes KeyError or TypeError
exceptions if the JSON is malformed. Add JSON schema validation in the
load_dependabot_file function to validate the Dependabot alert structure when
the file is loaded, ensuring only properly formatted data is returned and
preventing runtime crashes when the helper functions access attributes on
malformed items.



def count_attribute_with_value(
items: DependabotAlerts, attribute: str, value: str
) -> int:
"""Count all attributes with given value."""
cnt: int = 0
for item in items:
if has_attribute_with_value(item, attribute, value):
cnt += 1
return cnt


def count_deep_attribute_with_value(
items: DependabotAlerts, selector: str, attribute: str, value: str
) -> int:
"""Count all deep attributes with given value."""
cnt: int = 0
for item in items:
if has_deep_attribute_with_value(item, selector, attribute, value):
cnt += 1
return cnt


def opened_cves(source_data: DependabotAlerts) -> int:
"""Compute how many CVEs are opened."""
return count_attribute_with_value(source_data, "state", "open")


def fixed_cves(source_data: DependabotAlerts) -> int:
"""Compute how many CVEs has been fixed opened."""
return count_attribute_with_value(source_data, "state", "fixed")


def with_severity(severity: str, source_data: DependabotAlerts) -> int:
"""Count number of CVE having specified severity."""
return count_deep_attribute_with_value(
source_data, "security_advisory", "severity", severity
)


def process_dependabot_file(dependabot_file: str, prefix: str) -> dict[str, Any]:
"""Read Dependabot alerts and prepare statistic info."""
return {}
Comment on lines +175 to +177

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

main() currently performs no effective processing.

process_dependabot_file() is a stub returning {}, and main() ignores its result. The CLI succeeds without producing report data, which breaks the processing flow.

Proposed fix
 def process_dependabot_file(dependabot_file: str, prefix: str) -> dict[str, Any]:
     """Read Dependabot alerts and prepare statistic info."""
-    return {}
+    source_data: DependabotAlerts = load_dependabot_file(dependabot_file)
+    return {
+        "prefix": prefix,
+        "opened_cves": opened_cves(source_data),
+        "fixed_cves": fixed_cves(source_data),
+        "critical": with_severity("critical", source_data),
+        "high": with_severity("high", source_data),
+        "medium": with_severity("medium", source_data),
+        "low": with_severity("low", source_data),
+    }

@@
-    process_dependabot_file(dependabot_file, prefix)
+    report = process_dependabot_file(dependabot_file, prefix)
+    print(json.dumps(report))
     return 0

Also applies to: 199-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vulnerability_report.py` around lines 175 - 177, The function
process_dependabot_file() is currently a stub that returns an empty dictionary,
and main() ignores its result, breaking the report generation flow. Implement
process_dependabot_file() to read the Dependabot alerts file specified by the
dependabot_file parameter and return a dictionary containing the processed alert
statistics. Then update main() to capture the dictionary returned from
process_dependabot_file() and incorporate that data into the report generation
logic so the vulnerability report contains actual data.



def main() -> int:
"""
CLI entry point that retrieves Dependabot issues and produces Vulnerability report.
Expand All @@ -94,8 +196,9 @@ def main() -> int:
"""
parser = create_argument_parser()
args = parser.parse_args()

print(args)
dependabot_file = dependabot_file_name(args)
prefix = args.repository
process_dependabot_file(dependabot_file, prefix)
return 0


Expand Down
Loading