dgtlmoon/changedetection.io · error · ValidationError

Watch protocol is not permitted or invalid URL format

Error message

Watch protocol is not permitted or invalid URL format

What it means

validate_url() in changedetectionio/forms.py calls is_safe_valid_url() from changedetectionio.validate_url and raises wtforms.ValidationError when the URL fails that check. The check enforces both URL syntax (via urlparse/validators) and a protocol allow-list, blocking schemes like file://, javascript://, and other non-http(s) protocols.

Source

Thrown at changedetectionio/forms.py:625

class validateURL(object):

    """
       Flask wtform validators wont work with basic auth
    """

    def __init__(self, message=None):
        self.message = message

    def __call__(self, form, field):
        # This should raise a ValidationError() or not
        validate_url(field.data)


def validate_url(test_url):
    from changedetectionio.validate_url import is_safe_valid_url
    if not is_safe_valid_url(test_url):
        # This should be wtforms.validators.
        raise ValidationError('Watch protocol is not permitted or invalid URL format')


class validateLLMApiBaseSafe(object):
    """Block private/loopback/reserved api_base values (SSRF) unless the operator
    has opted in via ALLOW_IANA_RESTRICTED_ADDRESSES=true."""

    def __call__(self, form, field):
        from changedetectionio.validate_url import is_llm_api_base_safe
        ok, reason = is_llm_api_base_safe(field.data)
        if not ok:
            raise ValidationError(reason)


class ValidateSinglePythonRegexString(object):
    def __init__(self, message=None):
        self.message = message

    def __call__(self, form, field):

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Prefix the URL with http:// or https:// and ensure it parses as an absolute URL
  2. Remove disallowed schemes (file:, ftp:, javascript:, data:) — only http/https are permitted
  3. Trim whitespace/quotes around the URL before submitting
  4. If integrating programmatically, pre-check with changedetectionio.validate_url.is_safe_valid_url before calling the API

Example fix

# before
validate_url('example.com/page')
# after
validate_url('https://example.com/page')
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def url_ok(u: str) -> bool:
    p = urlparse(u.strip())
    return p.scheme in ('http', 'https') and bool(p.netloc)

# or use the library's own check:
from changedetectionio.validate_url import is_safe_valid_url
assert is_safe_valid_url('https://example.com')

Type guard

def is_http_url(u: str) -> bool:
    p = urlparse(u.strip())
    return p.scheme in ('http', 'https') and bool(p.netloc)

Try / catch

from wtforms import ValidationError
try:
    validate_url(url)
except ValidationError as e:
    flash(str(e))

Prevention

When it happens

Trigger: Calling validate_url(test_url) (directly or via a wtforms field validator) with a URL whose scheme is not http/https, is malformed, or fails is_safe_valid_url's parsing — e.g. 'ftp://example.com', 'file:///etc/passwd', 'javascript:alert(1)', or a string with no scheme at all.

Common situations: Users entering non-http schemes in the watch URL field, missing scheme ('example.com/page'), whitespace/control characters, or integrations passing un-normalized URLs. Also used as an SSRF guard so private/unsafe schemes are rejected.

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/21e25549413cc0ba. Report an issue: GitHub.