Skip to content

Commit 67f88dd

Browse files
authored
Merge pull request #1 from advanced-security:include-locations
Add locations/commits and push protection estimate
2 parents 1ca8292 + 6bb76e1 commit 67f88dd

File tree

5 files changed

+243
-45
lines changed

5 files changed

+243
-45
lines changed

enrich_code_scanning_alerts.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ def enrich_alerts(alerts: list, metadata: dict) -> None:
129129

130130
def format_header(key: str) -> str:
131131
"""Format the heading depending on its value."""
132+
output = ""
133+
132134
if key not in ["cwe", "language"]:
133135
output = PUNCTUATION_RE.sub(" ", key).title()
134136
elif key == "cwe":
@@ -668,6 +670,8 @@ def main() -> None:
668670
enrich_alerts(alerts, metadata)
669671
fixup_alerts(alerts)
670672

673+
fields = []
674+
671675
if args.format in ["html", "pdf"]:
672676
fields = (
673677
[

estimate_push_protection_rate.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
3+
"""Estimate how many secrets would have been detected in a list of existing secret detections, and a list of which patterns have push protection now."""
4+
5+
import argparse
6+
import json
7+
from datetime import datetime, timezone
8+
9+
10+
def add_args(parser: argparse.ArgumentParser) -> None:
11+
"""Add command line arguments to the parser."""
12+
parser.add_argument(
13+
"secrets_file",
14+
type=str,
15+
help="Path to the file containing the list of secrets",
16+
)
17+
parser.add_argument(
18+
"patterns_file",
19+
type=str,
20+
help="Path to the file containing the list of patterns with push protection",
21+
)
22+
23+
24+
def main() -> None:
25+
"""Command line entry point."""
26+
parser = argparse.ArgumentParser(
27+
description="Estimate push protection rate for secrets"
28+
)
29+
add_args(parser)
30+
args = parser.parse_args()
31+
32+
with open(args.patterns_file, "r") as f:
33+
patterns: set = {line.strip() for line in f if line.strip()}
34+
35+
with open(args.secrets_file, "r") as f:
36+
secrets = json.load(f)
37+
38+
total_secrets = len(secrets)
39+
protected_secrets = [secret for secret in secrets if secret.get("secret_type") in patterns]
40+
41+
print(f"Total secrets: {total_secrets}")
42+
print(f"Protected secrets: {len(protected_secrets)}")
43+
44+
if total_secrets > 0:
45+
protection_rate = (len(protected_secrets) / total_secrets) * 100
46+
print(f"Estimated push protection rate: {protection_rate:.2f}%")
47+
else:
48+
print("No secrets found to evaluate.")
49+
50+
# now evaluate how often we'd expect to block pushes, using the `first_commit_date` field
51+
# that's in ISO format with a Z suffix
52+
now = datetime.now(timezone.utc)
53+
54+
# find the oldest blocked commit
55+
earliest_blocked_commit_date = min([
56+
datetime.fromisoformat(secret["first_commit_date"].replace("Z", "+00:00"))
57+
for secret in protected_secrets
58+
])
59+
60+
blocking_timespan = now - earliest_blocked_commit_date
61+
rate = len(protected_secrets) / blocking_timespan.days if blocking_timespan.days > 0 else len(protected_secrets)
62+
63+
print(f"Estimated secrets blocked per day since {earliest_blocked_commit_date.date()}: {rate:.2f}")
64+
65+
66+
if __name__ == "__main__":
67+
main()

githubapi.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@
2626
ISO_NO_TZ_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
2727
VALID_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]{1,39}$")
2828

29+
GENERIC_SECRET_TYPES = ",".join(
30+
[
31+
"http_basic_authentication_header",
32+
"http_bearer_authentication_header",
33+
"mongodb_connection_string",
34+
"mysql_connection_string",
35+
"openssh_private_key",
36+
"pgp_private_key",
37+
"postgres_connection_string",
38+
"rsa_private_key",
39+
"password", # Copilot powered secret detection
40+
]
41+
)
42+
2943

3044
class RateLimited(Exception):
3145
"""Rate limited exception."""
@@ -54,7 +68,7 @@ def __init__(self, token: str | None = None, hostname="github.com") -> None:
5468
self.hostname = hostname
5569

5670
@classmethod
57-
def check_name(self, name: str, scope: str) -> bool:
71+
def check_name(cls, name: str, scope: str) -> bool:
5872
"""Check the name is valid."""
5973
# check repo slug has <owner</<repo> format or org/Enterprise name is valid
6074
if scope == "repo":
@@ -112,7 +126,7 @@ def query(
112126
if paging is None:
113127
try:
114128
result = self._do(url, method, data=data)
115-
yield result
129+
yield result.json()
116130
except Exception as e:
117131
LOG.error("Error: %s", e)
118132
# show traceback without raising the exception
@@ -161,6 +175,8 @@ def construct_api_url(
161175

162176
path = api_path + scope_path + endpoint
163177

178+
query_params = {}
179+
164180
if paging is None:
165181
query_params = {}
166182
elif paging == "cursor":
@@ -314,7 +330,7 @@ def paginate(
314330
break
315331

316332
if progress:
317-
pbar.update(1)
333+
pbar.update(1) # type: ignore
318334

319335
LOG.debug(data)
320336

@@ -413,9 +429,14 @@ def list_secret_scanning_alerts(
413429
since: datetime.datetime | None = None,
414430
scope: str = "org",
415431
bypassed: bool = False,
432+
generic: bool = False,
416433
) -> Generator[dict, None, None]:
417434
"""List secret scanning alerts for a GitHub repository, organization or Enterprise."""
418435
query = {"state": state} if state is not None else {}
436+
437+
if generic:
438+
query["secret_type"] = GENERIC_SECRET_TYPES
439+
419440
alerts = self.query(
420441
scope,
421442
name,

list_code_scanning_alerts.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import sys
66
import argparse
7-
import re
87
import logging
98
import datetime
109
import json

0 commit comments

Comments
 (0)