chore: modify some pro checks

This commit is contained in:
Daniel Barranquero
2026-04-13 17:14:20 +02:00
parent 18160e615f
commit 04a6dca608
32 changed files with 95 additions and 57 deletions

View File

@@ -84,10 +84,10 @@ class VercelService:
)
if response.status_code == 403:
# Plan limitation or permission error — return None for graceful handling
# Endpoint unavailable for this token/scope; let checks handle it gracefully
logger.warning(
f"{self.service} - Access denied for {path} (403). "
"This may be a plan limitation."
"This may be caused by plan or permission restrictions."
)
return None

View File

@@ -34,5 +34,5 @@
"RelatedTo": [
"project_deployment_protection_enabled"
],
"Notes": "Required billing plan: Pro."
"Notes": "Required billing plan: Enterprise, or as a paid add-on for Pro plans."
}

View File

@@ -37,8 +37,8 @@ class project_password_protection_enabled(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {project.name} does not have password protection "
f"configured for deployments. This feature is only available "
f"on Vercel Pro/Enterprise plans."
f"configured for deployments. This feature is available on "
f"Vercel Enterprise plans, or as a paid add-on for Pro plans."
)
findings.append(report)

View File

@@ -34,5 +34,5 @@
"RelatedTo": [
"project_deployment_protection_enabled"
],
"Notes": "Required billing plan: Pro."
"Notes": "Protecting production deployments requires Enterprise, or Pro plans with supported paid deployment protection options."
}

View File

@@ -37,8 +37,9 @@ class project_production_deployment_protection_enabled(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {project.name} does not have deployment protection "
f"enabled on production deployments. This feature is only "
f"available on Vercel Pro/Enterprise plans."
f"enabled on production deployments. Protecting production "
f"deployments is available on Vercel Enterprise plans, or on "
f"Pro plans for supported deployment protection options."
)
findings.append(report)

View File

@@ -32,5 +32,5 @@
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Required billing plan: Pro."
"Notes": "Required billing plan: Pro or Enterprise."
}

View File

@@ -34,7 +34,7 @@ class project_skew_protection_enabled(Check):
report.status_extended = (
f"Project {project.name} does not have skew protection enabled, "
f"which may cause version mismatches during deployments. This "
f"feature is only available on Vercel Pro/Enterprise plans."
f"feature is available on Vercel Enterprise and Pro plans."
)
findings.append(report)

View File

@@ -34,5 +34,5 @@
"RelatedTo": [
"security_waf_enabled"
],
"Notes": "Required billing plan: Pro."
"Notes": ""
}

View File

@@ -34,8 +34,7 @@ class security_custom_rules_configured(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"does not have any custom firewall rules configured. "
f"This feature is only available on Vercel Pro/Enterprise plans."
f"does not have any custom firewall rules configured."
)
findings.append(report)

View File

@@ -34,5 +34,5 @@
"RelatedTo": [
"security_waf_enabled"
],
"Notes": "Required billing plan: Pro."
"Notes": ""
}

View File

@@ -35,8 +35,7 @@ class security_ip_blocking_rules_configured(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"does not have any IP blocking rules configured. "
f"This feature is only available on Vercel Pro/Enterprise plans."
f"does not have any IP blocking rules configured."
)
findings.append(report)

View File

