The Python validators library's url function checks whether a string is a well-formed URL, and while it is convenient, URL validation via regular expressions has historically carried a denial-of-service risk that you should understand before relying on it. Reaching for python validators url validation is common when a form or API accepts a link, but "is this a valid URL" is a narrower question than most developers think, and getting it wrong opens the door to both availability and server-side request forgery problems. This guide covers safe usage, the ReDoS history, and what URL validation does and does not protect you from.
Basic usage
The library is small and its URL check is a single call:
import validators
validators.url("https://example.com/path?q=1")
# True
validators.url("not a url")
# ValidationError(func=url, args={'value': 'not a url'})
Note that a falsy result is not a plain False in older-style usage; the function returns a truthy value on success and a ValidationError object on failure, and that object is itself falsy. So if validators.url(candidate): works as expected, but do not assume the failure value is the boolean False. Test the truthiness, not the identity.
The ReDoS history you should know about
Regular-expression-based URL validators are a classic source of Regular Expression Denial of Service (ReDoS). The problem is catastrophic backtracking: a pattern with nested or ambiguous quantifiers can take exponential time on a crafted input, so a single malicious string ties up a CPU core and can take a service down.
This is not hypothetical for URL validation specifically. CVE-2023-45813 was reported for inefficient regular expression complexity in URL-validation logic, part of a broader pattern where a widely copied URL-matching regex, shared around since at least 2012, exhibits exponential blowup on hostile input. The validators project reworked its url implementation in the 0.21 series to move away from the problematic approach, so the practical defense is straightforward: use a current, maintained release rather than an old pinned one.
pip install --upgrade validators
If you cannot upgrade immediately, the mitigations for any regex-based validator apply: cap the length of input you will even attempt to validate, and run untrusted validation with a timeout so a single request cannot burn CPU indefinitely. A dependency scanner will also flag a vulnerable pinned version for you; an SCA tool such as Safeguard can surface an outdated validators release as a transitive dependency before it reaches production.
Validation is not authorization
The more important point is conceptual. Confirming a string is a syntactically valid URL tells you nothing about whether it is safe to fetch. If your application takes a user-supplied URL and makes a request to it, syntactic validation does not stop server-side request forgery (SSRF). An attacker can supply a perfectly valid URL that points at your internal metadata endpoint, a database admin port, or a link-local address:
http://169.254.169.254/latest/meta-data/
http://localhost:6379/
http://[::1]:8080/admin
Every one of those passes a URL validator. Defending against SSRF is a separate job:
- Resolve the hostname and reject requests to private, loopback, link-local, and reserved IP ranges, including IPv6 forms.
- Enforce an allowlist of schemes, typically just
https, and an allowlist of hosts where the use case permits. - Re-check after DNS resolution, because a name can resolve to an internal address (DNS rebinding), and follow redirects manually so a redirect cannot bounce you into a private range.
- Fetch from a network segment that has no route to internal services.
Treat validators.url() as a cheap syntactic filter that rejects obvious garbage, not as a security boundary.
Choosing between validators and the standard library
For many cases you do not need a third-party library at all. Python's urllib.parse gives you the components, and you can assert the parts you care about:
from urllib.parse import urlparse
def is_acceptable(candidate: str) -> bool:
try:
parts = urlparse(candidate)
except ValueError:
return False
return parts.scheme in {"https"} and bool(parts.netloc)
This is explicit, has no ReDoS surface, and encodes your actual policy: which schemes you accept and that a host is present. The validators library is more convenient and handles more edge cases of well-formedness, which is genuinely useful for user-facing validation messages. Pick based on whether you need strict well-formedness feedback (library) or a security-relevant policy check (parse the components yourself).
A practical checklist
When you accept URLs from users, layer the checks:
- Length-limit the input before validating anything.
- Use a maintained version of your validation library and let a scanner watch it for you.
- Parse and enforce a scheme allowlist explicitly.
- If you will fetch the URL, add SSRF protection at the network and resolution layers, not just the string layer.
- Log and rate-limit validation failures so a flood of bad input is visible.
The habit that matters most is separating well-formedness from safety in your own head. python validators url checks answer the first question well. The second question is where the real risk lives, and no validator answers it for you.
FAQ
Is the Python validators library safe to use for URL validation?
Yes, on a current version. Older regex-based releases carried a ReDoS risk, addressed when the project reworked its url validator in the 0.21 series. Keep the dependency up to date and let a scanner flag stale versions.
Does validators.url() prevent SSRF?
No. It only checks that a string is a well-formed URL. A valid URL can still point at an internal address. SSRF defense requires resolving the host, blocking private and link-local ranges, and enforcing scheme and host allowlists.
Why does validators.url() not return False on failure?
On failure it returns a ValidationError object, which is falsy, rather than the boolean False. Check truthiness with if validators.url(x): and avoid comparing the result to False directly.
Should I use urllib.parse instead?
For a security-relevant policy check, parsing the URL yourself with urllib.parse and asserting the scheme and host is explicit and has no regex ReDoS surface. Use the validators library when you need richer well-formedness feedback for users.