Compare commits

...
48 changed files with 706 additions and 192 deletions

No files matched your search

+1 -1
View File
@@ -174,7 +174,7 @@ SENTRY_RELEASE=local
# REO_DEV_CLIENT_ID=
#### Prowler release version ####
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.44.0
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.44.1
# Social login credentials
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
@@ -76,7 +76,7 @@ jobs:
### Changes
This PR updates the `OCI_COMMERCIAL_REGIONS` dictionary in `prowler/providers/oraclecloud/config.py` with the latest regions fetched from the OCI Identity API (`list_regions()`).
This PR updates the `OCI_COMMERCIAL_REGIONS` dictionary in `prowler/providers/oraclecloud/config.py` and the matching list in `ui/lib/provider-credentials/oci-regions.ts` with the latest regions fetched from the OCI Identity API (`list_regions()`).
- Government regions (`OCI_GOVERNMENT_REGIONS`) are preserved unchanged
- DOD regions (`OCI_US_DOD_REGIONS`) are preserved unchanged
+33 -1
View File
@@ -68,7 +68,7 @@ vulnerabilities:
expired_at: 2026-11-30
# Declared in the SPDX manifest that ships inside PowerShell's MicrosoftTeams module
# (Modules/MicrosoftTeams/7.9.0/_manifest/spdx_2.2/manifest.spdx.json). Trivy reads that
# (Modules/MicrosoftTeams/8.0.0/_manifest/spdx_2.2/manifest.spdx.json). Trivy reads that
# SBOM and reports what it declares, which is not the same as what the image contains:
# there is no Node runtime and no node_modules anywhere in the image, and the .NET
# assemblies target net472, a Windows-only framework. Nothing here is reachable, and none
@@ -129,10 +129,42 @@ vulnerabilities:
purls:
- "pkg:npm/fast-uri"
expired_at: 2027-01-31
- id: CVE-2026-84292
purls:
- "pkg:npm/fast-uri"
expired_at: 2027-01-31
- id: CVE-2026-69192
purls:
- "pkg:npm/ip-address"
expired_at: 2027-01-31
- id: CVE-2026-101898
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
- id: CVE-2026-101901
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
- id: CVE-2026-101903
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
- id: CVE-2026-101905
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
- id: CVE-2026-101906
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
- id: CVE-2026-101907
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
- id: CVE-2026-101909
purls:
- "pkg:npm/axios"
expired_at: 2027-01-31
# CVE-2026-84304 is a DoS in grpc-go <= 1.83.0: a peer fragments a gRPC stream into
# millions of tiny HTTP/2 DATA frames until the receiver runs out of heap. Fixed in
@@ -0,0 +1 @@
`django` from 5.1.15 to 5.2.17, patching CVE-2026-15307
@@ -0,0 +1 @@
`dulwich` from 1.2.5 to 1.2.9, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx
@@ -0,0 +1 @@
OCI provider secrets keep the region as home region for credential validation and scans, instead of always using us-ashburn-1
+1
View File
@@ -0,0 +1 @@
`pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9
@@ -0,0 +1 @@
`urllib3` from 2.7.0 to 2.8.0, patching CVE-2026-97687 and CVE-2026-97689
+13 -14
View File
@@ -25,7 +25,7 @@ authors = [{name = "Prowler Engineering", email = "engineering@prowler.com"}]
dependencies = [
"celery (==5.6.2)",
"dj-rest-auth[with_social,jwt] (==7.0.1)",
"django (==5.1.15)",
"django (==5.2.17)",
"django-allauth[saml] (==65.15.0)",
"django-celery-beat (==2.9.0)",
"django-celery-results (==2.6.0)",
@@ -45,7 +45,7 @@ dependencies = [
"gunicorn==26.0.0",
"uvloop==0.22.1",
"lxml==6.1.0",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.44",
"psycopg2-binary==2.9.9",
"pytest-celery[redis] (==1.3.0)",
"sentry-sdk[django] (==2.56.0)",
@@ -71,7 +71,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.45.0"
version = "1.45.1"
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
# target-version tracks this project's lowest supported Python.
@@ -222,7 +222,7 @@ constraint-dependencies = [
"dill==0.4.1",
"distro==1.9.0",
"dj-rest-auth==7.0.1",
"django==5.1.15",
"django==5.2.17",
"django-allauth==65.15.0",
"django-celery-beat==2.9.0",
"django-celery-results==2.6.0",
@@ -246,7 +246,7 @@ constraint-dependencies = [
"drf-simple-apikey==2.2.1",
"drf-spectacular==0.27.2",
"drf-spectacular-jsonapi==0.5.1",
"dulwich==1.2.5",
"dulwich==1.2.9",
"duo-client==5.5.0",
"durationpy==0.10",
"email-validator==2.2.0",
@@ -375,7 +375,7 @@ constraint-dependencies = [
"pydantic-core==2.41.5",
"pygithub==2.8.0",
"pygments==2.20.0",
"pyjwt==2.13.0",
"pyjwt==2.14.0",
"pylint==3.2.5",
"pymsalruntime==0.18.1",
"pynacl==1.6.2",
@@ -439,7 +439,7 @@ constraint-dependencies = [
"tzdata==2025.3",
"tzlocal==5.3.1",
"uritemplate==4.2.0",
"urllib3==2.7.0",
"urllib3==2.8.0",
"uuid6==2024.7.10",
"uvicorn==0.49.0",
"uvloop==0.22.1",
@@ -475,11 +475,10 @@ constraint-dependencies = [
# a constraint cannot satisfy against the SDK's hard pins, so override the whole set
# to 1.9.10 until the SDK bump propagates to the pinned master rev.
#
# prowler@master hard-pins dulwich==0.23.0 and pyjwt==2.12.1 in [project.dependencies].
# dulwich 1.2.5 patches GHSA-897w-fcg9-f6xj (arbitrary file write) and pyjwt 2.13.0
# patches PYSEC-2026-179 (HMAC/JWK key-confusion); a constraint cannot satisfy these
# against the SDK's hard pins, so override them to the patched versions until the SDK
# bump propagates to the pinned master rev. pyjwt keeps the [crypto] extra because an
# The API installs prowler from a pinned master git rev whose dulwich and pyjwt pins trail
# the root pyproject.toml. dulwich 1.2.9 patches GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf,
# GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx, and pyjwt 2.14.0 patches GHSA-ffc3-869f-jxw9,
# so override both until that rev is refreshed. pyjwt keeps the [crypto] extra because an
# override replaces the whole requirement; bare pyjwt would drop it from the consumers
# that request pyjwt[crypto] and leave cryptography (needed for RS256) only transitive.
override-dependencies = [
@@ -499,6 +498,6 @@ override-dependencies = [
"microsoft-kiota-serialization-json==1.9.10",
"microsoft-kiota-serialization-multipart==1.9.10",
"microsoft-kiota-serialization-text==1.9.10",
"dulwich==1.2.5",
"pyjwt[crypto]==2.13.0"
"dulwich==1.2.9",
"pyjwt[crypto]==2.14.0"
]
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.45.0
version: 1.45.1
description: |-
Prowler API specification.
+21 -23
View File
@@ -215,36 +215,34 @@ class TestOracleCloudProviderSecret:
assert serializer.is_valid(), serializer.errors
assert "region" not in serializer.validated_data
def test_accepts_and_ignores_region_field(self):
secret = self.valid_secret(region="us-phoenix-1")
serializer = OracleCloudProviderSecret(data=secret)
assert serializer.is_valid(), serializer.errors
assert "region" not in serializer.validated_data
@pytest.mark.parametrize(
"legacy_field, legacy_value",
[
("region", None),
("region", ""),
("region", {"name": "us-ashburn-1"}),
],
)
def test_accepts_and_ignores_any_legacy_region_value(
self, legacy_field, legacy_value
):
def test_keeps_region_as_home_region(self):
serializer = OracleCloudProviderSecret(
data=self.valid_secret(**{legacy_field: legacy_value})
data=self.valid_secret(region=" me-abudhabi-1 ")
)
assert serializer.is_valid(), serializer.errors
assert serializer.validated_data["region"] == "me-abudhabi-1"
assert legacy_field not in serializer.validated_data
def test_rejects_unknown_region(self):
serializer = OracleCloudProviderSecret(
data=self.valid_secret(region="mars-north-1")
)
assert not serializer.is_valid()
assert "region" in serializer.errors
@pytest.mark.parametrize("legacy_value", [None, "", {"name": "us-ashburn-1"}])
def test_drops_blank_or_non_string_region(self, legacy_value):
serializer = OracleCloudProviderSecret(
data=self.valid_secret(region=legacy_value)
)
assert serializer.is_valid(), serializer.errors
assert "region" not in serializer.validated_data
class TestProviderSecretFieldSchema:
def test_oraclecloud_schema_includes_legacy_region_field(self):
def test_oraclecloud_schema_region_is_not_deprecated(self):
schema = ProviderSecretField._spectacular_annotation["field"]
oraclecloud_schema = next(
credential_schema
@@ -253,7 +251,7 @@ class TestProviderSecretFieldSchema:
== "Oracle Cloud Infrastructure (OCI) API Key Credentials"
)
assert oraclecloud_schema["properties"]["region"]["deprecated"] is True
assert "deprecated" not in oraclecloud_schema["properties"]["region"]
class TestKubernetesProviderSecret:
+34 -8
View File
@@ -172,7 +172,7 @@ class TestInitializeProwlerProvider:
)
@patch("api.utils.return_prowler_provider")
def test_initialize_oraclecloud_provider_removes_region_string(
def test_initialize_oraclecloud_provider_passes_region_as_home_region(
self, mock_return_prowler_provider
):
provider = MagicMock()
@@ -182,7 +182,7 @@ class TestInitializeProwlerProvider:
"fingerprint": "00:11:22:33:44:55:66:77",
"key_content": "fake-base64-key-content",
"tenancy": "ocid1.tenancy.oc1..fake",
"region": "us-ashburn-1",
"region": "me-abudhabi-1",
}
mock_return_prowler_provider.return_value = MagicMock()
@@ -193,6 +193,7 @@ class TestInitializeProwlerProvider:
fingerprint="00:11:22:33:44:55:66:77",
key_content="fake-base64-key-content",
tenancy="ocid1.tenancy.oc1..fake",
home_region="me-abudhabi-1",
)
@patch("api.utils.return_prowler_provider")
@@ -254,11 +255,35 @@ class TestProwlerProviderConnectionTest:
fingerprint="00:11:22:33:44:55:66:77",
key_content="fake-base64-key-content",
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
region=getattr(
OraclecloudProvider,
"_bootstrap_region",
OraclecloudProvider._home_region,
),
region=OraclecloudProvider._bootstrap_region,
provider_id="ocid1.tenancy.oc1..aaaaaaaexample",
raise_on_exception=False,
)
@patch("api.utils.return_prowler_provider")
def test_oraclecloud_connection_test_uses_stored_region_for_identity(
self, mock_return_prowler_provider
):
provider = MagicMock()
provider.uid = "ocid1.tenancy.oc1..aaaaaaaexample"
provider.provider = Provider.ProviderChoices.ORACLECLOUD.value
provider.secret.secret = {
"user": "ocid1.user.oc1..aaaaaaaexample",
"fingerprint": "00:11:22:33:44:55:66:77",
"key_content": "fake-base64-key-content",
"tenancy": "ocid1.tenancy.oc1..aaaaaaaexample",
"region": "me-abudhabi-1",
}
mock_return_prowler_provider.return_value = MagicMock()
prowler_provider_connection_test(provider)
mock_return_prowler_provider.return_value.test_connection.assert_called_once_with(
user="ocid1.user.oc1..aaaaaaaexample",
fingerprint="00:11:22:33:44:55:66:77",
key_content="fake-base64-key-content",
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
region="me-abudhabi-1",
provider_id="ocid1.tenancy.oc1..aaaaaaaexample",
raise_on_exception=False,
)
@@ -434,7 +459,7 @@ class TestGetProwlerProviderKwargs:
expected_result = {**secret_dict, **expected_extra_kwargs}
assert result == expected_result
def test_get_prowler_provider_kwargs_oraclecloud_removes_region(
def test_get_prowler_provider_kwargs_oraclecloud_maps_region_to_home_region(
self,
):
secret_dict = {
@@ -461,6 +486,7 @@ class TestGetProwlerProviderKwargs:
"key_content": "-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----",
"tenancy": "ocid1.tenancy.oc1..fake",
"pass_phrase": "fake-passphrase",
"home_region": "us-ashburn-1",
}
def test_get_prowler_provider_kwargs_with_mutelist(self):
+6 -6
View File
@@ -3363,7 +3363,7 @@ current-context: test-context
provider_secret = ProviderSecret.objects.get()
assert "region" not in provider_secret.secret
def test_provider_secrets_create_oraclecloud_accepts_and_ignores_region(
def test_provider_secrets_create_oraclecloud_stores_region(
self,
authenticated_client,
oraclecloud_provider,
@@ -3372,14 +3372,14 @@ current-context: test-context
authenticated_client,
oraclecloud_provider,
self._oraclecloud_secret(
key_content=" test-key-content ", region=" us-ashburn-1 "
key_content=" test-key-content ", region=" me-abudhabi-1 "
),
)
assert response.status_code == status.HTTP_201_CREATED
provider_secret = ProviderSecret.objects.get()
assert provider_secret.secret["key_content"] == "test-key-content"
assert "region" not in provider_secret.secret
assert provider_secret.secret["region"] == "me-abudhabi-1"
def test_provider_secrets_update_oraclecloud_without_region_stores_no_region(
self,
@@ -3412,7 +3412,7 @@ current-context: test-context
provider_secret.refresh_from_db()
assert "region" not in provider_secret.secret
def test_provider_secrets_update_oraclecloud_accepts_and_ignores_region(
def test_provider_secrets_update_oraclecloud_stores_region(
self,
authenticated_client,
oraclecloud_provider,
@@ -3430,7 +3430,7 @@ current-context: test-context
"type": "provider-secrets",
"id": str(provider_secret.id),
"attributes": {
"secret": self._oraclecloud_secret(region=" us-ashburn-1 ")
"secret": self._oraclecloud_secret(region=" me-abudhabi-1 ")
},
}
}
@@ -3443,7 +3443,7 @@ current-context: test-context
assert response.status_code == status.HTTP_200_OK
provider_secret.refresh_from_db()
assert "region" not in provider_secret.secret
assert provider_secret.secret["region"] == "me-abudhabi-1"
@pytest.mark.parametrize(
"attributes, error_code, error_pointer",
+14 -7
View File
@@ -302,17 +302,26 @@ def get_prowler_provider_kwargs(
def _normalize_oraclecloud_provider_kwargs(secret: dict) -> dict:
"""Normalize external OCI secret fields into SDK provider kwargs."""
prowler_provider_kwargs = secret.copy()
prowler_provider_kwargs.pop("region", None)
home_region = _oraclecloud_home_region(prowler_provider_kwargs.pop("region", None))
if home_region:
prowler_provider_kwargs["home_region"] = home_region
return prowler_provider_kwargs
def _oraclecloud_home_region(region) -> str | None:
"""Return the stored OCI region as a home region, ignoring blank or non-string legacy values."""
if isinstance(region, str) and region.strip():
return region.strip()
return None
def _normalize_oraclecloud_connection_test_kwargs(secret: dict) -> dict:
"""Normalize external OCI secret fields into test_connection kwargs."""
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
prowler_provider_kwargs = secret.copy()
prowler_provider_kwargs.pop("region", None)
home_region = _oraclecloud_home_region(prowler_provider_kwargs.pop("region", None))
if (
prowler_provider_kwargs.get("user")
@@ -323,11 +332,9 @@ def _normalize_oraclecloud_connection_test_kwargs(secret: dict) -> dict:
or prowler_provider_kwargs.get("key_file")
)
):
# Connection validation needs one OCI endpoint, but scans remain unfiltered.
prowler_provider_kwargs["region"] = getattr(
OraclecloudProvider,
"_bootstrap_region",
OraclecloudProvider._home_region,
# Identity calls only succeed in a region the tenancy is subscribed to.
prowler_provider_kwargs["region"] = (
home_region or OraclecloudProvider._bootstrap_region
)
return prowler_provider_kwargs
@@ -301,8 +301,7 @@ from rest_framework_json_api import serializers
},
"region": {
"type": "string",
"deprecated": True,
"description": "Legacy OCI region field accepted for backwards compatibility but ignored; OCI scans all regions.",
"description": "Optional OCI home region (or any region the tenancy is subscribed to) used to validate the credentials. It does not filter the scan, which covers all subscribed regions. Defaults to us-ashburn-1.",
},
},
"required": ["user", "fingerprint", "tenancy"],
+13 -4
View File
@@ -71,6 +71,7 @@ from django.db import IntegrityError, transaction
from drf_spectacular.utils import extend_schema_field
from jwt.exceptions import InvalidKeyError
from prowler.lib.mutelist.mutelist import Mutelist
from prowler.providers.oraclecloud.config import OCI_REGIONS
from rest_framework.reverse import reverse
from rest_framework.validators import UniqueTogetherValidator
from rest_framework_json_api import serializers
@@ -1917,9 +1918,16 @@ class IacProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
class LegacyOCIRegionField(serializers.Field):
class OCIHomeRegionField(serializers.Field):
"""Optional OCI home region; blank or non-string legacy values are dropped."""
def to_internal_value(self, data):
return data
if not isinstance(data, str) or not data.strip():
return None
region = data.strip()
if region not in OCI_REGIONS:
raise serializers.ValidationError(f"Invalid OCI region: {region}")
return region
def to_representation(self, value):
return value
@@ -1932,10 +1940,11 @@ class OracleCloudProviderSecret(serializers.Serializer):
key_content = serializers.CharField(required=False)
tenancy = serializers.CharField()
pass_phrase = serializers.CharField(required=False)
region = LegacyOCIRegionField(required=False, allow_null=True)
region = OCIHomeRegionField(required=False, allow_null=True)
def validate(self, attrs):
attrs.pop("region", None)
if not attrs.get("region"):
attrs.pop("region", None)
if "key_file" not in attrs and "key_content" not in attrs:
raise serializers.ValidationError(
Generated
+47 -35
View File
@@ -138,7 +138,7 @@ constraints = [
{ name = "dill", specifier = "==0.4.1" },
{ name = "distro", specifier = "==1.9.0" },
{ name = "dj-rest-auth", specifier = "==7.0.1" },
{ name = "django", specifier = "==5.1.15" },
{ name = "django", specifier = "==5.2.17" },
{ name = "django-allauth", specifier = "==65.15.0" },
{ name = "django-celery-beat", specifier = "==2.9.0" },
{ name = "django-celery-results", specifier = "==2.6.0" },
@@ -162,7 +162,7 @@ constraints = [
{ name = "drf-simple-apikey", specifier = "==2.2.1" },
{ name = "drf-spectacular", specifier = "==0.27.2" },
{ name = "drf-spectacular-jsonapi", specifier = "==0.5.1" },
{ name = "dulwich", specifier = "==1.2.5" },
{ name = "dulwich", specifier = "==1.2.9" },
{ name = "duo-client", specifier = "==5.5.0" },
{ name = "durationpy", specifier = "==0.10" },
{ name = "email-validator", specifier = "==2.2.0" },
@@ -291,7 +291,7 @@ constraints = [
{ name = "pydantic-core", specifier = "==2.41.5" },
{ name = "pygithub", specifier = "==2.8.0" },
{ name = "pygments", specifier = "==2.20.0" },
{ name = "pyjwt", specifier = "==2.13.0" },
{ name = "pyjwt", specifier = "==2.14.0" },
{ name = "pylint", specifier = "==3.2.5" },
{ name = "pymsalruntime", specifier = "==0.18.1" },
{ name = "pynacl", specifier = "==1.6.2" },
@@ -355,7 +355,7 @@ constraints = [
{ name = "tzdata", specifier = "==2025.3" },
{ name = "tzlocal", specifier = "==5.3.1" },
{ name = "uritemplate", specifier = "==4.2.0" },
{ name = "urllib3", specifier = "==2.7.0" },
{ name = "urllib3", specifier = "==2.8.0" },
{ name = "uuid6", specifier = "==2024.7.10" },
{ name = "uvicorn", specifier = "==0.49.0" },
{ name = "uvloop", specifier = "==0.22.1" },
@@ -378,7 +378,7 @@ constraints = [
overrides = [
{ name = "azure-mgmt-containerservice", specifier = "==34.1.0" },
{ name = "cryptography", specifier = "==50.0.0" },
{ name = "dulwich", specifier = "==1.2.5" },
{ name = "dulwich", specifier = "==1.2.9" },
{ name = "microsoft-kiota-abstractions", specifier = "==1.9.10" },
{ name = "microsoft-kiota-authentication-azure", specifier = "==1.9.10" },
{ name = "microsoft-kiota-http", specifier = "==1.9.10" },
@@ -387,7 +387,7 @@ overrides = [
{ name = "microsoft-kiota-serialization-multipart", specifier = "==1.9.10" },
{ name = "microsoft-kiota-serialization-text", specifier = "==1.9.10" },
{ name = "okta", specifier = "==3.4.2" },
{ name = "pyjwt", extras = ["crypto"], specifier = "==2.13.0" },
{ name = "pyjwt", extras = ["crypto"], specifier = "==2.14.0" },
]
[[package]]
@@ -2329,16 +2329,16 @@ with-social = [
[[package]]
name = "django"
version = "5.1.15"
version = "5.2.17"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/45/1ac68964193cfcc0b0912a0f68025d5bdb54f71ba7b8716e85b959874bd0/django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947", size = 10719662, upload-time = "2025-12-02T14:01:31.931Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d5/d8/43e9d000519adceb189620b6869ff88031e046df91c2e9da72f8f6918399/django-5.2.17.tar.gz", hash = "sha256:9d4d93be539a18ab80d058eb515900e10951e04c537c5a6b394fc49528d3251f", size = 10889740, upload-time = "2026-08-04T15:04:03.173Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/79/372e091f0eba4ddb8228245ccd1baaa140e9658711f5e3a0056e540b4c1e/django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432", size = 8260901, upload-time = "2025-12-02T14:01:27.352Z" },
{ url = "https://files.pythonhosted.org/packages/df/f8/ce120525ca78f12b07daf65786679c5d0b54a75285a8958d3ae55e39da35/django-5.2.17-py3-none-any.whl", hash = "sha256:f04fb3b36ee119e1af4fa1d397d5fd6cf12700f49321e84d4f4c642c5b1973db", size = 8315563, upload-time = "2026-08-04T15:03:59.1Z" },
]
[[package]]
@@ -2667,27 +2667,27 @@ wheels = [
[[package]]
name = "dulwich"
version = "1.2.5"
version = "1.2.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/85/ceb8ecff5cdeee4ceeebb86b599476dee559041dacc6c2c50cc0d4711549/dulwich-1.2.5.tar.gz", hash = "sha256:0395b2c8924c3424bafe2d9c1edd5348cc4b21ce9c1d6655bf01f9a5c47164c8", size = 1253230, upload-time = "2026-05-28T22:27:55.17Z" }
sdist = { url = "https://files.pythonhosted.org/packages/de/17/0a0fa2b998cd9a852b1aa901e3f6145f13a4334f64b91564847e5880968d/dulwich-1.2.9.tar.gz", hash = "sha256:b395466b78eb5c36baf34381b8b831fd26da8a44b428a2eea1d57ba53f68a234", size = 1309797, upload-time = "2026-07-06T20:57:23.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/4a/654ae1671610fdf6b65a64586ad67ddd8550d4d08a632b2a4b9614754b6d/dulwich-1.2.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:556593fd11637f80f6018bee1916b1a84f5b420423b470ebb3f1a782ad6ef081", size = 1399277, upload-time = "2026-05-28T22:27:00.801Z" },
{ url = "https://files.pythonhosted.org/packages/85/d8/06ee3bc8eded4bd7adf8adf0c9ea5f19bf96f7e5e626bfaf7311cde4208a/dulwich-1.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a70477c991e96cfe8fdd7c866e7251faf71b38bfeb51d6f27554c9cce1caabf3", size = 1382310, upload-time = "2026-05-28T22:27:02.216Z" },
{ url = "https://files.pythonhosted.org/packages/07/17/a03adf50b9095f9f5d863393f21d585dea39bdc4fdf60788ff3a9407a512/dulwich-1.2.5-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9008ef25cabd379cda4fa86000fc38ca14b72afe17db798a8c85c0b2b7ce4d1e", size = 1470993, upload-time = "2026-05-28T22:27:04.075Z" },
{ url = "https://files.pythonhosted.org/packages/60/58/1dc352d2a5e80befe4338af7208febb44bcfd7496b0dde5ac6dacb07b031/dulwich-1.2.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a5549f4afc973e0a15ea6b0244d57f848d3f3ee13dac557eb311024aebebf128", size = 1497820, upload-time = "2026-05-28T22:27:05.549Z" },
{ url = "https://files.pythonhosted.org/packages/c1/a8/e058959a87e7df7753b112ef66a43ccbc57338c1bbdc23a0edf3833396df/dulwich-1.2.5-cp311-cp311-win32.whl", hash = "sha256:5108acead814d1de8b6262d6d8fb90af7e82f5a4d83788b6b48e39d01800a92f", size = 1066549, upload-time = "2026-05-28T22:27:06.832Z" },
{ url = "https://files.pythonhosted.org/packages/33/91/ff0b444f686718635348986bd73dfce42e947912417893de35de399b878b/dulwich-1.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:5e067b7feceb7034bc99e7c7143a704f1d97d4be7027d9a0aa5a83c0657ff091", size = 1079481, upload-time = "2026-05-28T22:27:08.33Z" },
{ url = "https://files.pythonhosted.org/packages/19/22/4f75770bbe5521cac61c4820ef46d4fbf8c2175d3519ba3d0378d4ba798e/dulwich-1.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:701a9ecf7a8a44f5e2459e46befa93530cf36a8b1ae3140aefc007db1d7d0207", size = 1396522, upload-time = "2026-05-28T22:27:09.997Z" },
{ url = "https://files.pythonhosted.org/packages/e5/b1/c07c347681c0cf6acd4b189bf6e8d6207c71a1347b7a1e865eb40faa46b9/dulwich-1.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f90d68bfa97c4ca71de7507984365aefe27b6d248cb28dc99644d0f3ae8c60b", size = 1334826, upload-time = "2026-05-28T22:27:11.582Z" },
{ url = "https://files.pythonhosted.org/packages/13/80/6818eb7ce492e18ab2efa92ab901d173b4b0b159e5681c1424f329600c40/dulwich-1.2.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:00b54a1d56ddbacdd8eadd6d4787a51b3a05fefa30eadbf9165fd283a00b90ed", size = 1416616, upload-time = "2026-05-28T22:27:13.195Z" },
{ url = "https://files.pythonhosted.org/packages/14/a7/9790e60d19870f6554f7583722bb324c1355784316f20aeda1c0b5b1491a/dulwich-1.2.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d8f7ea8f47e38e5b0de3fab97e07e9c9161ffddc90b3964512cab2b7749df4e6", size = 1441354, upload-time = "2026-05-28T22:27:14.683Z" },
{ url = "https://files.pythonhosted.org/packages/91/44/0ea8a69c24aa1254ff5996d682eae2eab287d471b937dcdb26d9ea9720b4/dulwich-1.2.5-cp312-cp312-win32.whl", hash = "sha256:8929134acf4ff967203df7600b38535f9b5b590462067a7e30dbce01acb97af9", size = 1017058, upload-time = "2026-05-28T22:27:16.121Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/2fcddda7faec3bae52db7c64bfcb5dc756f597f33fae90e8d4e4b4d3b39b/dulwich-1.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:9693d2c9e226b2ea855c1dc3a87e2f4d972f7523fc0f7924e5997e9f4c23d97f", size = 1031731, upload-time = "2026-05-28T22:27:17.633Z" },
{ url = "https://files.pythonhosted.org/packages/07/4b/4a18a59ad230581cd0ef460e96001f90762e566dc2dfdba22aa358eb5a0e/dulwich-1.2.5-py3-none-any.whl", hash = "sha256:1679b376433a0fc7f36586afda1d4ed7427afa7a79d4bf17e5014474eea69fa4", size = 686745, upload-time = "2026-05-28T22:27:53.695Z" },
{ url = "https://files.pythonhosted.org/packages/95/70/0345eb2b07a9b26337d7e9ee2c0fc634be81de9969d683b72eca04878f72/dulwich-1.2.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b01fd4d8bd4fdd2bd99eb465e101b4fac2b50436e317b971da7ac3a1bd3a83d8", size = 1378231, upload-time = "2026-07-06T20:56:20.301Z" },
{ url = "https://files.pythonhosted.org/packages/56/dd/a699facf7371cb26233ea1f659ff172995cc06a999ba07992d2762a7dc1f/dulwich-1.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e92f76f0048b076ef692094d28645c5e0f65dae1d5e393fbb2e8db2edc017cd3", size = 1371812, upload-time = "2026-07-06T20:56:21.958Z" },
{ url = "https://files.pythonhosted.org/packages/35/02/73baf09f61b66431adb1cd01a9fbe748369a62814b6e27362daaaa924f04/dulwich-1.2.9-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ac47e02988bdcde0c23f98275908390448617d491ad5d9cb9f33cf3eb8ad512b", size = 1493888, upload-time = "2026-07-06T20:56:23.697Z" },
{ url = "https://files.pythonhosted.org/packages/48/e4/aa2dff296c4a9cd769b5a55a8de68622c1605ad39d75b741527e63117acf/dulwich-1.2.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5e6eb07fbd288059da71019f6746655cd1e136db2f1664fa3e1c81bc11973187", size = 1523051, upload-time = "2026-07-06T20:56:25.525Z" },
{ url = "https://files.pythonhosted.org/packages/24/da/73162905ffb2041a8f484e46c7bb269cb39cc984335b1e66eef23d38cb1d/dulwich-1.2.9-cp311-cp311-win32.whl", hash = "sha256:e96f5d29bf509ceb2dbf9cf4de67fe6e5d785b22f721814360563be6757e7473", size = 1043350, upload-time = "2026-07-06T20:56:27.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/e6/8e40755771b5d05c9268771f26cef8f2ba5f7a849bedcee06504d7f842ae/dulwich-1.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:255c4c3605582fe8958afeff94fc1f0883aedc37495c43a74e031b904f3bb86d", size = 1103738, upload-time = "2026-07-06T20:56:29.145Z" },
{ url = "https://files.pythonhosted.org/packages/ab/b0/e4f14c603fa42e07d8a7f1d5a73f123a8148d52b79b3cc478797126dcdd3/dulwich-1.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d5a2896b6dc3f0c794d34b3eb1af317a43b557fefbee0099ef83d35bb6e102f", size = 1372001, upload-time = "2026-07-06T20:56:30.605Z" },
{ url = "https://files.pythonhosted.org/packages/c9/b7/11c40f1c3ce304e7c2e0e759c589430f043a7967689e61ecb0142ad1ebdc/dulwich-1.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:30e72110630e2990d11770227191dfa730b7c045030f818eca5803c6b89bbc5b", size = 1356130, upload-time = "2026-07-06T20:56:32.176Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1a/10d61b784c295a4567615579272d91ede81793c72ead207484c0ed845ddd/dulwich-1.2.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5c636fcb33c4db5495a2dc7686cf6a26cf537060a859982e104dcf67635ea7f7", size = 1437923, upload-time = "2026-07-06T20:56:33.741Z" },
{ url = "https://files.pythonhosted.org/packages/6e/68/3073e2737e2c3f299e736490ec2e8b5864593858b9d08f36fbcbca80a862/dulwich-1.2.9-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1aa1b55f59a046fc8f25a67560473c789b7ec113b85c6a08021aae10c6f78945", size = 1463517, upload-time = "2026-07-06T20:56:35.812Z" },
{ url = "https://files.pythonhosted.org/packages/f2/61/f96c0da72b08bfbc4e38571374c3000dee13e6d72dd06f25281d36d5b0b7/dulwich-1.2.9-cp312-cp312-win32.whl", hash = "sha256:e27a06f9f71275d712b616ad23008df5f2dcf54a06cb8e2a24be8819ee1ce13a", size = 1082071, upload-time = "2026-07-06T20:56:38.139Z" },
{ url = "https://files.pythonhosted.org/packages/38/96/567f32885e780aaa7441f9f5c6f162e7271d149e27d3cb661caf939cd671/dulwich-1.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:81e1005ffc5d5c872bfc87ac3442f722bce27ba10e435543d5302853c34abf56", size = 1053612, upload-time = "2026-07-06T20:56:39.977Z" },
{ url = "https://files.pythonhosted.org/packages/cd/f9/a6677c0e698b5b781d663eb394bd15cc69c4a270b11cdcc128076753d75c/dulwich-1.2.9-py3-none-any.whl", hash = "sha256:79ff0ff35c4e17ccd58f9bfb23f4f8d416b0b09337b8c99ee3b4f10459bb0d68", size = 711459, upload-time = "2026-07-06T20:57:21.516Z" },
]
[[package]]
@@ -3501,6 +3501,17 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/18/7d/721f162c46e3de604a73674223bf6c6bc6cf7ade25b3751a71288f4dd122/huaweicloudsdkrds-3.1.204-py3-none-any.whl", hash = "sha256:a790b5b3c457a608e5679c101f463b4d037dd9a8a66f6e46144a9e5a4b37780f", size = 1626906, upload-time = "2026-07-09T09:04:06.936Z" },
]
[[package]]
name = "huaweicloudsdksmn"
version = "3.1.204"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "huaweicloudsdkcore" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/5d/c0de47d011f1932c9c0ddf669c238d9fad01653d438d38203703526799d7/huaweicloudsdksmn-3.1.204-py3-none-any.whl", hash = "sha256:b0818ea9293e27458c8fa1d2da021c98006cbf9ce01cf17a0f1df598c4da3a6c", size = 323674, upload-time = "2026-07-09T09:04:24.804Z" },
]
[[package]]
name = "huaweicloudsdkvpc"
version = "3.1.204"
@@ -4835,8 +4846,8 @@ wheels = [
[[package]]
name = "prowler"
version = "5.41.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#f05a490cd74a2c0f11a5d66d8ce29d03fa5c64a2" }
version = "5.44.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.44#60b936005cc109c611ad8b7f7c41cadb586fb4b6" }
dependencies = [
{ name = "alibabacloud-actiontrail20200706" },
{ name = "alibabacloud-credentials" },
@@ -4900,6 +4911,7 @@ dependencies = [
{ name = "huaweicloudsdkkms" },
{ name = "huaweicloudsdkobs" },
{ name = "huaweicloudsdkrds" },
{ name = "huaweicloudsdksmn" },
{ name = "huaweicloudsdkvpc" },
{ name = "huaweicloudsdkwaf" },
{ name = "jsonschema" },
@@ -4938,7 +4950,7 @@ dependencies = [
[[package]]
name = "prowler-api"
version = "1.45.0"
version = "1.45.1"
source = { virtual = "." }
dependencies = [
{ name = "cartography" },
@@ -5012,7 +5024,7 @@ requires-dist = [
{ name = "celery", specifier = "==5.6.2" },
{ name = "defusedxml", specifier = "==0.7.1" },
{ name = "dj-rest-auth", extras = ["with-social", "jwt"], specifier = "==7.0.1" },
{ name = "django", specifier = "==5.1.15" },
{ name = "django", specifier = "==5.2.17" },
{ name = "django-allauth", extras = ["saml"], specifier = "==65.15.0" },
{ name = "django-celery-beat", specifier = "==2.9.0" },
{ name = "django-celery-results", specifier = "==2.6.0" },
@@ -5038,7 +5050,7 @@ requires-dist = [
{ name = "matplotlib", specifier = "==3.10.8" },
{ name = "neo4j", specifier = "==6.1.0" },
{ name = "openai", specifier = "==1.109.1" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=master" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=v5.44" },
{ name = "psycopg2-binary", specifier = "==2.9.9" },
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
{ name = "reportlab", specifier = "==4.4.10" },
@@ -5332,11 +5344,11 @@ wheels = [
[[package]]
name = "pyjwt"
version = "2.13.0"
version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
{ url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" },
]
[package.optional-dependencies]
@@ -6332,11 +6344,11 @@ wheels = [
[[package]]
name = "urllib3"
version = "2.7.0"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
{ url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
]
[[package]]
@@ -12,7 +12,7 @@ The following steps apply to Prowler Cloud and Prowler Local Server.
1. Sign in to the [OCI Console](https://cloud.oracle.com/) and open **Tenancy Details** to copy the Tenancy OCID.
2. Go to **Identity & Security** → **Users**, select the principal that owns the API key, and copy the **User OCID**.
3. Generate or locate the API key fingerprint and private key for that user. Follow the [Config File Authentication steps](/user-guide/providers/oci/authentication#config-file-authentication-manual-api-key-setup) to create or rotate the key pair and copy the fingerprint.
4. Note the **Region** identifier to scan (for example, `us-ashburn-1`).
4. In **Tenancy Details**, note the **Home Region** identifier (for example, `me-abudhabi-1`). Any other region the tenancy is subscribed to also works.
### Step 2: Access Prowler Cloud
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
@@ -26,12 +26,18 @@ Prowler Cloud connects to OCI with API key credentials. Provide:
- **User OCID** for the API key owner
- **Fingerprint** of the API key
- **Region** (for example, `us-ashburn-1`)
- **Home Region**: select it from the list (for example, `me-abudhabi-1`)
- **Private Key Content** (paste the full PEM value)
- **Passphrase (Optional)** if the private key is encrypted
Select **Next**, then **Launch Scan** to validate the connection and start the first OCI scan. The private key content is encoded for secure transmission.
<Note>
The home region is used only to validate the credentials and discover the regions the tenancy is subscribed to. It does not limit the scan: Prowler audits every subscribed region. OCI Identity and Access Management (IAM) only answers in subscribed regions, so a tenancy that is not subscribed to the selected region cannot be validated.
</Note>
Providers created without a region keep using `us-ashburn-1` for validation. If such a provider fails with `401 NotAuthenticated`, update its credentials and select the home region.
![Add OCI API Key Credentials](./images/oci-add-api-key-credentials.png)
---
@@ -334,6 +340,11 @@ prowler oci \
#### Region Issues
**Error: "OCI credential validation failed" with `401 NotAuthenticated` on `get_tenancy`**
- The tenancy is not subscribed to the region used for validation (by default `us-ashburn-1`)
- In Prowler Cloud or Prowler Local Server, update the provider credentials and select the tenancy home region
- In Prowler CLI, set `region` in `~/.oci/config` to the home region. All subscribed regions are still scanned; `--region` also works but limits the scan to that region
**Error: "Invalid region"**
- Check available regions: `prowler oci --list-regions`
- Verify your tenancy is subscribed to the region
@@ -0,0 +1 @@
`pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9
+1
View File
@@ -81,5 +81,6 @@ constraint-dependencies = [
"cryptography==50.0.0",
"joserfc==1.6.8",
"mcp==1.28.1",
"pyjwt==2.14.0",
"python-multipart==0.0.30"
]
+6 -5
View File
@@ -13,6 +13,7 @@ constraints = [
{ name = "cryptography", specifier = "==50.0.0" },
{ name = "joserfc", specifier = "==1.6.8" },
{ name = "mcp", specifier = "==1.28.1" },
{ name = "pyjwt", specifier = "==2.14.0" },
{ name = "python-multipart", specifier = "==0.0.30" },
]
@@ -977,11 +978,11 @@ wheels = [
[[package]]
name = "pyjwt"
version = "2.13.0"
version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
{ url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" },
]
[package.optional-dependencies]
@@ -1292,8 +1293,8 @@ name = "secretstorage"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "jeepney" },
{ name = "cryptography", marker = "sys_platform != 'win32'" },
{ name = "jeepney", marker = "sys_platform != 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
wheels = [
@@ -0,0 +1 @@
`dulwich` from 1.2.5 to 1.2.9, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx
@@ -0,0 +1 @@
OCI API key credentials accept a home region to bootstrap identity calls, so tenancies not subscribed to us-ashburn-1 can connect
@@ -0,0 +1 @@
`pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9
@@ -0,0 +1 @@
`urllib3` from 2.7.0 to 2.8.0, patching CVE-2026-97687 and CVE-2026-97689
+1 -1
View File
@@ -52,7 +52,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.44.0"
prowler_version = "5.44.1"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
@@ -89,6 +89,7 @@ class OraclecloudProvider(Provider):
key_content: str = None,
tenancy: str = None,
pass_phrase: str = None,
home_region: str = None,
):
"""
Initializes the OCI provider.
@@ -110,6 +111,7 @@ class OraclecloudProvider(Provider):
- key_content: Content of the private key (base64 encoded).
- tenancy: The OCID of the tenancy.
- pass_phrase: The passphrase for the private key, if encrypted.
- home_region: Region used to bootstrap identity calls with API key credentials; it does not filter the audited regions.
Raises:
- OCISetUpSessionError: If an error occurs during the setup process.
@@ -140,7 +142,7 @@ class OraclecloudProvider(Provider):
)
has_direct_credentials = user and fingerprint and tenancy
bootstrap_region = single_region or (
self._bootstrap_region if has_direct_credentials else None
(home_region or self._bootstrap_region) if has_direct_credentials else None
)
# Setup OCI Session
+4 -4
View File
@@ -76,7 +76,7 @@ dependencies = [
"dash==3.1.1",
"dash-bootstrap-components==2.0.3",
"defusedxml==0.7.1",
"dulwich==1.2.5",
"dulwich==1.2.9",
"google-api-python-client==2.163.0",
"google-auth-httplib2==0.2.0",
"jsonschema==4.23.0",
@@ -144,7 +144,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"}
name = "prowler"
readme = "README.md"
requires-python = ">=3.10,<3.14"
version = "5.44.0"
version = "5.44.1"
[project.scripts]
prowler = "prowler.__main__:prowler"
@@ -349,7 +349,7 @@ constraint-dependencies = [
"pydash==8.0.6",
"pyflakes==3.2.0",
"pygments==2.20.0",
"pyjwt==2.13.0",
"pyjwt==2.14.0",
"pylint==3.3.4",
"pynacl==1.6.2",
"pyopenssl==26.4.0",
@@ -387,7 +387,7 @@ constraint-dependencies = [
"typing-inspection==0.4.2",
"tzdata==2026.2",
"uritemplate==4.2.0",
"urllib3==2.7.0",
"urllib3==2.8.0",
"vulture==2.14",
"websocket-client==1.9.0",
"werkzeug==3.1.8",
@@ -543,6 +543,58 @@ class TestOraclecloudProviderInit:
assert mock_get_regions_to_audit.call_args_list[0].args == (None,)
assert provider.regions == all_subscribed_regions
def test_init_with_home_region_bootstraps_there_without_scan_filter(self):
mock_session = OCISession(
config={"region": "me-abudhabi-1"}, signer=None, profile=None
)
mock_identity = OCIIdentityInfo(
tenancy_id="ocid1.tenancy.oc1..aaaaaaaexample",
tenancy_name="test-tenancy",
user_id="ocid1.user.oc1..aaaaaaaexample",
region="me-abudhabi-1",
profile=None,
audited_regions=set(),
audited_compartments=[],
)
all_subscribed_regions = [
OCIRegion(key="me-abudhabi-1", name="me-abudhabi-1", is_home_region=True),
OCIRegion(key="me-dubai-1", name="me-dubai-1", is_home_region=False),
]
with (
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.setup_session",
return_value=mock_session,
) as mock_setup_session,
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.set_identity",
return_value=mock_identity,
),
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_regions_to_audit",
return_value=all_subscribed_regions,
) as mock_get_regions_to_audit,
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_compartments_to_audit",
return_value=["ocid1.compartment.oc1..aaaaaaaexample"],
),
patch("prowler.providers.common.provider.Provider.set_global_provider"),
):
provider = OraclecloudProvider(
user="ocid1.user.oc1..aaaaaaaexample",
fingerprint="aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
key_content="fake-base64-key-content",
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
home_region="me-abudhabi-1",
config_content={"dummy": True},
mutelist_content={"Accounts": {}},
)
assert mock_setup_session.call_args.kwargs["region"] == "me-abudhabi-1"
assert mock_get_regions_to_audit.call_args_list[0].args == (None,)
assert provider.regions == all_subscribed_regions
assert provider.home_region == "me-abudhabi-1"
def test_init_with_config_file_auth_without_region_uses_session_config_region_for_identity(
self,
):
@@ -0,0 +1,20 @@
import re
from pathlib import Path
from prowler.providers.oraclecloud.config import OCI_REGIONS
UI_REGIONS_FILE = (
Path(__file__).resolve().parents[3]
/ "ui"
/ "lib"
/ "provider-credentials"
/ "oci-regions.ts"
)
def test_ui_home_region_list_matches_sdk_regions():
ui_regions = set(
re.findall(r'"([a-z]{2,3}-[a-z-]+-\d+)"', UI_REGIONS_FILE.read_text())
)
assert ui_regions == set(OCI_REGIONS)
@@ -0,0 +1 @@
Required home region selector in the OCI credentials form, so tenancies not subscribed to us-ashburn-1 can connect
@@ -0,0 +1 @@
First-login redirect to the add-provider wizard is retried on the next load when the navigation was cut short, on Cloud and self-hosted alike, instead of being written off after a single attempt
@@ -1,7 +1,15 @@
import { render, waitFor } from "@testing-library/react";
import { act, render, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { isFirstRunHandled } from "@/lib/onboarding/first-run-marker";
import {
FIRST_RUN_MAX_ATTEMPTS,
isFirstRunHandled,
} from "@/lib/onboarding/first-run-marker";
import {
dispatchProviderFunnel,
PROVIDER_FUNNEL_STEP,
WIZARD_OPEN_SOURCE,
} from "@/lib/provider-funnel/provider-funnel-events";
import { addProviderTour } from "@/lib/tours/add-provider.tour";
import { localStorageAdapter } from "@/lib/tours/store/local-storage-adapter";
@@ -111,7 +119,7 @@ describe("OnboardingGate", () => {
expect(armMock).toHaveBeenCalledOnce();
});
it("happens only once per tenant on this browser", async () => {
it("tries again on the next load when the wizard never opened (the navigation was cut short)", async () => {
// Given
const { unmount } = render(
<OnboardingGate hasProviders={false} tenantId={TENANT_A} />,
@@ -124,28 +132,83 @@ describe("OnboardingGate", () => {
render(<OnboardingGate hasProviders={false} tenantId={TENANT_A} />);
// Then
expect(isFirstRunHandled(TENANT_A)).toBe(true);
expect(replaceMock).not.toHaveBeenCalled();
await waitFor(() =>
expect(replaceMock).toHaveBeenCalledExactlyOnceWith(
CLOUD_FIRST_RUN_HREF,
),
);
expect(isFirstRunHandled(TENANT_A)).toBe(false);
});
it("honours a browser-wide marker written before markers were tenant-scoped", () => {
// Given: e2e storage state and pre-existing browsers set the bare key.
window.localStorage.setItem("prowler.onboarding.first-run", "true");
// When
render(<OnboardingGate hasProviders={false} tenantId={TENANT_A} />);
// Then
expect(replaceMock).not.toHaveBeenCalled();
expect(isFirstRunHandled(TENANT_A)).toBe(true);
});
it("still runs for a different empty tenant on the same browser", async () => {
it("is resolved once the add-provider wizard opens, so later loads leave the user alone", async () => {
// Given
const { unmount } = render(
<OnboardingGate hasProviders={false} tenantId={TENANT_A} />,
);
await waitFor(() => expect(replaceMock).toHaveBeenCalledOnce());
act(() => {
dispatchProviderFunnel({
step: PROVIDER_FUNNEL_STEP.WIZARD_OPENED,
source: WIZARD_OPEN_SOURCE.FIRST_RUN,
});
});
unmount();
replaceMock.mockClear();
// When
render(<OnboardingGate hasProviders={false} tenantId={TENANT_A} />);
// Then
expect(isFirstRunHandled(TENANT_A)).toBe(true);
expect(replaceMock).not.toHaveBeenCalled();
});
it("gives up after a few attempts that never reached the wizard, so no browser is trapped", async () => {
// Given: three loads whose navigation never completed.
for (let attempt = 0; attempt < FIRST_RUN_MAX_ATTEMPTS; attempt++) {
const { unmount } = render(
<OnboardingGate hasProviders={false} tenantId={TENANT_A} />,
);
await waitFor(() => expect(replaceMock).toHaveBeenCalledOnce());
unmount();
replaceMock.mockClear();
}
// When
render(<OnboardingGate hasProviders={false} tenantId={TENANT_A} />);
// Then
expect(isFirstRunHandled(TENANT_A)).toBe(true);
expect(replaceMock).not.toHaveBeenCalled();
});
it.each(["true", "1legacy", "-1"])(
"honours a browser-wide marker holding %s, written before markers counted attempts",
(value) => {
// Given: e2e storage state and pre-existing browsers set the bare key.
window.localStorage.setItem("prowler.onboarding.first-run", value);
// When
render(<OnboardingGate hasProviders={false} tenantId={TENANT_A} />);
// Then
expect(replaceMock).not.toHaveBeenCalled();
expect(isFirstRunHandled(TENANT_A)).toBe(true);
},
);
it("still runs for a different empty tenant on the same browser", async () => {
// Given: tenant A went through its first run on this browser.
const { unmount } = render(
<OnboardingGate hasProviders={false} tenantId={TENANT_A} />,
);
await waitFor(() => expect(replaceMock).toHaveBeenCalledOnce());
act(() => {
dispatchProviderFunnel({
step: PROVIDER_FUNNEL_STEP.WIZARD_OPENED,
source: WIZARD_OPEN_SOURCE.FIRST_RUN,
});
});
unmount();
replaceMock.mockClear();
@@ -154,7 +217,8 @@ describe("OnboardingGate", () => {
// Then
await waitFor(() => expect(replaceMock).toHaveBeenCalledOnce());
expect(isFirstRunHandled(TENANT_B)).toBe(true);
expect(isFirstRunHandled(TENANT_A)).toBe(true);
expect(isFirstRunHandled(TENANT_B)).toBe(false);
});
});
@@ -172,6 +236,24 @@ describe("OnboardingGate", () => {
);
expect(armMock).not.toHaveBeenCalled();
});
it("tries again on the next load when the wizard never opened, with no tenant id available", async () => {
// Given: self-hosted layouts mount the gate without a tenant id.
vi.stubEnv("UI_CLOUD_ENABLED", "false");
const { unmount } = render(<OnboardingGate hasProviders={false} />);
await waitFor(() => expect(replaceMock).toHaveBeenCalledOnce());
unmount();
replaceMock.mockClear();
// When
render(<OnboardingGate hasProviders={false} />);
// Then
await waitFor(() =>
expect(replaceMock).toHaveBeenCalledExactlyOnceWith(OSS_FIRST_RUN_HREF),
);
expect(isFirstRunHandled()).toBe(false);
});
});
describe("when the user cannot add providers", () => {
+26 -7
View File
@@ -12,8 +12,14 @@ import {
import {
isFirstRunHandled,
markFirstRunHandled,
recordFirstRunAttempt,
} from "@/lib/onboarding/first-run-marker";
import { WIZARD_OPEN_SOURCE } from "@/lib/provider-funnel/provider-funnel-events";
import {
PROVIDER_FUNNEL_EVENT,
PROVIDER_FUNNEL_STEP,
type ProviderFunnelDetail,
WIZARD_OPEN_SOURCE,
} from "@/lib/provider-funnel/provider-funnel-events";
import { buildAddProviderHref } from "@/lib/providers-navigation";
import { isCloud } from "@/lib/shared/env";
import { localStorageAdapter } from "@/lib/tours/store/local-storage-adapter";
@@ -28,7 +34,8 @@ interface OnboardingGateProps {
}
// New-tenant gate. Mounted once in the layout: an empty tenant is sent straight to
// the add-provider wizard, once per tenant and browser. Renders nothing.
// the add-provider wizard, retried per load until the wizard opens once for that
// tenant on this browser (bounded attempts). Renders nothing.
export function OnboardingGate({
hasProviders,
tenantId = null,
@@ -77,17 +84,29 @@ function FirstRunRedirect({ flow, tenantId }: FirstRunRedirectProps) {
return;
}
markFirstRunHandled(tenantId);
// The wizard opening resolves the first run, whether this redirect got there
// or the user opened it on their own. Until then each load retries, bounded
// by the attempt count, so a navigation cut short is not the end of it.
const resolveOnWizardOpened = (event: Event) => {
const { detail } = event as CustomEvent<ProviderFunnelDetail>;
if (detail?.step === PROVIDER_FUNNEL_STEP.WIZARD_OPENED) {
markFirstRunHandled(tenantId);
}
};
window.addEventListener(PROVIDER_FUNNEL_EVENT, resolveOnWizardOpened);
recordFirstRunAttempt(tenantId);
const addProviderHref = buildAddProviderHref(WIZARD_OPEN_SOURCE.FIRST_RUN);
if (!isCloud()) {
router.replace(addProviderHref);
return;
} else {
// Tours and the post-connect checkpoint are Cloud-only.
useOnboardingCheckpointStore.getState().arm();
router.replace(`${addProviderHref}&onboarding=${flow.id}`);
}
// Tours and the post-connect checkpoint are Cloud-only.
useOnboardingCheckpointStore.getState().arm();
router.replace(`${addProviderHref}&onboarding=${flow.id}`);
return () =>
window.removeEventListener(PROVIDER_FUNNEL_EVENT, resolveOnWizardOpened);
});
return null;
@@ -4,6 +4,9 @@ import {
WizardInputField,
WizardTextareaField,
} from "@/components/providers/workflow/forms/fields";
import { Combobox } from "@/components/shadcn/combobox";
import { FormControl, FormField, FormMessage } from "@/components/shadcn/form";
import { OCI_REGION_GROUPS } from "@/lib/provider-credentials/oci-regions";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { OCICredentials } from "@/types";
@@ -48,6 +51,34 @@ export const OracleCloudCredentialsForm = ({
variant="bordered"
isRequired
/>
<FormField
control={control}
name={ProviderCredentialFields.OCI_REGION}
render={({ field }) => (
<div className="flex flex-col gap-1.5">
<span className="text-text-neutral-tertiary text-xs font-light tracking-tight">
Home Region<span className="text-text-error-primary">*</span>
</span>
<FormControl>
<Combobox
aria-label="Home Region"
value={field.value ?? ""}
onValueChange={field.onChange}
groups={OCI_REGION_GROUPS}
placeholder="Select your tenancy home region"
searchPlaceholder="Search region..."
emptyMessage="No region found."
contentClassName="z-[60] sm:w-(--radix-popover-trigger-width) sm:max-w-none"
/>
</FormControl>
<span className="text-text-neutral-tertiary text-xs">
Shown in the OCI Console under Tenancy Details. Used only to
validate the credentials: all subscribed regions are scanned.
</span>
<FormMessage className="text-text-error-primary max-w-full text-xs" />
</div>
)}
/>
<WizardTextareaField
control={control}
name={ProviderCredentialFields.OCI_KEY_CONTENT}
+1
View File
@@ -181,6 +181,7 @@ export const useCredentialsForm = ({
[ProviderCredentialFields.OCI_FINGERPRINT]: "",
[ProviderCredentialFields.OCI_KEY_CONTENT]: "",
[ProviderCredentialFields.OCI_TENANCY]: providerUid || "",
[ProviderCredentialFields.OCI_REGION]: "",
[ProviderCredentialFields.OCI_PASS_PHRASE]: "",
};
case "mongodbatlas":
+41 -5
View File
@@ -3,11 +3,20 @@
// written there; without this marker an empty tenant would be redirected on
// every page load.
//
// The first run is resolved when the add-provider wizard actually opens, not
// when the redirect is issued: a navigation cut short (a second login, a tab
// closed mid-flight) must be retried on the next load. Each redirect counts as
// an attempt; after a few attempts that never reached the wizard the marker
// resolves anyway, so a browser can never be trapped in the redirect.
//
// Scoped per tenant, like the other onboarding markers: going through the first
// run in one tenant must not silence it for another one on the same browser.
// The bare key is a browser-wide opt-out: written before markers were scoped,
// by e2e storage state, or when no usable tenant id exists.
const FIRST_RUN_MARKER_KEY = "prowler.onboarding.first-run";
const HANDLED_VALUE = "true";
export const FIRST_RUN_MAX_ATTEMPTS = 3;
// Tenant ids are UUIDs; anything else is refused rather than concatenated
// into a storage key.
@@ -21,23 +30,50 @@ export function firstRunMarkerKey(tenantId?: string | null): string {
return `${FIRST_RUN_MARKER_KEY}.${tenantId.toLowerCase()}`;
}
// A stored value is either an attempt count (digits only) or `HANDLED_VALUE`;
// anything else (a legacy or hand-written marker) is read as resolved.
const ATTEMPT_COUNT_PATTERN = /^\d+$/;
function readAttempts(value: string | null): number | null {
if (value === null) return 0;
return ATTEMPT_COUNT_PATTERN.test(value) ? Number(value) : null;
}
export function isFirstRunHandled(tenantId?: string | null): boolean {
if (typeof window === "undefined") return true;
try {
return (
window.localStorage.getItem(FIRST_RUN_MARKER_KEY) !== null ||
window.localStorage.getItem(firstRunMarkerKey(tenantId)) !== null
// The bare key opts the whole browser out, unless it merely holds the
// attempt count of a deployment that mounts the gate without a tenant id.
const bareAttempts = readAttempts(
window.localStorage.getItem(FIRST_RUN_MARKER_KEY),
);
if (bareAttempts === null) return true;
const attempts = readAttempts(
window.localStorage.getItem(firstRunMarkerKey(tenantId)),
);
return attempts === null || attempts >= FIRST_RUN_MAX_ATTEMPTS;
} catch {
// Unreadable storage must not redirect forever: treat as handled.
return true;
}
}
export function markFirstRunHandled(tenantId?: string | null): void {
export function recordFirstRunAttempt(tenantId?: string | null): void {
if (typeof window === "undefined") return;
try {
window.localStorage.setItem(firstRunMarkerKey(tenantId), "true");
const key = firstRunMarkerKey(tenantId);
const attempts = readAttempts(window.localStorage.getItem(key));
if (attempts === null) return;
window.localStorage.setItem(key, String(attempts + 1));
} catch {
// Non-fatal: a repeated redirect beats a thrown render.
}
}
export function markFirstRunHandled(tenantId?: string | null): void {
if (typeof window === "undefined") return;
try {
window.localStorage.setItem(firstRunMarkerKey(tenantId), HANDLED_VALUE);
} catch {
// Non-fatal: a repeated redirect beats a thrown render.
}
@@ -389,6 +389,10 @@ export const buildOracleCloudSecret = (
[ProviderCredentialFields.OCI_TENANCY]:
providerUid ||
getFormValue(formData, ProviderCredentialFields.OCI_TENANCY),
[ProviderCredentialFields.OCI_REGION]: getFormValue(
formData,
ProviderCredentialFields.OCI_REGION,
),
[ProviderCredentialFields.OCI_PASS_PHRASE]: getFormValue(
formData,
ProviderCredentialFields.OCI_PASS_PHRASE,
@@ -0,0 +1,72 @@
import type { ComboboxGroup } from "@/components/shadcn/combobox";
// Keep in sync with OCI_REGIONS in prowler/providers/oraclecloud/config.py
const OCI_COMMERCIAL_REGIONS = [
"af-casablanca-1",
"af-johannesburg-1",
"ap-batam-1",
"ap-chuncheon-1",
"ap-hyderabad-1",
"ap-kulai-2",
"ap-melbourne-1",
"ap-mumbai-1",
"ap-osaka-1",
"ap-seoul-1",
"ap-singapore-1",
"ap-singapore-2",
"ap-sydney-1",
"ap-tokyo-1",
"ca-montreal-1",
"ca-toronto-1",
"eu-amsterdam-1",
"eu-frankfurt-1",
"eu-madrid-1",
"eu-madrid-3",
"eu-marseille-1",
"eu-milan-1",
"eu-paris-1",
"eu-stockholm-1",
"eu-turin-1",
"eu-zurich-1",
"il-jerusalem-1",
"me-abudhabi-1",
"me-dubai-1",
"me-jeddah-1",
"me-riyadh-1",
"mx-monterrey-1",
"mx-queretaro-1",
"sa-bogota-1",
"sa-santiago-1",
"sa-saopaulo-1",
"sa-valparaiso-1",
"sa-vinhedo-1",
"uk-cardiff-1",
"uk-london-1",
"us-ashburn-1",
"us-chicago-1",
"us-phoenix-1",
"us-sanjose-1",
];
const OCI_GOVERNMENT_REGIONS = [
{ value: "us-langley-1", label: "us-langley-1 (US Gov West)" },
{ value: "us-luke-1", label: "us-luke-1 (US Gov East)" },
{ value: "us-gov-ashburn-1", label: "us-gov-ashburn-1 (US DoD East)" },
{ value: "us-gov-chicago-1", label: "us-gov-chicago-1 (US DoD North)" },
{ value: "us-gov-phoenix-1", label: "us-gov-phoenix-1 (US DoD West)" },
];
export const OCI_REGION_GROUPS: ComboboxGroup[] = [
{
heading: "Commercial",
options: OCI_COMMERCIAL_REGIONS.map((region) => ({
value: region,
label: region,
})),
},
{ heading: "Government", options: OCI_GOVERNMENT_REGIONS },
];
export const OCI_REGION_VALUES = OCI_REGION_GROUPS.flatMap((group) =>
group.options.map((option) => option.value),
);
+13
View File
@@ -224,6 +224,7 @@ export interface OCIProviderCredential {
userId?: string;
fingerprint?: string;
keyContent?: string;
homeRegion?: string;
}
// AlibabaCloud credential options
@@ -365,6 +366,7 @@ export class ProvidersPage extends BasePage {
readonly ociUserIdInput: Locator;
readonly ociFingerprintInput: Locator;
readonly ociKeyContentInput: Locator;
readonly ociHomeRegionCombobox: Locator;
// AlibabaCloud provider form elements
readonly alibabacloudAccountIdInput: Locator;
@@ -510,6 +512,9 @@ export class ProvidersPage extends BasePage {
this.ociKeyContentInput = page.getByRole("textbox", {
name: /Private Key Content/i,
});
this.ociHomeRegionCombobox = page.getByRole("combobox", {
name: /Home Region/i,
});
// AlibabaCloud provider form inputs
this.alibabacloudAccountIdInput = page.getByRole("textbox", {
@@ -1284,6 +1289,12 @@ export class ProvidersPage extends BasePage {
if (credentials.keyContent) {
await this.ociKeyContentInput.fill(credentials.keyContent);
}
if (credentials.homeRegion) {
await this.ociHomeRegionCombobox.click();
await this.page
.locator(`[role="option"][data-value="${credentials.homeRegion}"]`)
.click();
}
}
async verifyOCICredentialsPageLoaded(): Promise<void> {
@@ -1294,6 +1305,7 @@ export class ProvidersPage extends BasePage {
await expect(this.ociUserIdInput).toBeVisible();
await expect(this.ociFingerprintInput).toBeVisible();
await expect(this.ociKeyContentInput).toBeVisible();
await expect(this.ociHomeRegionCombobox).toBeVisible();
}
async verifyOCIUpdateCredentialsPageLoaded(): Promise<void> {
@@ -1304,6 +1316,7 @@ export class ProvidersPage extends BasePage {
await expect(this.ociUserIdInput).toBeVisible();
await expect(this.ociFingerprintInput).toBeVisible();
await expect(this.ociKeyContentInput).toBeVisible();
await expect(this.ociHomeRegionCombobox).toBeVisible();
}
async selectAlibabaCloudProvider(): Promise<void> {
+5 -5
View File
@@ -611,7 +611,7 @@
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION (optional, defaults to us-ashburn-1)
- Remove any existing provider with the same Tenancy ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Tenancy ID not to be already registered beforehand.
@@ -622,7 +622,7 @@
3. Select OCI provider type
4. Fill provider details (tenancy ID and alias)
5. Verify OCI credentials page is loaded
6. Fill OCI credentials (user ID, fingerprint, key content)
6. Fill OCI credentials (user ID, fingerprint, key content, home region)
7. Confirm provider connection without launching a scan
8. Verify return to Providers page
9. Verify provider exists in Providers table
@@ -640,7 +640,7 @@
- Connect account page displays OCI option
- Provider details form accepts tenancy ID and alias
- OCI credentials page loads
- Credentials form accepts all required fields (user ID, fingerprint, key content)
- Credentials form accepts all required fields (user ID, fingerprint, key content, home region)
- Launch step appears
- Successful return to Providers page after closing the launch step
- Provider exists in Providers table (verified by tenancy ID)
@@ -670,7 +670,7 @@
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION (optional, defaults to us-ashburn-1)
- An OCI provider with the specified Tenancy ID must already exist (run PROVIDER-E2E-012 first)
- This test must be run serially and never in parallel with other tests
@@ -682,7 +682,7 @@
4. Click "Update Credentials" option
5. Verify update credentials page is loaded
6. Verify OCI credentials form fields are visible (confirms providerUid is loaded)
7. Fill OCI credentials (user ID, fingerprint, key content)
7. Fill OCI credentials (user ID, fingerprint, key content, home region)
8. Click Next to submit
9. Verify successful navigation to test connection page
+4
View File
@@ -954,6 +954,7 @@ test.describe("Add Provider", () => {
const userId = process.env.E2E_OCI_USER_ID ?? "";
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
const homeRegion = process.env.E2E_OCI_REGION ?? "us-ashburn-1";
// Setup before each test
test.beforeEach(async ({ page }) => {
@@ -995,6 +996,7 @@ test.describe("Add Provider", () => {
userId: userId,
fingerprint: fingerprint,
keyContent: keyContent,
homeRegion: homeRegion,
};
// Navigate to providers page
@@ -1439,6 +1441,7 @@ test.describe("Update Provider Credentials", () => {
const userId = process.env.E2E_OCI_USER_ID ?? "";
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
const homeRegion = process.env.E2E_OCI_REGION ?? "us-ashburn-1";
// Setup before each test
test.beforeEach(async ({ page }) => {
@@ -1465,6 +1468,7 @@ test.describe("Update Provider Credentials", () => {
userId: userId,
fingerprint: fingerprint,
keyContent: keyContent,
homeRegion: homeRegion,
};
// Navigate to providers page
+1
View File
@@ -281,6 +281,7 @@ export type OCICredentials = {
[ProviderCredentialFields.OCI_FINGERPRINT]: string;
[ProviderCredentialFields.OCI_KEY_CONTENT]: string;
[ProviderCredentialFields.OCI_TENANCY]: string;
[ProviderCredentialFields.OCI_REGION]: string;
[ProviderCredentialFields.OCI_PASS_PHRASE]?: string;
[ProviderCredentialFields.PROVIDER_ID]: string;
};
+1
View File
@@ -151,6 +151,7 @@ declare global {
E2E_OCI_USER_ID?: string;
E2E_OCI_FINGERPRINT?: string;
E2E_OCI_KEY_CONTENT?: string;
E2E_OCI_REGION?: string;
// E2E Alibaba Cloud
E2E_ALIBABACLOUD_ACCOUNT_ID?: string;
+23 -1
View File
@@ -307,11 +307,33 @@ describe("addCredentialsFormSchema - oraclecloud", () => {
[ProviderCredentialFields.OCI_TENANCY]: "ocid1.tenancy.oc1..example",
} as const;
it("accepts OCI API key credentials without region", () => {
it("rejects OCI API key credentials without a home region", () => {
const schema = addCredentialsFormSchema("oraclecloud");
const result = schema.safeParse(BASE_OCI_VALUES);
expect(result.success).toBe(false);
});
it("rejects an unknown OCI home region", () => {
const schema = addCredentialsFormSchema("oraclecloud");
const result = schema.safeParse({
...BASE_OCI_VALUES,
[ProviderCredentialFields.OCI_REGION]: "mars-north-1",
});
expect(result.success).toBe(false);
});
it("accepts OCI API key credentials with a home region", () => {
const schema = addCredentialsFormSchema("oraclecloud");
const result = schema.safeParse({
...BASE_OCI_VALUES,
[ProviderCredentialFields.OCI_REGION]: "me-abudhabi-1",
});
expect(result.success).toBe(true);
});
});
+9
View File
@@ -1,6 +1,7 @@
import yaml from "js-yaml";
import { z } from "zod";
import { OCI_REGION_VALUES } from "@/lib/provider-credentials/oci-regions";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
import { MAX_SAML_ADDITIONAL_EMAIL_DOMAINS } from "@/types/saml";
@@ -325,6 +326,14 @@ export const addCredentialsFormSchema = (
[ProviderCredentialFields.OCI_TENANCY]: z
.string()
.min(1, "Tenancy OCID is required"),
[ProviderCredentialFields.OCI_REGION]: z
.string()
.refine(
(region) => OCI_REGION_VALUES.includes(region),
{
error: "Home region is required",
},
),
[ProviderCredentialFields.OCI_PASS_PHRASE]: z
.union([z.string(), z.literal("")])
.optional(),
+38
View File
@@ -178,6 +178,34 @@ def update_config_file(regions, config_file_path):
logging.info(f"Updated OCI_COMMERCIAL_REGIONS with {len(regions)} regions")
def update_ui_regions_file(regions, ui_file_path):
"""Rewrite OCI_COMMERCIAL_REGIONS in the UI region list used by the credentials form."""
logging.info(f"Updating UI regions file: {ui_file_path}")
with open(ui_file_path, "r") as f:
ui_content = f.read()
new_regions_array = "const OCI_COMMERCIAL_REGIONS = [\n"
for region_id in regions.keys():
new_regions_array += f' "{region_id}",\n'
new_regions_array += "];"
pattern = r"const OCI_COMMERCIAL_REGIONS = \[[^\]]*\];"
if not re.search(pattern, ui_content):
raise Exception(
"Validation failed: OCI_COMMERCIAL_REGIONS not found in the UI regions file."
)
updated_content = re.sub(pattern, new_regions_array, ui_content)
if updated_content == ui_content:
logging.warning("No changes detected in UI regions file")
return
with open(ui_file_path, "w") as f:
f.write(updated_content)
logging.info("Successfully updated UI regions file")
def main():
"""
Main execution function for OCI regions updater.
@@ -201,6 +229,16 @@ def main():
update_config_file(commercial_regions, config_file_path)
ui_file_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"..",
"ui",
"lib",
"provider-credentials",
"oci-regions.ts",
)
update_ui_regions_file(commercial_regions, ui_file_path)
logging.info("OCI regions update completed successfully")
return 0
Generated
+39 -39
View File
@@ -181,7 +181,7 @@ constraints = [
{ name = "pydash", specifier = "==8.0.6" },
{ name = "pyflakes", specifier = "==3.2.0" },
{ name = "pygments", specifier = "==2.20.0" },
{ name = "pyjwt", specifier = "==2.13.0" },
{ name = "pyjwt", specifier = "==2.14.0" },
{ name = "pylint", specifier = "==3.3.4" },
{ name = "pynacl", specifier = "==1.6.2" },
{ name = "pyopenssl", specifier = "==26.4.0" },
@@ -219,7 +219,7 @@ constraints = [
{ name = "typing-inspection", specifier = "==0.4.2" },
{ name = "tzdata", specifier = "==2026.2" },
{ name = "uritemplate", specifier = "==4.2.0" },
{ name = "urllib3", specifier = "==2.7.0" },
{ name = "urllib3", specifier = "==2.8.0" },
{ name = "vulture", specifier = "==2.14" },
{ name = "websocket-client", specifier = "==1.9.0" },
{ name = "werkzeug", specifier = "==3.1.8" },
@@ -1922,41 +1922,41 @@ wheels = [
[[package]]
name = "dulwich"
version = "1.2.5"
version = "1.2.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/85/ceb8ecff5cdeee4ceeebb86b599476dee559041dacc6c2c50cc0d4711549/dulwich-1.2.5.tar.gz", hash = "sha256:0395b2c8924c3424bafe2d9c1edd5348cc4b21ce9c1d6655bf01f9a5c47164c8", size = 1253230, upload-time = "2026-05-28T22:27:55.17Z" }
sdist = { url = "https://files.pythonhosted.org/packages/de/17/0a0fa2b998cd9a852b1aa901e3f6145f13a4334f64b91564847e5880968d/dulwich-1.2.9.tar.gz", hash = "sha256:b395466b78eb5c36baf34381b8b831fd26da8a44b428a2eea1d57ba53f68a234", size = 1309797, upload-time = "2026-07-06T20:57:23.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/af/60929b502d6541cb015beb9f1da82600aac64d5f705d0188aaf44a7aa77f/dulwich-1.2.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:46db47394ba8a95748ae739f5d3a5a3e1724a2f857bf2437bc71bfc0baaed91d", size = 1400236, upload-time = "2026-05-28T22:26:50.998Z" },
{ url = "https://files.pythonhosted.org/packages/04/f8/25de359a9249cc05a58c2500babfe2adff174931f2fa3fe97c700ca16626/dulwich-1.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66aded7d364341b55941973a1562323f25bd205f0809692b687ec36ccd31242c", size = 1382996, upload-time = "2026-05-28T22:26:53.545Z" },
{ url = "https://files.pythonhosted.org/packages/04/f7/640fee144007262096173f5fafd04cc7e5a0d72b0ceeeb9c9a51d99abc43/dulwich-1.2.5-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:dd9569bc26174a3437d749114d36c81fc6c7478b55370ae50125e34e9629e4fe", size = 1471811, upload-time = "2026-05-28T22:26:55.044Z" },
{ url = "https://files.pythonhosted.org/packages/51/2a/348c1f0baa0c42dc79a9f503463ddb00452c234ec5c9e20b43530d78528e/dulwich-1.2.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:827366331603150de5976d72dd456a3fd5fc91e856471dc1d10fd64758c05f02", size = 1498006, upload-time = "2026-05-28T22:26:56.531Z" },
{ url = "https://files.pythonhosted.org/packages/d4/f8/95f51bcbc2ce86beea49a67f61228be86bf614a24aa714a8c59e0abdd153/dulwich-1.2.5-cp310-cp310-win32.whl", hash = "sha256:6c683c0f4a062894b6826c61102d415dae86ade61a10003c82ccc2b91858d5fb", size = 1066964, upload-time = "2026-05-28T22:26:58.049Z" },
{ url = "https://files.pythonhosted.org/packages/68/c1/ffa02a1623c3d668de8e66b654187bb8dc24c085224644e5554537ee4642/dulwich-1.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:a6620963196c49212c511cd909f367dacf771f199a27d116f357cc671ea956c7", size = 1079537, upload-time = "2026-05-28T22:26:59.319Z" },
{ url = "https://files.pythonhosted.org/packages/4a/4a/654ae1671610fdf6b65a64586ad67ddd8550d4d08a632b2a4b9614754b6d/dulwich-1.2.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:556593fd11637f80f6018bee1916b1a84f5b420423b470ebb3f1a782ad6ef081", size = 1399277, upload-time = "2026-05-28T22:27:00.801Z" },
{ url = "https://files.pythonhosted.org/packages/85/d8/06ee3bc8eded4bd7adf8adf0c9ea5f19bf96f7e5e626bfaf7311cde4208a/dulwich-1.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a70477c991e96cfe8fdd7c866e7251faf71b38bfeb51d6f27554c9cce1caabf3", size = 1382310, upload-time = "2026-05-28T22:27:02.216Z" },
{ url = "https://files.pythonhosted.org/packages/07/17/a03adf50b9095f9f5d863393f21d585dea39bdc4fdf60788ff3a9407a512/dulwich-1.2.5-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9008ef25cabd379cda4fa86000fc38ca14b72afe17db798a8c85c0b2b7ce4d1e", size = 1470993, upload-time = "2026-05-28T22:27:04.075Z" },
{ url = "https://files.pythonhosted.org/packages/60/58/1dc352d2a5e80befe4338af7208febb44bcfd7496b0dde5ac6dacb07b031/dulwich-1.2.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a5549f4afc973e0a15ea6b0244d57f848d3f3ee13dac557eb311024aebebf128", size = 1497820, upload-time = "2026-05-28T22:27:05.549Z" },
{ url = "https://files.pythonhosted.org/packages/c1/a8/e058959a87e7df7753b112ef66a43ccbc57338c1bbdc23a0edf3833396df/dulwich-1.2.5-cp311-cp311-win32.whl", hash = "sha256:5108acead814d1de8b6262d6d8fb90af7e82f5a4d83788b6b48e39d01800a92f", size = 1066549, upload-time = "2026-05-28T22:27:06.832Z" },
{ url = "https://files.pythonhosted.org/packages/33/91/ff0b444f686718635348986bd73dfce42e947912417893de35de399b878b/dulwich-1.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:5e067b7feceb7034bc99e7c7143a704f1d97d4be7027d9a0aa5a83c0657ff091", size = 1079481, upload-time = "2026-05-28T22:27:08.33Z" },
{ url = "https://files.pythonhosted.org/packages/19/22/4f75770bbe5521cac61c4820ef46d4fbf8c2175d3519ba3d0378d4ba798e/dulwich-1.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:701a9ecf7a8a44f5e2459e46befa93530cf36a8b1ae3140aefc007db1d7d0207", size = 1396522, upload-time = "2026-05-28T22:27:09.997Z" },
{ url = "https://files.pythonhosted.org/packages/e5/b1/c07c347681c0cf6acd4b189bf6e8d6207c71a1347b7a1e865eb40faa46b9/dulwich-1.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f90d68bfa97c4ca71de7507984365aefe27b6d248cb28dc99644d0f3ae8c60b", size = 1334826, upload-time = "2026-05-28T22:27:11.582Z" },
{ url = "https://files.pythonhosted.org/packages/13/80/6818eb7ce492e18ab2efa92ab901d173b4b0b159e5681c1424f329600c40/dulwich-1.2.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:00b54a1d56ddbacdd8eadd6d4787a51b3a05fefa30eadbf9165fd283a00b90ed", size = 1416616, upload-time = "2026-05-28T22:27:13.195Z" },
{ url = "https://files.pythonhosted.org/packages/14/a7/9790e60d19870f6554f7583722bb324c1355784316f20aeda1c0b5b1491a/dulwich-1.2.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d8f7ea8f47e38e5b0de3fab97e07e9c9161ffddc90b3964512cab2b7749df4e6", size = 1441354, upload-time = "2026-05-28T22:27:14.683Z" },
{ url = "https://files.pythonhosted.org/packages/91/44/0ea8a69c24aa1254ff5996d682eae2eab287d471b937dcdb26d9ea9720b4/dulwich-1.2.5-cp312-cp312-win32.whl", hash = "sha256:8929134acf4ff967203df7600b38535f9b5b590462067a7e30dbce01acb97af9", size = 1017058, upload-time = "2026-05-28T22:27:16.121Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/2fcddda7faec3bae52db7c64bfcb5dc756f597f33fae90e8d4e4b4d3b39b/dulwich-1.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:9693d2c9e226b2ea855c1dc3a87e2f4d972f7523fc0f7924e5997e9f4c23d97f", size = 1031731, upload-time = "2026-05-28T22:27:17.633Z" },
{ url = "https://files.pythonhosted.org/packages/f5/33/8bd311edfb312e907b4ec7ad4680922e3f5baeccd18d987aeb57141a685d/dulwich-1.2.5-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2f10dafa1ef5660b1331364bc8d68446448608a8d8f493ed0e260eaf5133e71c", size = 1530974, upload-time = "2026-05-28T22:27:19.301Z" },
{ url = "https://files.pythonhosted.org/packages/f5/0d/e4704188a78efbe785d6dc921b70a4f896dbbd4e37caed3a4122be31e871/dulwich-1.2.5-cp313-cp313-android_21_x86_64.whl", hash = "sha256:9b6d234f1f91335e9f01d9daac42ddc2d2e5c2fdbe285d8eeef50353b283648a", size = 1524254, upload-time = "2026-05-28T22:27:20.993Z" },
{ url = "https://files.pythonhosted.org/packages/62/95/feb632693a629314593972f9393bb0a614aee5301ebd0848cb1c7fbfa56c/dulwich-1.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c65230abaa52c72093b70d3b499d5689d1d8f9627e88ad3c3b4f8154e86ac0c8", size = 1394846, upload-time = "2026-05-28T22:27:22.475Z" },
{ url = "https://files.pythonhosted.org/packages/1b/4c/f0b4ae4cd2a34bf21ac37944d75c68d2297ebc0128928f988767d1542ef0/dulwich-1.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e128cddeccae4146b556684a0d5426454fff5bfe7306862e5a8ce6b471568af4", size = 1333523, upload-time = "2026-05-28T22:27:23.966Z" },
{ url = "https://files.pythonhosted.org/packages/0f/bf/b834306cd6a5959b1521d7f4d3e7473d39cdaea43063d97fa1eed6f1ba28/dulwich-1.2.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:07cb75b58216440e2c170fff4f3d55a5f387358d9489863af8cb11f24ee37121", size = 1415849, upload-time = "2026-05-28T22:27:25.329Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/8a6110eb9314fea1bf79c13ab1a5ab699167f1401284cee4bebbf3db2e90/dulwich-1.2.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:df4ac3746099562c8160d78d55bb2fa10c9ada7ef970af3e2536bd133cb7830e", size = 1441075, upload-time = "2026-05-28T22:27:26.723Z" },
{ url = "https://files.pythonhosted.org/packages/00/75/e993287749f7fa9ff871a0a4fbbede81727f982b1658ba50e7273a100fd3/dulwich-1.2.5-cp313-cp313-win32.whl", hash = "sha256:9fc113c1348c7eb22c4e8790f68b562bb4f42a721fafb813e89a57e9cd632040", size = 1016835, upload-time = "2026-05-28T22:27:28.544Z" },
{ url = "https://files.pythonhosted.org/packages/50/bc/17cc36a67696e06e040e8035fcfdc2583e3834f736b7fe808d3ce01531d0/dulwich-1.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:1ba83ec3cfb4c506c277400357a51523c8258fa07b841ee06e8e1071da4cfed1", size = 1031444, upload-time = "2026-05-28T22:27:30.204Z" },
{ url = "https://files.pythonhosted.org/packages/07/4b/4a18a59ad230581cd0ef460e96001f90762e566dc2dfdba22aa358eb5a0e/dulwich-1.2.5-py3-none-any.whl", hash = "sha256:1679b376433a0fc7f36586afda1d4ed7427afa7a79d4bf17e5014474eea69fa4", size = 686745, upload-time = "2026-05-28T22:27:53.695Z" },
{ url = "https://files.pythonhosted.org/packages/4a/d8/5144092504b20df4e4baf1006f099f7f53ca57353ccbf5e24f3da13db25d/dulwich-1.2.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:90c79e0fe54f63f6b56aacc9b5fdfd20771810607a1dc18fa2a78754bf6abade", size = 1379387, upload-time = "2026-07-06T20:56:10.005Z" },
{ url = "https://files.pythonhosted.org/packages/14/02/cc814bbae0c9763317fff593597b6d860275777f72f54d791c8d99c18257/dulwich-1.2.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9dcae6b21ca4ddafe30f79caf3fdb3137214886cc09e6bf1e67e9f5bb61e8206", size = 1373369, upload-time = "2026-07-06T20:56:11.854Z" },
{ url = "https://files.pythonhosted.org/packages/61/a8/6db716095113107b2c72b9a8a95480c71206d87ee994345c06ffefffe14a/dulwich-1.2.9-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2c8760ad8cd09d26cc2da891bcb65e3ffc3b4dc4ca58d4377d9cd0712cb9dd37", size = 1452849, upload-time = "2026-07-06T20:56:13.451Z" },
{ url = "https://files.pythonhosted.org/packages/de/b1/a9c720ede886341f8b037951a5b9dac4387f8bceaad7f76d15e9e28f1b30/dulwich-1.2.9-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a94c107eb1b8e397dd6440248f939e31756c4738eaa038d3416b52bad8ece94c", size = 1476940, upload-time = "2026-07-06T20:56:15.134Z" },
{ url = "https://files.pythonhosted.org/packages/7f/c8/6738a67a5cb112880834b8d785c24555295dfa06d8fa5ab29d7bf9c12b98/dulwich-1.2.9-cp310-cp310-win32.whl", hash = "sha256:27dc86b999f25f314205e723a51860851563b7ec144ed852acdaf9c2e6c4a714", size = 1043723, upload-time = "2026-07-06T20:56:16.673Z" },
{ url = "https://files.pythonhosted.org/packages/d3/40/3d6e4960daae19145f7e90778b017280e4b8683071077f68ee8bbe54ec2d/dulwich-1.2.9-cp310-cp310-win_amd64.whl", hash = "sha256:5a2923f2cc4abc4ced2769a54cdec51be949717208ab9a31fe2565fe27f09bbb", size = 1061250, upload-time = "2026-07-06T20:56:18.576Z" },
{ url = "https://files.pythonhosted.org/packages/95/70/0345eb2b07a9b26337d7e9ee2c0fc634be81de9969d683b72eca04878f72/dulwich-1.2.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b01fd4d8bd4fdd2bd99eb465e101b4fac2b50436e317b971da7ac3a1bd3a83d8", size = 1378231, upload-time = "2026-07-06T20:56:20.301Z" },
{ url = "https://files.pythonhosted.org/packages/56/dd/a699facf7371cb26233ea1f659ff172995cc06a999ba07992d2762a7dc1f/dulwich-1.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e92f76f0048b076ef692094d28645c5e0f65dae1d5e393fbb2e8db2edc017cd3", size = 1371812, upload-time = "2026-07-06T20:56:21.958Z" },
{ url = "https://files.pythonhosted.org/packages/35/02/73baf09f61b66431adb1cd01a9fbe748369a62814b6e27362daaaa924f04/dulwich-1.2.9-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ac47e02988bdcde0c23f98275908390448617d491ad5d9cb9f33cf3eb8ad512b", size = 1493888, upload-time = "2026-07-06T20:56:23.697Z" },
{ url = "https://files.pythonhosted.org/packages/48/e4/aa2dff296c4a9cd769b5a55a8de68622c1605ad39d75b741527e63117acf/dulwich-1.2.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5e6eb07fbd288059da71019f6746655cd1e136db2f1664fa3e1c81bc11973187", size = 1523051, upload-time = "2026-07-06T20:56:25.525Z" },
{ url = "https://files.pythonhosted.org/packages/24/da/73162905ffb2041a8f484e46c7bb269cb39cc984335b1e66eef23d38cb1d/dulwich-1.2.9-cp311-cp311-win32.whl", hash = "sha256:e96f5d29bf509ceb2dbf9cf4de67fe6e5d785b22f721814360563be6757e7473", size = 1043350, upload-time = "2026-07-06T20:56:27.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/e6/8e40755771b5d05c9268771f26cef8f2ba5f7a849bedcee06504d7f842ae/dulwich-1.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:255c4c3605582fe8958afeff94fc1f0883aedc37495c43a74e031b904f3bb86d", size = 1103738, upload-time = "2026-07-06T20:56:29.145Z" },
{ url = "https://files.pythonhosted.org/packages/ab/b0/e4f14c603fa42e07d8a7f1d5a73f123a8148d52b79b3cc478797126dcdd3/dulwich-1.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d5a2896b6dc3f0c794d34b3eb1af317a43b557fefbee0099ef83d35bb6e102f", size = 1372001, upload-time = "2026-07-06T20:56:30.605Z" },
{ url = "https://files.pythonhosted.org/packages/c9/b7/11c40f1c3ce304e7c2e0e759c589430f043a7967689e61ecb0142ad1ebdc/dulwich-1.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:30e72110630e2990d11770227191dfa730b7c045030f818eca5803c6b89bbc5b", size = 1356130, upload-time = "2026-07-06T20:56:32.176Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1a/10d61b784c295a4567615579272d91ede81793c72ead207484c0ed845ddd/dulwich-1.2.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5c636fcb33c4db5495a2dc7686cf6a26cf537060a859982e104dcf67635ea7f7", size = 1437923, upload-time = "2026-07-06T20:56:33.741Z" },
{ url = "https://files.pythonhosted.org/packages/6e/68/3073e2737e2c3f299e736490ec2e8b5864593858b9d08f36fbcbca80a862/dulwich-1.2.9-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1aa1b55f59a046fc8f25a67560473c789b7ec113b85c6a08021aae10c6f78945", size = 1463517, upload-time = "2026-07-06T20:56:35.812Z" },
{ url = "https://files.pythonhosted.org/packages/f2/61/f96c0da72b08bfbc4e38571374c3000dee13e6d72dd06f25281d36d5b0b7/dulwich-1.2.9-cp312-cp312-win32.whl", hash = "sha256:e27a06f9f71275d712b616ad23008df5f2dcf54a06cb8e2a24be8819ee1ce13a", size = 1082071, upload-time = "2026-07-06T20:56:38.139Z" },
{ url = "https://files.pythonhosted.org/packages/38/96/567f32885e780aaa7441f9f5c6f162e7271d149e27d3cb661caf939cd671/dulwich-1.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:81e1005ffc5d5c872bfc87ac3442f722bce27ba10e435543d5302853c34abf56", size = 1053612, upload-time = "2026-07-06T20:56:39.977Z" },
{ url = "https://files.pythonhosted.org/packages/55/dc/86419d8e1f740a4dc29e32f529bcc32848bd726e34d71481c8e04764f8cf/dulwich-1.2.9-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:598cfe12262f97e13a7caf7793afd76e909ea9b1f228ff2b0d0af9cd3992829b", size = 1510306, upload-time = "2026-07-06T20:56:41.863Z" },
{ url = "https://files.pythonhosted.org/packages/ea/47/43d1730d42e3f11b9f1561084101e7cdebb40e3a87879f558370647be052/dulwich-1.2.9-cp313-cp313-android_24_x86_64.whl", hash = "sha256:c68e1302c77baa6db49dde60b2878b4f56728316e9f1bd5adc28d8a22058d1ed", size = 1499940, upload-time = "2026-07-06T20:56:43.71Z" },
{ url = "https://files.pythonhosted.org/packages/67/83/1934132defd24dc8cc38c01e5ca5bb271a9f6f8e109b1ca80e6b80e36998/dulwich-1.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d154fc8cb21d393f064057c2965f481464b8d82977c4dff335ce89fb60cd46e7", size = 1370890, upload-time = "2026-07-06T20:56:45.261Z" },
{ url = "https://files.pythonhosted.org/packages/18/1e/da9e332726570f5cdaa9f1227f516fb4eff1b0e4b9121d658239c794d5eb/dulwich-1.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:300573ce5075641d18562da5343f53352c5672465ccd7974ef88d316bf043f84", size = 1355428, upload-time = "2026-07-06T20:56:47.126Z" },
{ url = "https://files.pythonhosted.org/packages/11/d6/375b8e5cab3a001163a93e610c30a3dc4829cf1b17a2afa57ee4caec3f5e/dulwich-1.2.9-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:697fc6dbec9fb33d91a53c36c27bbac7e25e2008fb70e90d6f26e05ac95dfdf2", size = 1437132, upload-time = "2026-07-06T20:56:48.774Z" },
{ url = "https://files.pythonhosted.org/packages/41/ae/c4fd50807495b909e8b7568effd06be2613ab7c6c9d4235756a3037cd817/dulwich-1.2.9-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6d86a1baa6f6dd8a11770cf210cc9193e1debb68a7265dfe6da3682b7167d631", size = 1461912, upload-time = "2026-07-06T20:56:50.487Z" },
{ url = "https://files.pythonhosted.org/packages/17/de/4a64c796afe8862cec6fc800bd01c003e15bac11935cdd2be6565c228faa/dulwich-1.2.9-cp313-cp313-win32.whl", hash = "sha256:6ac4b9dd9d668bf757aae70641adba22f9aa0c916ad45d3f734fdd58edb1add8", size = 1081801, upload-time = "2026-07-06T20:56:52.134Z" },
{ url = "https://files.pythonhosted.org/packages/45/2e/29ab77e0553f94f4ee43b94e41c883f86e092f6fa3edea4838d9dda91071/dulwich-1.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:516f843f9a03c34cf4f5f8b55a459bd8833550f4b44dade1ea7f91ef907788e7", size = 1053482, upload-time = "2026-07-06T20:56:53.963Z" },
{ url = "https://files.pythonhosted.org/packages/cd/f9/a6677c0e698b5b781d663eb394bd15cc69c4a270b11cdcc128076753d75c/dulwich-1.2.9-py3-none-any.whl", hash = "sha256:79ff0ff35c4e17ccd58f9bfb23f4f8d416b0b09337b8c99ee3b4f10459bb0d68", size = 711459, upload-time = "2026-07-06T20:57:21.516Z" },
]
[[package]]
@@ -3764,7 +3764,7 @@ wheels = [
[[package]]
name = "prowler"
version = "5.44.0"
version = "5.44.1"
source = { editable = "." }
dependencies = [
{ name = "alibabacloud-actiontrail20200706" },
@@ -3940,7 +3940,7 @@ requires-dist = [
{ name = "dash", specifier = "==3.1.1" },
{ name = "dash-bootstrap-components", specifier = "==2.0.3" },
{ name = "defusedxml", specifier = "==0.7.1" },
{ name = "dulwich", specifier = "==1.2.5" },
{ name = "dulwich", specifier = "==1.2.9" },
{ name = "google-api-python-client", specifier = "==2.163.0" },
{ name = "google-auth-httplib2", specifier = "==0.2.0" },
{ name = "h2", specifier = "==4.3.0" },
@@ -4296,14 +4296,14 @@ wheels = [
[[package]]
name = "pyjwt"
version = "2.13.0"
version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
{ url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" },
]
[package.optional-dependencies]
@@ -5315,11 +5315,11 @@ wheels = [
[[package]]
name = "urllib3"
version = "2.7.0"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
{ url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
]
[[package]]