@@ -9,7 +9,7 @@
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "security",
"Description": "**Vercel projects** are assessed for **managed WAF ruleset** enablement. Managed rulesets are curated by Vercel and provide protection against known attack patterns including **OWASP Top 10** threats. This feature requires an Enterprise plan and reports MANUAL status when unavailable.",
"Description": "**Vercel projects** are assessed for **managed WAF ruleset** enablement. Managed rulesets are curated by Vercel and provide protection against known attack patterns including **OWASP Top 10** threats. Availability varies by ruleset, and the check reports MANUAL when the firewall configuration cannot be assessed from the API.",
"Risk": "Without **managed rulesets** enabled, the firewall lacks curated protection rules against well-known attack patterns. The application relies solely on custom rules, which may miss **new or evolving threats** that managed rulesets are designed to detect and block automatically.",
"RelatedUrl": "",
"AdditionalURLs": [
@@ -19,11 +19,11 @@
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Vercel dashboard\n2. Navigate to the project Settings > Security > Firewall\n3. Enable managed rulesets from the available options\n4. Review and configure ruleset sensitivity levels\n5. Note: This feature requires an Enterprise plan",
"Other": "1. Sign in to the Vercel dashboard\n2. Navigate to the project Settings > Security > Firewall\n3. Enable the managed rulesets that are available for your plan\n4. Review and configure ruleset sensitivity levels\n5. If the API does not expose firewall configuration for the project, verify the rulesets manually in the dashboard",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable managed WAF rulesets to benefit from Vercel-curated protection against common attack patterns. If you are on a plan that does not support managed rulesets, consider upgrading to the Enterprise plan for enhanced security features.",
"Text": "Enable the managed WAF rulesets that are available for your Vercel plan to benefit from curated protection against common attack patterns. If the API does not expose firewall configuration for the project, verify the rulesets manually in the dashboard.",
"Url": "https://hub.prowler.com/check/security_managed_rulesets_enabled"
}
},
@@ -34,5 +34,5 @@
"RelatedTo": [
"security_waf_enabled"
],
"Notes": "Required billing plan: Enterprise."
"Notes": "Managed ruleset availability varies by ruleset. OWASP Core Ruleset requires Enterprise, while Bot Protection and AI Bots managed rulesets are available on all plans."
}

View File

