feat(github): scale organization_repository_creation_limited severity by repository visibility (#12164)

Co-authored-by: Lydia Vilchez <lydiavilchezlopez@gmail.com>
This commit is contained in:
Andoni Alonso
2026-08-10 11:51:38 +02:00
committed by GitHub
co-authored by Lydia Vilchez
parent 9daca2e4df
commit 286685a4f3
3 changed files with 451 additions and 5 deletions
@@ -0,0 +1 @@
GitHub `organization_repository_creation_limited` check now reports low severity for FAIL findings when repository creation is provably limited to private/internal visibility, instead of always reporting high
@@ -1,6 +1,6 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGithub
from prowler.lib.check.models import Check, CheckReportGithub, Severity
from prowler.providers.github.services.organization.organization_client import (
organization_client,
)
@@ -15,8 +15,19 @@ def _join_human_readable(items: List[str]) -> str:
return ", ".join(items[:-1]) + f" and {items[-1]}"
PUBLIC_CREATION_TYPES = {"all", "public"}
NON_PUBLIC_CREATION_TYPES = {"private", "internal"}
PUBLIC_DISABLED_CREATION_TYPES = NON_PUBLIC_CREATION_TYPES | {"none"}
KNOWN_CREATION_TYPES = PUBLIC_CREATION_TYPES | PUBLIC_DISABLED_CREATION_TYPES
class organization_repository_creation_limited(Check):
"""Check if repository creation is limited to trusted organization members."""
"""Check if repository creation is limited to trusted organization members.
FAIL severity scales with the visibility members can create: high when public
repository creation is (or may be) allowed, low when it is provably limited to
private/internal repositories.
"""
def execute(self) -> List[CheckReportGithub]:
findings = []
@@ -48,12 +59,19 @@ class organization_repository_creation_limited(Check):
org, "members_allowed_repository_creation_type", None
)
normalized_type = creation_type.lower() if creation_type else ""
type_flags = []
enabled_types = []
if global_creation is not None:
if global_creation:
enabled_types.append("repositories of any type")
public_known_disabled = (
public_creation is False
or normalized_type in PUBLIC_DISABLED_CREATION_TYPES
)
if not public_known_disabled:
enabled_types.append("repositories of any type")
else:
type_flags.append(False)
@@ -70,7 +88,6 @@ class organization_repository_creation_limited(Check):
enabled_types.append(label)
if creation_type:
normalized_type = creation_type.lower()
if normalized_type == "none":
type_flags.append(False)
else:
@@ -97,7 +114,28 @@ class organization_repository_creation_limited(Check):
unique_enabled = list(dict.fromkeys(enabled_types))
allowed_desc = _join_human_readable(unique_enabled)
if allowed_desc:
report.status_extended = f"Organization {org.name} allows members to create {allowed_desc}."
public_allowed = (
public_creation is True
or normalized_type in PUBLIC_CREATION_TYPES
)
non_public_allowed = (
private_creation is True
or internal_creation is True
or normalized_type in NON_PUBLIC_CREATION_TYPES
)
public_known = (
public_creation is not None
or normalized_type in KNOWN_CREATION_TYPES
)
if not public_allowed and non_public_allowed and public_known:
report.check_metadata.Severity = Severity.low
report.status_extended = (
f"Organization {org.name} allows members to create {allowed_desc}. "
"Public repository creation is disabled."
)
else:
report.status_extended = f"Organization {org.name} allows members to create {allowed_desc}."
else:
report.status_extended = f"Organization {org.name} does not have enough data to confirm repository creation restrictions."
@@ -1,5 +1,6 @@
from unittest import mock
from prowler.lib.check.models import Severity
from prowler.providers.github.services.organization.organization_service import Org
from tests.providers.github.github_fixtures import set_mocked_github_provider
@@ -58,6 +59,7 @@ class Test_organization_repository_creation_limited:
assert len(result) == 1
assert result[0].resource_name == org_name
assert result[0].status == "PASS"
assert result[0].check_metadata.Severity == Severity.high
assert (
result[0].status_extended
== f"Organization {org_name} has disabled repository creation for members."
@@ -94,6 +96,7 @@ class Test_organization_repository_creation_limited:
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
assert "public repositories" in result[0].status_extended
assert "repositories of any type" in result[0].status_extended
@@ -129,6 +132,7 @@ class Test_organization_repository_creation_limited:
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].check_metadata.Severity == Severity.high
assert (
result[0].status_extended
== f"Organization {org_name} has disabled repository creation for members."
@@ -164,7 +168,410 @@ class Test_organization_repository_creation_limited:
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].check_metadata.Severity == Severity.high
assert (
result[0].status_extended
== f"Organization {org_name} has disabled repository creation for members."
)
def test_repository_creation_type_none_with_private_enabled(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_repositories=True,
members_allowed_repository_creation_type="none",
members_can_create_private_repositories=True,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.low
assert "repositories of any type" not in result[0].status_extended
assert "private repositories" in result[0].status_extended
assert (
"Public repository creation is disabled." in result[0].status_extended
)
def test_repository_creation_public_flag_only(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_public_repositories=True,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
def test_repository_creation_private_only(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_repositories=True,
members_can_create_public_repositories=False,
members_can_create_private_repositories=True,
members_can_create_internal_repositories=False,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.low
assert (
"Public repository creation is disabled." in result[0].status_extended
)
assert "repositories of any type" not in result[0].status_extended
def test_repository_creation_public_flag_unknown_is_not_downgraded(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_private_repositories=True,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
assert (
"Public repository creation is disabled."
not in result[0].status_extended
)
def test_repository_creation_internal_only(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_public_repositories=False,
members_can_create_private_repositories=False,
members_can_create_internal_repositories=True,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.low
assert (
"Public repository creation is disabled." in result[0].status_extended
)
def test_repository_creation_private_and_internal(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_public_repositories=False,
members_can_create_private_repositories=True,
members_can_create_internal_repositories=True,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.low
assert (
"Public repository creation is disabled." in result[0].status_extended
)
def test_repository_creation_type_all(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_allowed_repository_creation_type="all",
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
def test_repository_creation_type_private(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_allowed_repository_creation_type="private",
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.low
assert (
"Public repository creation is disabled." in result[0].status_extended
)
def test_repository_creation_type_selected_is_undeterminable(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_allowed_repository_creation_type="selected",
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
def test_repository_creation_global_flag_only(self):
organization_client = mock.MagicMock
org_name = "test-organization"
organization_client.organizations = {
1: Org(
id=1,
name=org_name,
mfa_required=None,
members_can_create_repositories=True,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
def test_repository_creation_severity_does_not_leak_between_organizations(self):
organization_client = mock.MagicMock
public_org_name = "public-organization"
private_org_name = "private-organization"
organization_client.organizations = {
1: Org(
id=1,
name=public_org_name,
mfa_required=None,
members_can_create_public_repositories=True,
),
2: Org(
id=2,
name=private_org_name,
mfa_required=None,
members_can_create_public_repositories=False,
members_can_create_private_repositories=True,
members_can_create_internal_repositories=False,
),
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_github_provider(),
),
mock.patch(
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
new=organization_client,
),
):
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
organization_repository_creation_limited,
)
check = organization_repository_creation_limited()
result = check.execute()
assert len(result) == 2
results_by_name = {r.resource_name: r for r in result}
assert results_by_name[public_org_name].status == "FAIL"
assert (
results_by_name[public_org_name].check_metadata.Severity
== Severity.high
)
assert results_by_name[private_org_name].status == "FAIL"
assert (
results_by_name[private_org_name].check_metadata.Severity
== Severity.low
)