From 65136a1e8bf54d81de6dc86ba0e20d4b86601c4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 21:55:28 +0000 Subject: [PATCH 1/8] Bump requests from 2.26.0 to 2.31.0 Bumps [requests](https://github.com/psf/requests) from 2.26.0 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.26.0...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4f5b899..077c95d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests==2.26.0 \ No newline at end of file +requests==2.31.0 \ No newline at end of file From ebb383c6d317498b6e67a8f72003aeaf6923e381 Mon Sep 17 00:00:00 2001 From: Tim Kelly <1355145+austimkelly@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:00:46 -0600 Subject: [PATCH 2/8] Create dependency-review.yml --- .github/workflows/dependency-review.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..b0dedc4 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,20 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v3 From b6150a5c5edbcf6be342b00373dd4e4ee03bd8a2 Mon Sep 17 00:00:00 2001 From: Tim Kelly Date: Mon, 20 Nov 2023 18:12:14 -0600 Subject: [PATCH 3/8] make sure code scanning tools reported are unique --- ghas-scan.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ghas-scan.py b/ghas-scan.py index 7aa17e8..e1c5a39 100644 --- a/ghas-scan.py +++ b/ghas-scan.py @@ -67,21 +67,26 @@ def get_dependabot_alerts(owner, repo_name, headers): ) def get_code_scanning_tool_names(owner, repo_name, headers): - url = "https://api.github.com/repos/" + owner + "/" + repo_name + "/code-scanning/analyses" + url = f'https://api.github.com/repos/{owner}/{repo_name}/code-scanning/analyses' + #print("Request URL:", url) try: response = requests.get(url, headers=headers) - if response.status_code == 404: - return "None" response.raise_for_status() # Raises a HTTPError if the status is 4xx, 5xx + data = response.json() - + if data: - return data[0]['tool']['name'] + # Extract unique tools from the analyses + tools = list(set(analysis['tool']['name'] for analysis in data)) + return ', '.join(tools) else: return "No data received from the server" except requests.exceptions.HTTPError as http_err: - return f"HTTPError: {http_err}" + if response.status_code == 404: + return "Code scanning not set up for this repository." + else: + return f"HTTPError: {http_err}" except Exception as err: return f"Error occurred: {err}" From a0d250f6fc48570916ca335011b289e60fee0ca5 Mon Sep 17 00:00:00 2001 From: Tim Kelly Date: Mon, 20 Nov 2023 19:17:51 -0600 Subject: [PATCH 4/8] code cleanup --- ghas-scan.py | 51 +++++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/ghas-scan.py b/ghas-scan.py index e1c5a39..f443eae 100644 --- a/ghas-scan.py +++ b/ghas-scan.py @@ -84,17 +84,12 @@ def get_code_scanning_tool_names(owner, repo_name, headers): return "No data received from the server" except requests.exceptions.HTTPError as http_err: if response.status_code == 404: - return "Code scanning not set up for this repository." + return "None" else: return f"HTTPError: {http_err}" except Exception as err: return f"Error occurred: {err}" -def is_secrets_scanning_enabled(owner, repo_name, headers): - secrets_scanning_url = f'https://api.github.com/repos/{owner}/{repo_name}/secret-scanning/alerts' - response = requests.get(secrets_scanning_url, headers=headers) - return len(response.json()) > 0 - def get_codeowners(owner, repo_name, headers): codeowners_locations = [ f'https://api.github.com/repos/{owner}/{repo_name}/CODEOWNERS', @@ -138,11 +133,24 @@ def get_repo_details(owner, repo_name, headers): is_archived = repo_info['archived'] # Check if secrets scanning is enabled - secrets_scanning_enabled = is_secrets_scanning_enabled(owner, repo_name, headers) - + security_and_analysis = repo_info.get('security_and_analysis', {}) + #advanced_security = security_and_analysis.get('advanced_security', {}) + secret_scanning = security_and_analysis.get('secret_scanning', {}) + secret_scanning_push_protection = security_and_analysis.get('secret_scanning_push_protection', {}) + + # even though scheam docs say this exists, it doesn't + # https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user + # security_and_analysis_enabled = advanced_security.get('status') == 'enabled' if advanced_security else False + secret_scanning_enabled = secret_scanning.get('status') == 'enabled' if secret_scanning else False + secret_scanning_push_protection_enabled = secret_scanning_push_protection.get('status') == 'enabled' if secret_scanning_push_protection else False + # Get names of code scanners code_scanners_enabled = get_code_scanning_tool_names(owner, repo_name, headers) + security_and_analysis_enabled = False + if code_scanners_enabled != "None": + security_and_analysis_enabled = True + # Check the number of Dependabot alerts dependabot_enabled, open_alerts_count, num_critical_dep_alerts, num_high_dep_alerts, num_medium_dep_alerts, num_low_dep_alerts = get_dependabot_alerts(owner, repo_name, headers) @@ -154,25 +162,9 @@ def get_repo_details(owner, repo_name, headers): 'is_fork': is_fork, 'is_private': is_private, 'is_archived': is_archived, - 'secrets_scanning_enabled': secrets_scanning_enabled, - 'code_scanners_enabled': code_scanners_enabled, - 'dependabot_enabled': dependabot_enabled, - 'dependabot_open_alerts_count': open_alerts_count, - 'num_critical_dep_alerts': num_critical_dep_alerts, - 'num_high_dep_alerts': num_high_dep_alerts, - 'num_medium_dep_alerts': num_medium_dep_alerts, - 'num_low_dep_alerts': num_low_dep_alerts - # Add other details here - } - return { - 'repo_name': repo_info['name'], - 'codeowners': codeowners, - 'last_commit_date': last_commit_date, - 'first_commit_date': first_commit_date, - 'is_fork': is_fork, - 'is_private': is_private, - 'is_archived': is_archived, - 'secrets_scanning_enabled': secrets_scanning_enabled, + 'security_and_analysis_enabled': security_and_analysis_enabled, + 'secret_scanning_enabled': secret_scanning_enabled, + 'secret_scanning_push_protection_enabled': secret_scanning_push_protection_enabled, 'code_scanners_enabled': code_scanners_enabled, 'dependabot_enabled': dependabot_enabled, 'dependabot_open_alerts_count': open_alerts_count, @@ -182,6 +174,7 @@ def get_repo_details(owner, repo_name, headers): 'num_low_dep_alerts': num_low_dep_alerts # Add other details here } + def get_repos(owner, headers, owner_type): if owner_type == 'user': @@ -218,7 +211,9 @@ def get_repos(owner, headers, owner_type): 'is_fork', 'is_private', 'is_archived', - 'secrets_scanning_enabled', + 'security_and_analysis_enabled', + 'secret_scanning_enabled', + 'secret_scanning_push_protection_enabled', 'code_scanners_enabled', 'dependabot_enabled', 'dependabot_open_alerts_count', From 8d1e10a5c4d25d9c08a9919ac9cec7dd9918260d Mon Sep 17 00:00:00 2001 From: Tim Kelly Date: Mon, 20 Nov 2023 20:11:17 -0600 Subject: [PATCH 5/8] add in code alert counts. parse codeowners content if found --- ghas-scan.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/ghas-scan.py b/ghas-scan.py index f443eae..feef941 100644 --- a/ghas-scan.py +++ b/ghas-scan.py @@ -1,6 +1,8 @@ import csv import requests import os +import base64 +import re # Set the GitHub owner type, owner name, and personal access token owner_type = 'user' # Change to 'org' if needed @@ -66,6 +68,7 @@ def get_dependabot_alerts(owner, repo_name, headers): num_low_dep_alerts ) +# API Ref: https://docs.github.com/en/rest/code-scanning/code-scanning def get_code_scanning_tool_names(owner, repo_name, headers): url = f'https://api.github.com/repos/{owner}/{repo_name}/code-scanning/analyses' #print("Request URL:", url) @@ -90,21 +93,73 @@ def get_code_scanning_tool_names(owner, repo_name, headers): except Exception as err: return f"Error occurred: {err}" +# API Ref: https://docs.github.com/en/rest/reference/code-scanning#list-code-scanning-alerts-for-a-repository +def get_code_scanning_alert_counts(owner, repo_name, headers): + # Get the code scanning alerts for the repository + url = f'https://api.github.com/repos/{owner}/{repo_name}/code-scanning/alerts' + response = requests.get(url, headers=headers) + + code_scanning_critical_alert_count = 0 + code_scanning_high_alert_count = 0 + code_scanning_medium_alert_count = 0 + code_scanning_low_alert_count = 0 + code_scanning_warning_alert_count = 0 + code_scanning_note_alert_count = 0 + code_scanning_error_alert_count = 0 + + if response.status_code == 200: + alerts_data = response.json() + + # Filter open code scanning alerts + open_alerts = [alert for alert in alerts_data if alert['state'] == 'open'] + + # Count alerts based on severity + for alert in open_alerts: + severity = alert['rule']['severity'] + if severity == 'critical': + code_scanning_critical_alert_count += 1 + elif severity == 'high': + code_scanning_high_alert_count += 1 + elif severity == 'medium': + code_scanning_medium_alert_count += 1 + elif severity == 'low': + code_scanning_low_alert_count += 1 + elif severity == 'warning': + code_scanning_warning_alert_count += 1 + elif severity == 'note': + code_scanning_note_alert_count += 1 + elif severity == 'error': + code_scanning_error_alert_count += 1 + + return ( + code_scanning_critical_alert_count, + code_scanning_high_alert_count, + code_scanning_medium_alert_count, + code_scanning_low_alert_count, + code_scanning_warning_alert_count, + code_scanning_note_alert_count, + code_scanning_error_alert_count + ) + def get_codeowners(owner, repo_name, headers): codeowners_locations = [ - f'https://api.github.com/repos/{owner}/{repo_name}/CODEOWNERS', - f'https://api.github.com/repos/{owner}/{repo_name}/.github/CODEOWNERS', - f'https://api.github.com/repos/{owner}/{repo_name}/docs/CODEOWNERS' + f'https://api.github.com/repos/{owner}/{repo_name}/contents/CODEOWNERS', + f'https://api.github.com/repos/{owner}/{repo_name}/contents/.github/CODEOWNERS', + f'https://api.github.com/repos/{owner}/{repo_name}/contents/docs/CODEOWNERS' ] for location in codeowners_locations: codeowners_response = requests.get(location, headers=headers) - #print(f"Location: {location}, Status Code: {codeowners_response.status_code}") - if codeowners_response.status_code == 200: - codeowners = codeowners_response.text - return codeowners + codeowners_content = codeowners_response.json().get('content') + if codeowners_content is not None: + codeowners = base64.b64decode(codeowners_content).decode('utf-8') + + # Remove comments from CODEOWNERS + codeowners_lines = [line for line in codeowners.split('\n') if not line.strip().startswith('#')] + + return '\n'.join(codeowners_lines) return "Not found" @@ -147,6 +202,8 @@ def get_repo_details(owner, repo_name, headers): # Get names of code scanners code_scanners_enabled = get_code_scanning_tool_names(owner, repo_name, headers) + code_scanning_critical_alert_count,code_scanning_high_alert_count,code_scanning_medium_alert_count,code_scanning_low_alert_count,code_scanning_warning_alert_count,code_scanning_note_alert_count,code_scanning_error_alert_count = get_code_scanning_alert_counts(owner, repo_name, headers) + security_and_analysis_enabled = False if code_scanners_enabled != "None": security_and_analysis_enabled = True @@ -166,6 +223,13 @@ def get_repo_details(owner, repo_name, headers): 'secret_scanning_enabled': secret_scanning_enabled, 'secret_scanning_push_protection_enabled': secret_scanning_push_protection_enabled, 'code_scanners_enabled': code_scanners_enabled, + 'code_scanning_critical_alert_count': code_scanning_critical_alert_count, + 'code_scanning_high_alert_count': code_scanning_high_alert_count, + 'code_scanning_medium_alert_count': code_scanning_medium_alert_count, + 'code_scanning_low_alert_count': code_scanning_low_alert_count, + 'code_scanning_warning_alert_count': code_scanning_warning_alert_count, + 'code_scanning_note_alert_count': code_scanning_note_alert_count, + 'code_scanning_error_alert_count': code_scanning_error_alert_count, 'dependabot_enabled': dependabot_enabled, 'dependabot_open_alerts_count': open_alerts_count, 'num_critical_dep_alerts': num_critical_dep_alerts, @@ -215,6 +279,13 @@ def get_repos(owner, headers, owner_type): 'secret_scanning_enabled', 'secret_scanning_push_protection_enabled', 'code_scanners_enabled', + 'code_scanning_critical_alert_count', + 'code_scanning_high_alert_count', + 'code_scanning_medium_alert_count', + 'code_scanning_low_alert_count', + 'code_scanning_warning_alert_count', + 'code_scanning_note_alert_count', + 'code_scanning_error_alert_count', 'dependabot_enabled', 'dependabot_open_alerts_count', 'num_critical_dep_alerts', From 0774f4297b2b5116ebdebb24a0137499db6b92dc Mon Sep 17 00:00:00 2001 From: Tim Kelly Date: Tue, 21 Nov 2023 12:17:07 -0600 Subject: [PATCH 6/8] provide some output example. print out status and metrics at the end --- README.md | 8 +++++--- example/example_output.csv | 17 +++++++++++++++++ ghas-scan.py | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 example/example_output.csv diff --git a/README.md b/README.md index 6f34e6a..9ba0e59 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ This is a Python script that interacts with the GitHub API to fetch repository details and code scanning analysis information. Make sure the repository exists and your GitHub token has the necessary permissions to access it. + ## Prerequisites - Python 3.6 or higher @@ -37,14 +38,15 @@ Make sure the repository exists and your GitHub token has the necessary permissi 2. Open `ghas-scan.py` in your favorite text editor. 3. Replace `owner_type` variabe value with `user` or `org`. 4. Replace `owner_name` variable value with the corresponding user or org name. -5. Run the script: +5. Set `skip_forks` to `True` if you want to omit forked repos from the results. +6. Run the script: ```bash python3 ghas-scan.py ``` -### Output +### Output and Example -Output is written to `github_data.csv` at the repository root. +Output is written to `github_data.csv` at the repository root. You can see an example in [./example/example_output.csv](./example/example_output.csv). This is just a basic example to give you an idea of the scehma. # References diff --git a/example/example_output.csv b/example/example_output.csv new file mode 100644 index 0000000..aae4764 --- /dev/null +++ b/example/example_output.csv @@ -0,0 +1,17 @@ +repo_name,codeowners,last_commit_date,first_commit_date,is_fork,is_private,is_archived,security_and_analysis_enabled,secret_scanning_enabled,secret_scanning_push_protection_enabled,code_scanners_enabled,code_scanning_critical_alert_count,code_scanning_high_alert_count,code_scanning_medium_alert_count,code_scanning_low_alert_count,code_scanning_warning_alert_count,code_scanning_note_alert_count,code_scanning_error_alert_count,dependabot_enabled,dependabot_open_alerts_count,num_critical_dep_alerts,num_high_dep_alerts,num_medium_dep_alerts,num_low_dep_alerts +bv-ios-sdk,Not found,2015-10-12T19:09:08Z,2015-09-25T18:58:29Z,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +DVWA,Not found,2023-11-08T15:53:38Z,2023-11-08T02:30:31Z,TRUE,FALSE,FALSE,TRUE,TRUE,TRUE,"PHP Security Analysis, Security Audit for Infrastructure, PHP Security Audit, CodeQL",0,0,0,0,0,2,28,FALSE,0,0,0,0,0 +flutter_hello_world,Not found,2022-10-11T02:10:31Z,2022-10-11T02:04:07Z,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +ghas-utils," + +* @austimkelly",2023-11-21T02:11:49Z,2023-11-18T16:48:53Z,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,CodeQL,0,0,0,0,0,0,0,TRUE,0,0,0,0,0 +jQuery-Autocomplete,Not found,2015-06-24T21:23:47Z,2015-06-24T21:15:43Z,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +MKNetworkKit,Not found,2014-05-26T19:38:46Z,2014-05-26T19:13:38Z,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +NodeGoat,Not found,2023-03-18T12:37:32Z,2023-04-12T14:45:28Z,TRUE,FALSE,FALSE,FALSE,TRUE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +pp14parser,Not found,2014-07-14T14:05:07Z,2014-07-11T17:19:22Z,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +pr-practice,Not found,2022-12-02T19:23:47Z,2022-11-11T18:47:06Z,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +rekon,Not found,2023-11-18T17:30:43Z,2023-09-22T15:02:20Z,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,CodeQL,0,0,0,0,0,0,0,TRUE,1,0,0,1,0 +riverdata,Not found,2023-09-22T17:00:25Z,2022-09-20T01:31:34Z,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +riverdata-alrudiny,Not found,2023-11-06T19:03:50Z,2023-09-30T21:08:12Z,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +snyk-automatic-ticketing,Not found,2023-08-11T13:23:59Z,2023-08-10T15:15:54Z,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,None,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 +terragoat,Not found,2021-12-08T14:18:34Z,2021-12-08T14:17:19Z,TRUE,FALSE,FALSE,TRUE,FALSE,FALSE,tfsec,0,0,0,0,0,0,0,FALSE,0,0,0,0,0 \ No newline at end of file diff --git a/ghas-scan.py b/ghas-scan.py index feef941..7cc87bb 100644 --- a/ghas-scan.py +++ b/ghas-scan.py @@ -2,7 +2,7 @@ import requests import os import base64 -import re +import pandas as pd # Set the GitHub owner type, owner name, and personal access token owner_type = 'user' # Change to 'org' if needed @@ -16,6 +16,27 @@ # Set up headers with the access token headers = {'Authorization': f'token {access_token}'} +def print_aggregated_metrics_from_csv(csv_file): + df = pd.read_csv(csv_file) + + total_repos = len(df) + public_repos = len(df[df['is_private'] == False]) + forked_repos = len(df[df['is_fork'] == True]) + repos_with_codeowners = len(df[df['codeowners'].notna() & (df['codeowners'] != 'Not found') & (df['codeowners'] != '')]) + repos_with_secrets_scanning = len(df[df['secret_scanning_enabled'] == True]) + repos_with_secrets_push_protection = len(df[df['secret_scanning_push_protection_enabled'] == True]) + open_critical_high_alerts = df['code_scanning_critical_alert_count'].sum() + df['code_scanning_high_alert_count'].sum() + open_critical_dependabot_alerts = df['num_critical_dep_alerts'].sum() + + print(f"Total repositories: {total_repos}") + print(f"Total public repositories: {public_repos}") + print(f"Percent of repositories that are forked: {forked_repos / total_repos * 100}%") + print(f"Percent of repositories with Codeowners: {repos_with_codeowners / total_repos * 100}%") + print(f"Percent of repositories with Secrets Scanning Enabled: {repos_with_secrets_scanning / total_repos * 100}%") + print(f"Percent of repositories with Secrets Push Protection Enabled: {repos_with_secrets_push_protection / total_repos * 100}%") + print(f"Total number of open critical and high code scanning alerts: {open_critical_high_alerts}") + print(f"Total number of open critical dependabot alerts: {open_critical_dependabot_alerts}") + def get_dependabot_alerts(owner, repo_name, headers): dependabot_url = f'https://api.github.com/repos/{owner}/{repo_name}/dependabot/alerts' dependabot_alerts = requests.get(dependabot_url, headers=headers) @@ -263,6 +284,7 @@ def get_repos(owner, headers, owner_type): raise Exception(f"Failed to fetch repositories. Status code: {response.status_code}, Response: {response.text}") # Get list of repositories for the user or organization +print("Getting list of repositories...") repos = get_repos(owner_name, headers, owner_type) # Write data to CSV @@ -297,6 +319,16 @@ def get_repos(owner, headers, owner_type): writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() + print("Fetching repo security configs... (this may take a while))") for repo in repos: repo_details = get_repo_details(owner_name, repo['name'], headers) writer.writerow(repo_details) + + csvfile.close() + print(f"CSV file '{csv_filename}' written successfully.") + + with open(csv_filename, 'r') as csvfile: + print_aggregated_metrics_from_csv(csvfile) + csvfile.close() + + print("Done.") From 147ce8885b775a00d9b27d9fd1f51858c38dd7c9 Mon Sep 17 00:00:00 2001 From: Tim Kelly Date: Tue, 21 Nov 2023 12:18:48 -0600 Subject: [PATCH 7/8] update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 077c95d..1ef835a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests==2.31.0 \ No newline at end of file +requests==2.31.0 +pandas==1.3.3 \ No newline at end of file From 9c4b1849b429125f27fb5c9276e23f457e8898ac Mon Sep 17 00:00:00 2001 From: Tim Kelly Date: Tue, 21 Nov 2023 12:21:44 -0600 Subject: [PATCH 8/8] add in sample console output --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ba0e59..bc1efea 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,25 @@ Make sure the repository exists and your GitHub token has the necessary permissi ### Output and Example -Output is written to `github_data.csv` at the repository root. You can see an example in [./example/example_output.csv](./example/example_output.csv). This is just a basic example to give you an idea of the scehma. +Output is written to `github_data.csv` at the repository root. It looks something like this: + +``` +Getting list of repositories... +Fetching repo security configs... +CSV file 'github_data.csv' written successfully. +Total repositories: 16 +Total public repositories: 16 +Percent of repositories that are forked: 0.0% +Percent of repositories with Codeowners: 6.25% +Percent of repositories with Secrets Scanning Enabled: 12.5% +Percent of repositories with Secrets Push Protection Enabled: 12.5% +Total number of open critical and high code scanning alerts: 0 +Total number of open critical dependabot alerts: 0 +Done. +``` + + +You can see an example in [./example/example_output.csv](./example/example_output.csv). This is just a basic example to give you an idea of the scehma. # References