@@ -17,8 +17,8 @@ class security_managed_rulesets_enabled(Check):
"""Execute the Vercel Managed Rulesets Enabled check.
Iterates over all firewall configurations and checks if managed
rulesets are enabled. Reports MANUAL status when the feature is
not available due to plan limitations.
rulesets are enabled. Reports MANUAL status when the firewall
configuration cannot be assessed from the API.
Returns:
List[CheckReportVercel]: A list of reports for each project.
@@ -28,11 +28,12 @@ class security_managed_rulesets_enabled(Check):
report = CheckReportVercel(metadata=self.metadata(), resource=config)
if config.managed_rulesets is None:
report.status = "FAIL"
report.status = "MANUAL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"does not have managed WAF rulesets enabled. "
f"This feature is only available on Vercel Enterprise plans."
f"could not be assessed for managed rulesets because the "
f"firewall configuration endpoint was not accessible. "
f"Manual verification is required."
)
elif config.managed_rulesets:
report.status = "PASS"
@@ -44,8 +45,7 @@ class security_managed_rulesets_enabled(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"does not have managed WAF rulesets enabled. "
f"This feature is only available on Vercel Enterprise plans."
f"does not have managed WAF rulesets enabled."
)
findings.append(report)

View File

@@ -34,5 +34,5 @@
"RelatedTo": [
"security_waf_enabled"
],
"Notes": "Required billing plan: Pro."
"Notes": ""
}

View File

@@ -34,8 +34,7 @@ class security_rate_limiting_configured(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"does not have any rate limiting rules configured. "
f"This feature is only available on Vercel Pro/Enterprise plans."
f"does not have any rate limiting rules configured."
)
findings.append(report)

View File

@@ -32,7 +32,7 @@ class Security(VercelService):
)
if data is None:
# 403 — plan limitation, store with managed_rulesets=None
# Firewall config endpoint unavailable for this project/token
self.firewall_configs[project.id] = VercelFirewallConfig(
project_id=project.id,
project_name=project.name,
@@ -118,7 +118,7 @@ class VercelFirewallConfig(BaseModel):
project_name: Optional[str] = None
team_id: Optional[str] = None
firewall_enabled: bool = False
managed_rulesets: Optional[dict] = None # None means plan-gated (403)
managed_rulesets: Optional[dict] = None # None means config endpoint unavailable
custom_rules: list[dict] = Field(default_factory=list)
ip_blocking_rules: list[dict] = Field(default_factory=list)
rate_limiting_rules: list[dict] = Field(default_factory=list)

View File

@@ -35,5 +35,5 @@
"security_managed_rulesets_enabled",
"security_custom_rules_configured"
],
"Notes": "Required billing plan: Pro."
"Notes": ""
}

View File

@@ -25,11 +25,12 @@ class security_waf_enabled(Check):
report = CheckReportVercel(metadata=self.metadata(), resource=config)
if config.managed_rulesets is None:
# 403 — plan limitation, cannot determine WAF status
# Firewall config could not be retrieved for this project
report.status = "MANUAL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"could not be checked for WAF status due to plan limitations. "
f"could not be checked for WAF status because the firewall "
f"configuration endpoint was not accessible. "
f"Manual verification is required."
)
elif config.firewall_enabled:
@@ -42,8 +43,7 @@ class security_waf_enabled(Check):
report.status = "FAIL"
report.status_extended = (
f"Project {config.project_name} ({config.project_id}) "
f"does not have the Web Application Firewall enabled. "
f"This feature is only available on Vercel Pro/Enterprise plans."
f"does not have the Web Application Firewall enabled."
)
findings.append(report)

View File

@@ -35,5 +35,5 @@
"RelatedTo": [
"team_saml_sso_enforced"
],
"Notes": "Required billing plan: Pro."
"Notes": "Required billing plan: Pro or Enterprise."
}

View File

@@ -38,7 +38,7 @@ class team_saml_sso_enabled(Check):
report.status = "FAIL"
report.status_extended = (
f"Team {team.name} does not have SAML SSO enabled. "
f"This feature is only available on Vercel Pro/Enterprise plans."
f"This feature is available on Vercel Enterprise and Pro plans."
)
findings.append(report)

View File

@@ -35,5 +35,5 @@
"RelatedTo": [
"team_saml_sso_enabled"
],
"Notes": "Required billing plan: Pro."
"Notes": "Required billing plan: Pro or Enterprise."
}

View File

@@ -39,12 +39,12 @@ class team_saml_sso_enforced(Check):
report.status_extended = (
f"Team {team.name} has SAML SSO enabled but does not enforce it. "
f"Members can still authenticate without SSO. This feature is "
f"only available on Vercel Pro/Enterprise plans."
f"available on Vercel Enterprise and Pro plans."
)
else:
report.status_extended = (
f"Team {team.name} does not have SAML SSO enforced. "
f"This feature is only available on Vercel Pro/Enterprise plans."
f"This feature is available on Vercel Enterprise and Pro plans."
)
findings.append(report)

View File

@@ -102,7 +102,7 @@ class Test_project_password_protection_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} does not have password protection configured for deployments. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} does not have password protection configured for deployments. This feature is available on Vercel Enterprise plans, or as a paid add-on for Pro plans."
)
assert result[0].team_id == TEAM_ID
@@ -139,6 +139,6 @@ class Test_project_password_protection_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} does not have password protection configured for deployments. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} does not have password protection configured for deployments. This feature is available on Vercel Enterprise plans, or as a paid add-on for Pro plans."
)
assert result[0].team_id == TEAM_ID

View File

@@ -109,7 +109,7 @@ class Test_project_production_deployment_protection_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} does not have deployment protection enabled on production deployments. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} does not have deployment protection enabled on production deployments. Protecting production deployments is available on Vercel Enterprise plans, or on Pro plans for supported deployment protection options."
)
assert result[0].team_id == TEAM_ID
@@ -146,6 +146,6 @@ class Test_project_production_deployment_protection_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} does not have deployment protection enabled on production deployments. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} does not have deployment protection enabled on production deployments. Protecting production deployments is available on Vercel Enterprise plans, or on Pro plans for supported deployment protection options."
)
assert result[0].team_id == TEAM_ID

View File

@@ -102,6 +102,6 @@ class Test_project_skew_protection_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} does not have skew protection enabled, which may cause version mismatches during deployments. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} does not have skew protection enabled, which may cause version mismatches during deployments. This feature is available on Vercel Enterprise and Pro plans."
)
assert result[0].team_id == TEAM_ID

View File

@@ -108,6 +108,6 @@ class Test_security_custom_rules_configured:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any custom firewall rules configured. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any custom firewall rules configured."
)
assert result[0].team_id == TEAM_ID

View File

@@ -108,6 +108,6 @@ class Test_security_ip_blocking_rules_configured:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any IP blocking rules configured. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any IP blocking rules configured."
)
assert result[0].team_id == TEAM_ID

View File

@@ -110,7 +110,7 @@ class Test_security_managed_rulesets_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have managed WAF rulesets enabled. This feature is only available on Vercel Enterprise plans."
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have managed WAF rulesets enabled."
)
assert result[0].team_id == TEAM_ID
@@ -147,9 +147,9 @@ class Test_security_managed_rulesets_enabled:
assert len(result) == 1
assert result[0].resource_id == PROJECT_ID
assert result[0].resource_name == PROJECT_NAME
assert result[0].status == "FAIL"
assert result[0].status == "MANUAL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have managed WAF rulesets enabled. This feature is only available on Vercel Enterprise plans."
== f"Project {PROJECT_NAME} ({PROJECT_ID}) could not be assessed for managed rulesets because the firewall configuration endpoint was not accessible. Manual verification is required."
)
assert result[0].team_id == TEAM_ID

View File

@@ -108,6 +108,6 @@ class Test_security_rate_limiting_configured:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any rate limiting rules configured. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any rate limiting rules configured."
)
assert result[0].team_id == TEAM_ID

View File

@@ -110,6 +110,46 @@ class Test_security_waf_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have the Web Application Firewall enabled. This feature is only available on Vercel Pro/Enterprise plans."
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have the Web Application Firewall enabled."
)
assert result[0].team_id == TEAM_ID
def test_waf_status_unavailable(self):
security_client = mock.MagicMock
security_client.firewall_configs = {
PROJECT_ID: VercelFirewallConfig(
project_id=PROJECT_ID,
project_name=PROJECT_NAME,
team_id=TEAM_ID,
firewall_enabled=False,
managed_rulesets=None,
id=PROJECT_ID,
name=PROJECT_NAME,
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_vercel_provider(),
),
mock.patch(
"prowler.providers.vercel.services.security.security_waf_enabled.security_waf_enabled.security_client",
new=security_client,
),
):
from prowler.providers.vercel.services.security.security_waf_enabled.security_waf_enabled import (
security_waf_enabled,
)
check = security_waf_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].resource_id == PROJECT_ID
assert result[0].resource_name == PROJECT_NAME
assert result[0].status == "MANUAL"
assert (
result[0].status_extended
== f"Project {PROJECT_NAME} ({PROJECT_ID}) could not be checked for WAF status because the firewall configuration endpoint was not accessible. Manual verification is required."
)
assert result[0].team_id == TEAM_ID

View File

@@ -103,6 +103,6 @@ class Test_team_saml_sso_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Team {TEAM_NAME} does not have SAML SSO enabled. This feature is only available on Vercel Pro/Enterprise plans."
== f"Team {TEAM_NAME} does not have SAML SSO enabled. This feature is available on Vercel Enterprise and Pro plans."
)
assert result[0].team_id == ""

View File

@@ -102,7 +102,7 @@ class Test_team_saml_sso_enforced:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Team {TEAM_NAME} has SAML SSO enabled but does not enforce it. Members can still authenticate without SSO. This feature is only available on Vercel Pro/Enterprise plans."
== f"Team {TEAM_NAME} has SAML SSO enabled but does not enforce it. Members can still authenticate without SSO. This feature is available on Vercel Enterprise and Pro plans."
)
assert result[0].team_id == ""
@@ -139,6 +139,6 @@ class Test_team_saml_sso_enforced:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Team {TEAM_NAME} does not have SAML SSO enforced. This feature is only available on Vercel Pro/Enterprise plans."
== f"Team {TEAM_NAME} does not have SAML SSO enforced. This feature is available on Vercel Enterprise and Pro plans."
)
assert result[0].team_id == ""