Skip to content

Commit 1fb94a6

Browse files
committed
Add GitHub secrets detection parser and tests
1 parent 4527392 commit 1fb94a6

6 files changed

Lines changed: 856 additions & 0 deletions

File tree

dojo/tools/github_secrets_detection_report/__init__.py

Whitespace-only changes.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import json
2+
from dojo.models import Finding
3+
4+
5+
class GithubSecretsDetectionReportParser(object):
6+
"""
7+
Import secrets detection report from GitHub
8+
"""
9+
10+
def get_scan_types(self):
11+
return ["Github Secrets Detection Report Scan"]
12+
13+
def get_label_for_scan_types(self, scan_type):
14+
return "Github Secrets Detection Report Scan"
15+
16+
def get_description_for_scan_types(self, scan_type):
17+
return "Github Secrets Detection Report report file can be imported in JSON format (option --json)."
18+
19+
def get_findings(self, file, test):
20+
data = json.load(file)
21+
22+
if not isinstance(data, list):
23+
error_msg = "Invalid GitHub secrets detection report format, expected a JSON list of alerts."
24+
raise TypeError(error_msg)
25+
26+
findings = []
27+
for alert in data:
28+
# Extract basic alert information
29+
alert_number = alert.get("number")
30+
state = alert.get("state", "open")
31+
secret_type = alert.get("secret_type", "Unknown")
32+
secret_type_display_name = alert.get("secret_type_display_name", secret_type)
33+
html_url = alert.get("html_url", "")
34+
35+
# Create title
36+
title = f"Exposed Secret Detected: {secret_type_display_name}"
37+
38+
# Build description
39+
desc_lines = []
40+
if html_url:
41+
desc_lines.append(f"**GitHub Alert**: [{html_url}]({html_url})")
42+
43+
desc_lines.append(f"**Secret Type**: {secret_type_display_name}")
44+
desc_lines.append(f"**Alert State**: {state}")
45+
46+
# Add repository information
47+
repository = alert.get("repository", {})
48+
if repository:
49+
repo_full_name = repository.get("full_name")
50+
if repo_full_name:
51+
desc_lines.append(f"**Repository**: {repo_full_name}")
52+
53+
# Add location information
54+
first_location = alert.get("first_location_detected", {})
55+
if first_location:
56+
file_path = first_location.get("path")
57+
start_line = first_location.get("start_line")
58+
end_line = first_location.get("end_line")
59+
60+
if file_path:
61+
desc_lines.append(f"**File Path**: {file_path}")
62+
if start_line:
63+
if end_line and end_line != start_line:
64+
desc_lines.append(f"**Lines**: {start_line}-{end_line}")
65+
else:
66+
desc_lines.append(f"**Line**: {start_line}")
67+
68+
# Add resolution information
69+
resolution = alert.get("resolution")
70+
if resolution:
71+
desc_lines.append(f"**Resolution**: {resolution}")
72+
73+
resolved_by = alert.get("resolved_by")
74+
if resolved_by:
75+
resolved_by_login = resolved_by.get("login", "Unknown")
76+
desc_lines.append(f"**Resolved By**: {resolved_by_login}")
77+
78+
resolved_at = alert.get("resolved_at")
79+
if resolved_at:
80+
desc_lines.append(f"**Resolved At**: {resolved_at}")
81+
82+
resolution_comment = alert.get("resolution_comment")
83+
if resolution_comment:
84+
desc_lines.append(f"**Resolution Comment**: {resolution_comment}")
85+
86+
# Add push protection information
87+
push_protection_bypassed = alert.get("push_protection_bypassed", False)
88+
if push_protection_bypassed:
89+
desc_lines.append("**Push Protection Bypassed**: True")
90+
91+
bypassed_by = alert.get("push_protection_bypassed_by")
92+
if bypassed_by:
93+
bypassed_by_login = bypassed_by.get("login", "Unknown")
94+
desc_lines.append(f"**Bypassed By**: {bypassed_by_login}")
95+
96+
bypassed_at = alert.get("push_protection_bypassed_at")
97+
if bypassed_at:
98+
desc_lines.append(f"**Bypassed At**: {bypassed_at}")
99+
else:
100+
desc_lines.append("**Push Protection Bypassed**: False")
101+
102+
# Add additional metadata
103+
validity = alert.get("validity", "unknown")
104+
desc_lines.append(f"**Validity**: {validity}")
105+
106+
publicly_leaked = alert.get("publicly_leaked", False)
107+
desc_lines.append(f"**Publicly Leaked**: {'Yes' if publicly_leaked else 'No'}")
108+
109+
multi_repo = alert.get("multi_repo", False)
110+
desc_lines.append(f"**Multi-Repository**: {'Yes' if multi_repo else 'No'}")
111+
112+
has_more_locations = alert.get("has_more_locations", False)
113+
if has_more_locations:
114+
desc_lines.append("**Note**: This secret has been detected in multiple locations")
115+
116+
description = "\n\n".join(desc_lines)
117+
118+
# Determine severity based on state and other factors
119+
if state == "resolved":
120+
severity = "Info"
121+
else:
122+
if validity == "active" and publicly_leaked:
123+
severity = "Critical"
124+
elif validity == "active":
125+
severity = "High"
126+
else:
127+
severity = "Medium"
128+
129+
# Create finding
130+
finding = Finding(
131+
title=title,
132+
test=test,
133+
description=description,
134+
severity=severity,
135+
static_finding=True,
136+
dynamic_finding=False,
137+
vuln_id_from_tool=str(alert_number) if alert_number else None,
138+
)
139+
140+
# Set file path and line information
141+
if first_location:
142+
finding.file_path = first_location.get("path")
143+
finding.line = first_location.get("start_line")
144+
145+
# Set external URL
146+
if html_url:
147+
finding.url = html_url
148+
149+
findings.append(finding)
150+
151+
return findings

0 commit comments

Comments
 (0)