From 06ff3db8af0578c5fc6230065c4eec0b4255bc3d Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Thu, 22 May 2025 11:23:42 +0200 Subject: [PATCH] feat(repository): add new check `repository_secret_scanning_enabled` (#7759) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 3 +- ...tory_secret_scanning_enabled.metadata.json | 30 +++++ .../repository_secret_scanning_enabled.py | 38 ++++++ .../services/repository/repository_service.py | 17 +++ ...repository_secret_scanning_enabled_test.py | 112 ++++++++++++++++++ .../repository/repository_service_test.py | 2 + 6 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.metadata.json create mode 100644 prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.py create mode 100644 tests/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 3b3f77db43..1e0a128541 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the **Prowler SDK** are documented in this file. -## [5.8.0] (Prowler v5.8.0) +## [5.8.0] (Prowler v5.8.0) ### Added - Add CIS 1.11 compliance framework for Kubernetes. [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) @@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add a level for Prowler ThreatScore in the accordion in Dashboard. [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739) - Add CIS 4.0 compliance framework for GCP. [(7785)](https://github.com/prowler-cloud/prowler/pull/7785) - Add `repository_has_codeowners_file` check for GitHub provider. [(#7752)](https://github.com/prowler-cloud/prowler/pull/7752) +- Add `repository_secret_scanning_enabled` check for GitHub provider. [(#7759)](https://github.com/prowler-cloud/prowler/pull/7759) - Add `repository_default_branch_requires_codeowners_review` check for GitHub provider. [(#7753)](https://github.com/prowler-cloud/prowler/pull/7753) ### Fixed diff --git a/prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.metadata.json b/prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.metadata.json new file mode 100644 index 0000000000..4f9f07cc26 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "repository_secret_scanning_enabled", + "CheckTitle": "Check if secret scanning is enabled to detect sensitive data in the repository", + "CheckType": [], + "ServiceName": "repository", + "SubServiceName": "", + "ResourceIdTemplate": "github:user-id:repository/repository-name", + "Severity": "high", + "ResourceType": "GitHubRepository", + "Description": "Ensure that scanners are in place to detect and prevent sensitive data, such as confidential ID numbers, passwords, and other sensitive information, from being committed in the source code. This check verifies that secret scanning is enabled to identify and prevent sensitive data from being included in the repository.", + "Risk": "If secret scanning is not enabled, sensitive data may be inadvertently committed to the repository, increasing the risk of data breaches and exploitation by attackers.", + "RelatedUrl": "https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable secret scanning in the repository settings to automatically detect and prevent sensitive data from being committed to the codebase.", + "Url": "https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.py b/prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.py new file mode 100644 index 0000000000..9489ba8653 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled.py @@ -0,0 +1,38 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportGithub +from prowler.providers.github.services.repository.repository_client import ( + repository_client, +) + + +class repository_secret_scanning_enabled(Check): + """Check if secret scanning is enabled to detect sensitive data in the repository + + This class verifies whether each repository has secret scanning enabled. + """ + + def execute(self) -> List[CheckReportGithub]: + """Execute the Github Repository Secret Scanning check + + Iterates over all repositories and checks if secret scanning is enabled. + + Returns: + List[CheckReportGithub]: A list of reports for each repository + """ + findings = [] + for repo in repository_client.repositories.values(): + if repo.secret_scanning_enabled is not None: + report = CheckReportGithub( + metadata=self.metadata(), resource=repo, repository=repo.name + ) + if getattr(repo, "secret_scanning_enabled", None): + report.status = "PASS" + report.status_extended = f"Repository {repo.name} has secret scanning enabled to detect sensitive data." + else: + report.status = "FAIL" + report.status_extended = f"Repository {repo.name} does not have secret scanning enabled to detect sensitive data." + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/repository/repository_service.py b/prowler/providers/github/services/repository/repository_service.py index a3afffccc1..f3a77de04e 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -117,6 +117,21 @@ class Repository(GithubService): f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + secret_scanning_enabled = False + try: + if ( + repo.security_and_analysis + and repo.security_and_analysis.secret_scanning + ): + secret_scanning_enabled = ( + repo.security_and_analysis.secret_scanning.status + == "enabled" + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + secret_scanning_enabled = None repos[repo.id] = Repo( id=repo.id, name=repo.name, @@ -135,6 +150,7 @@ class Repository(GithubService): default_branch_protection=branch_protection, codeowners_exists=codeowners_exists, require_code_owner_reviews=require_code_owner_reviews, + secret_scanning_enabled=secret_scanning_enabled, delete_branch_on_merge=delete_branch_on_merge, ) @@ -164,5 +180,6 @@ class Repo(BaseModel): approval_count: Optional[int] codeowners_exists: Optional[bool] require_code_owner_reviews: Optional[bool] + secret_scanning_enabled: Optional[bool] delete_branch_on_merge: Optional[bool] conversation_resolution: Optional[bool] diff --git a/tests/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled_test.py b/tests/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled_test.py new file mode 100644 index 0000000000..ba935d0a9d --- /dev/null +++ b/tests/providers/github/services/repository/repository_secret_scanning_enabled/repository_secret_scanning_enabled_test.py @@ -0,0 +1,112 @@ +from unittest import mock + +from prowler.providers.github.services.repository.repository_service import Repo +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_repository_secret_scanning_enabled: + def test_no_repositories(self): + repository_client = mock.MagicMock + repository_client.repositories = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_secret_scanning_enabled.repository_secret_scanning_enabled.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_secret_scanning_enabled.repository_secret_scanning_enabled import ( + repository_secret_scanning_enabled, + ) + + check = repository_secret_scanning_enabled() + result = check.execute() + assert len(result) == 0 + + def test_one_repository_no_secret_scanning(self): + repository_client = mock.MagicMock + repo_name = "repo1" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + default_branch="main", + private=False, + securitymd=True, + require_pull_request=False, + approval_count=0, + secret_scanning_enabled=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_secret_scanning_enabled.repository_secret_scanning_enabled.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_secret_scanning_enabled.repository_secret_scanning_enabled import ( + repository_secret_scanning_enabled, + ) + + check = repository_secret_scanning_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == repo_name + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Repository {repo_name} does not have secret scanning enabled to detect sensitive data." + ) + + def test_one_repository_with_secret_scanning(self): + repository_client = mock.MagicMock + repo_name = "repo2" + repository_client.repositories = { + 2: Repo( + id=2, + name=repo_name, + full_name="account-name/repo2", + default_branch="main", + private=False, + securitymd=True, + require_pull_request=False, + approval_count=0, + secret_scanning_enabled=True, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_secret_scanning_enabled.repository_secret_scanning_enabled.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_secret_scanning_enabled.repository_secret_scanning_enabled import ( + repository_secret_scanning_enabled, + ) + + check = repository_secret_scanning_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 2 + assert result[0].resource_name == repo_name + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Repository {repo_name} has secret scanning enabled to detect sensitive data." + ) diff --git a/tests/providers/github/services/repository/repository_service_test.py b/tests/providers/github/services/repository/repository_service_test.py index 82535fad14..1fb8db539e 100644 --- a/tests/providers/github/services/repository/repository_service_test.py +++ b/tests/providers/github/services/repository/repository_service_test.py @@ -25,6 +25,7 @@ def mock_list_repositories(_): approval_count=2, codeowners_exists=True, require_code_owner_reviews=True, + secret_scanning_enabled=True, enforce_admins=True, delete_branch_on_merge=True, conversation_resolution=True, @@ -64,6 +65,7 @@ class Test_Repository_Service: assert repository_service.repositories[1].approval_count == 2 assert repository_service.repositories[1].codeowners_exists is True assert repository_service.repositories[1].require_code_owner_reviews is True + assert repository_service.repositories[1].secret_scanning_enabled is True class Test_Repository_FileExists: