diff --git a/apps/api/plane/app/serializers/webhook.py b/apps/api/plane/app/serializers/webhook.py index 74ebde89205..40c36486f4d 100644 --- a/apps/api/plane/app/serializers/webhook.py +++ b/apps/api/plane/app/serializers/webhook.py @@ -3,6 +3,7 @@ # See the LICENSE file for details. # Python imports +import os import socket import ipaddress from urllib.parse import urlparse @@ -15,6 +16,8 @@ from plane.db.models import Webhook, WebhookLog from plane.db.models.webhook import validate_domain, validate_schema +ENABLE_WEBHOOK_SSRF_PROTECTION = os.environ.get("ENABLE_WEBHOOK_SSRF_PROTECTION", "1") != "0" + class WebhookSerializer(DynamicBaseSerializer): url = serializers.URLField(validators=[validate_schema, validate_domain]) @@ -36,10 +39,11 @@ def create(self, validated_data): if not ip_addresses: raise serializers.ValidationError({"url": "No IP addresses found for the hostname."}) - for addr in ip_addresses: - ip = ipaddress.ip_address(addr[4][0]) - if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local: - raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."}) + if ENABLE_WEBHOOK_SSRF_PROTECTION: + for addr in ip_addresses: + ip = ipaddress.ip_address(addr[4][0]) + if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local: + raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."}) # Additional validation for multiple request domains and their subdomains request = self.context.get("request") @@ -71,10 +75,11 @@ def update(self, instance, validated_data): if not ip_addresses: raise serializers.ValidationError({"url": "No IP addresses found for the hostname."}) - for addr in ip_addresses: - ip = ipaddress.ip_address(addr[4][0]) - if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local: - raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."}) + if ENABLE_WEBHOOK_SSRF_PROTECTION: + for addr in ip_addresses: + ip = ipaddress.ip_address(addr[4][0]) + if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local: + raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."}) # Additional validation for multiple request domains and their subdomains request = self.context.get("request") diff --git a/apps/api/plane/db/models/webhook.py b/apps/api/plane/db/models/webhook.py index 99431ed4225..01017a317e9 100644 --- a/apps/api/plane/db/models/webhook.py +++ b/apps/api/plane/db/models/webhook.py @@ -3,6 +3,7 @@ # See the LICENSE file for details. # Python imports +import os from uuid import uuid4 from urllib.parse import urlparse @@ -27,8 +28,9 @@ def validate_schema(value): def validate_domain(value): parsed_url = urlparse(value) domain = parsed_url.netloc - if domain in ["localhost", "127.0.0.1"]: - raise ValidationError("Local URLs are not allowed.") + if os.environ.get("ENABLE_WEBHOOK_SSRF_PROTECTION", "1") != "0": + if domain in ["localhost", "127.0.0.1"]: + raise ValidationError("Local URLs are not allowed.") class Webhook(BaseModel):