Skip to content

Commit dfe481e

Browse files
committed
feat: make webhook SSRF protection configurable via ENABLE_WEBHOOK_SSRF_PROTECTION
1 parent 6627282 commit dfe481e

File tree

2 files changed

+17
-10
lines changed

2 files changed

+17
-10
lines changed

apps/api/plane/app/serializers/webhook.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# See the LICENSE file for details.
44

55
# Python imports
6+
import os
67
import socket
78
import ipaddress
89
from urllib.parse import urlparse
@@ -15,6 +16,8 @@
1516
from plane.db.models import Webhook, WebhookLog
1617
from plane.db.models.webhook import validate_domain, validate_schema
1718

19+
ENABLE_WEBHOOK_SSRF_PROTECTION = os.environ.get("ENABLE_WEBHOOK_SSRF_PROTECTION", "1") != "0"
20+
1821

1922
class WebhookSerializer(DynamicBaseSerializer):
2023
url = serializers.URLField(validators=[validate_schema, validate_domain])
@@ -36,10 +39,11 @@ def create(self, validated_data):
3639
if not ip_addresses:
3740
raise serializers.ValidationError({"url": "No IP addresses found for the hostname."})
3841

39-
for addr in ip_addresses:
40-
ip = ipaddress.ip_address(addr[4][0])
41-
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
42-
raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."})
42+
if ENABLE_WEBHOOK_SSRF_PROTECTION:
43+
for addr in ip_addresses:
44+
ip = ipaddress.ip_address(addr[4][0])
45+
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
46+
raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."})
4347

4448
# Additional validation for multiple request domains and their subdomains
4549
request = self.context.get("request")
@@ -71,10 +75,11 @@ def update(self, instance, validated_data):
7175
if not ip_addresses:
7276
raise serializers.ValidationError({"url": "No IP addresses found for the hostname."})
7377

74-
for addr in ip_addresses:
75-
ip = ipaddress.ip_address(addr[4][0])
76-
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
77-
raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."})
78+
if ENABLE_WEBHOOK_SSRF_PROTECTION:
79+
for addr in ip_addresses:
80+
ip = ipaddress.ip_address(addr[4][0])
81+
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
82+
raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."})
7883

7984
# Additional validation for multiple request domains and their subdomains
8085
request = self.context.get("request")

apps/api/plane/db/models/webhook.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# See the LICENSE file for details.
44

55
# Python imports
6+
import os
67
from uuid import uuid4
78
from urllib.parse import urlparse
89

@@ -27,8 +28,9 @@ def validate_schema(value):
2728
def validate_domain(value):
2829
parsed_url = urlparse(value)
2930
domain = parsed_url.netloc
30-
if domain in ["localhost", "127.0.0.1"]:
31-
raise ValidationError("Local URLs are not allowed.")
31+
if os.environ.get("ENABLE_WEBHOOK_SSRF_PROTECTION", "1") != "0":
32+
if domain in ["localhost", "127.0.0.1"]:
33+
raise ValidationError("Local URLs are not allowed.")
3234

3335

3436
class Webhook(BaseModel):

0 commit comments

Comments
 (0)