Skip to content

Return ValidationError instead of leaking AttributeError on non-string input#465

Open
gaoflow wants to merge 1 commit into
python-validators:masterfrom
gaoflow:fix-validator-attributeerror-leak
Open

Return ValidationError instead of leaking AttributeError on non-string input#465
gaoflow wants to merge 1 commit into
python-validators:masterfrom
gaoflow:fix-validator-attributeerror-leak

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Several validators leak an uncaught AttributeError on non-string input instead of returning ValidationError:

import validators
validators.uuid(123)       # AttributeError: 'int' object has no attribute 'replace'
validators.email(123)      # AttributeError: 'int' object has no attribute 'count'
validators.hostname(123)   # AttributeError: 'int' object has no attribute 'count'
validators.cron(123)       # AttributeError: 'int' object has no attribute 'strip'

This is inconsistent with the many validators that already return ValidationError for wrong-typed input (domain, slug, md5, url, iban, mac_address, …). Since a validator's job is to vet untrusted/unknown input, a caller reasonably passes a value of unknown type and expects True/ValidationError — not a crash.

Cause

These validators reach for a string method on the value (UUID(value)value.replace(...), value.count(...), value.strip()), which raises AttributeError for an int/float/bool/list/dict. The @validator decorator in utils.py converts (ValueError, TypeError, UnicodeError) into ValidationError, but AttributeError isn't in that set, so it escapes.

Fix

Add AttributeError to the decorator's caught set. AttributeError from calling a string method on a non-string is squarely the "invalid input" category the decorator already handles for TypeError/ValueError, so this makes the whole class of validators handle non-string input uniformly — at the one place that defines the contract — and guards future validators from the same gap.

Added a cross-validator regression test (test_returns_validation_error_on_non_string_input, uuid/email/hostname/cron × int/float/bool/list/dict). Full suite: 915 passed (was 895). ruff check / ruff format --check clean.

Note / scope

I scoped this to the AttributeError class. Two validators (es_doi, es_nie) leak KeyError on some inputs via a different code path; I left those out deliberately, since catching KeyError globally in the decorator would risk masking genuine logic errors. Happy to address them separately if you'd like.

This pull request was prepared with the assistance of AI, under my direction and review.

…uteError

Validators that reach for string methods on the value -- uuid via
UUID(value) -> value.replace, email/hostname via value.count, cron via
value.strip -- raise AttributeError when given a non-string (int, float,
bool, list, dict). The validator decorator only converted (ValueError,
TypeError, UnicodeError) to ValidationError, so AttributeError escaped
uncaught: a caller passing untrusted or unknown-typed data to a validator
got a crash instead of the documented True/ValidationError result, and
inconsistently with the many validators that already return ValidationError
for wrong-typed input.

Add AttributeError to the decorator's caught set so this whole class of
validators handles non-string input uniformly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant