We release security updates for the following versions of Valid8r:
| Version | Supported |
|---|---|
| 0.9.x | ✅ |
| 0.8.x | ✅ |
| 0.7.x | ✅ |
| < 0.7.0 | ❌ |
We take security issues seriously. If you discover a security vulnerability in Valid8r, please follow these steps:
Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.
Send an email to [email protected] with the following information:
- Description: A clear description of the vulnerability
- Impact: Potential impact and attack scenarios
- Steps to Reproduce: Detailed steps to reproduce the issue
- Affected Versions: Which versions are affected
- Proposed Fix: If you have suggestions for fixing the issue
Subject: [SECURITY] Description of the vulnerability
**Description:**
A clear and concise description of the vulnerability.
**Impact:**
What could an attacker do with this vulnerability?
**Steps to Reproduce:**
1. Step one
2. Step two
3. ...
**Affected Versions:**
- Version X.X.X
- Version Y.Y.Y
**Proposed Fix:**
(Optional) Your suggestions for fixing the issue.
**Additional Context:**
Any additional information, configurations, or screenshots.
- Initial Response: You will receive an acknowledgment within 48 hours
- Status Updates: We will keep you informed of our progress
- Fix Timeline: We aim to release security fixes within 7 days for critical issues
- Credit: We will credit you in the security advisory (unless you prefer to remain anonymous)
- Disclosure: We follow coordinated disclosure and will work with you on the disclosure timeline
When a security vulnerability is confirmed:
- Patch Development: We develop and test a fix
- Version Bump: We prepare a new release with the security fix
- Security Advisory: We publish a GitHub Security Advisory
- Release: We release the patched version to PyPI
- Notification: We notify users through:
- GitHub Security Advisories
- Release notes
- CHANGELOG.md
When using Valid8r:
- Always validate untrusted input: Use Valid8r parsers for all external data
- Fail securely: Handle
Failureresults appropriately - Don't leak information: Avoid exposing detailed error messages to end users
- Keep Updated: Regularly update Valid8r and its dependencies
- Monitor Advisories: Watch for security advisories on GitHub
- Use Dependabot: Enable Dependabot alerts in your repository
from valid8r import parsers, validators
from valid8r.core.maybe import Success, Failure
# Good: Parse and validate untrusted input
user_age = input("Enter your age: ")
match parsers.parse_int(user_age):
case Success(age) if age >= 0 and age <= 120:
print(f"Valid age: {age}")
case Success(age):
print("Age out of valid range") # Don't expose the actual value
case Failure(_):
print("Invalid input") # Don't expose error details
# Good: Validate email addresses
email = input("Enter email: ")
match parsers.parse_email(email):
case Success(email_obj):
# Proceed with validated email
send_confirmation(email_obj)
case Failure(_):
print("Invalid email format")Valid8r is designed as a parsing and validation library, not a security boundary:
- ✅ Trusted input: Best suited for user-generated content in web forms, API requests
⚠️ Untrusted input: Should be pre-validated at application level (size limits, rate limiting)- ❌ Hostile input: Requires additional defenses (WAF, DDoS protection, sandboxing)
All parsers include built-in length validation to prevent DoS attacks:
| Parser | Max Input | Rationale |
|---|---|---|
parse_phone() |
100 chars | Longest international format + extension |
parse_email() |
254 chars | RFC 5321 maximum email address length |
parse_url() |
2048 chars | Common browser URL limit |
parse_uuid() |
36 chars | Standard UUID format (with hyphens) |
parse_ip() |
45 chars | IPv6 maximum length |
parse_ipv4() |
15 chars | xxx.xxx.xxx.xxx |
parse_ipv6() |
45 chars | Full IPv6 notation |
parse_cidr() |
50 chars | IPv6 + CIDR notation |
Valid8r provides input validation, not:
- ❌ SQL injection - Use parameterized queries / ORMs
- ❌ XSS - Use output encoding / template engines
- ❌ CSRF - Use CSRF tokens / SameSite cookies
- ❌ Rate limiting - Implement at framework/WAF level
- ❌ Authentication/Authorization - Use proper auth framework
- ❌ DDoS attacks - Use CDN / cloud provider protection
All parsers include built-in length validation to prevent Denial of Service attacks. Input length is checked BEFORE expensive operations like regex matching.
Example: Phone Parser DoS Protection (Fixed in v0.9.1)
Prior to v0.9.1, the phone parser performed regex operations before validating input length:
- ❌ 1MB malicious input: ~48ms to reject (after regex)
- ✅ v0.9.1+: 1MB input: <1ms to reject (before regex)
Implementation Pattern:
def parse_phone(text: str | None, *, region: str = 'US', strict: bool = False) -> Maybe[PhoneNumber]:
# Handle None or empty
if text is None or not isinstance(text, str):
return Maybe.failure('Phone number cannot be empty')
s = text.strip()
if s == '':
return Maybe.failure('Phone number cannot be empty')
# CRITICAL: Early length guard (DoS mitigation)
if len(text) > 100:
return Maybe.failure('Invalid format: phone number is too long')
# Now safe to perform regex operations
# ...Why This Matters:
- Prevents resource exhaustion from oversized inputs
- Rejects malicious inputs in microseconds instead of milliseconds
- Applies to all parsers that use regex (email, URL, IP, phone, etc.)
Additional Application-Level Protection:
While Valid8r includes built-in length validation, you can add additional limits for your specific use case:
from valid8r import parsers
from valid8r.core.maybe import Maybe, Failure
MAX_EMAIL_LENGTH = 254 # RFC 5321 maximum
def safe_parse_email(text: str) -> Maybe[EmailAddress]:
"""Parse email with stricter length limit for your application."""
if len(text) > MAX_EMAIL_LENGTH:
return Failure("Email address exceeds maximum length")
return parsers.parse_email(text)Parser error messages are designed to be user-friendly but may contain details about why validation failed. In security-sensitive contexts, consider sanitizing error messages before displaying to end users.
Issue: Phone parser performed regex operations before validating input length, allowing DoS attacks with extremely large inputs.
Impact: Medium severity - could cause resource exhaustion with 1MB+ inputs
- Processing time: ~48ms for 1MB malicious input
- Attack vector: Requires ability to send oversized POST data
- Real-world risk: Low (most frameworks limit request size)
Fix: Added early length validation before regex operations
- Rejects oversized inputs in <1ms (48x faster)
- Limit: 100 characters (reasonable for any phone number format)
- Error message: "Invalid format: phone number is too long"
Testing: Added performance-validated test ensuring <10ms rejection time
Lesson: Always validate input length BEFORE expensive operations. This pattern now applies to all new parsers.
Related: Issue #131, PR #138
Always use multiple layers of validation:
from flask import Flask, request
from valid8r import parsers
app = Flask(__name__)
@app.route('/submit', methods=['POST'])
def submit():
# Layer 1: Framework-level size limit (FIRST DEFENSE)
if len(request.data) > 10_000: # 10KB max request
return "Request too large", 413
# Layer 2: Application-level validation (SECOND DEFENSE)
phone = request.form.get('phone', '')
if len(phone) > 100:
return "Phone number too long", 400
# Layer 3: Parser validation (THIRD DEFENSE)
result = parsers.parse_phone(phone)
if result.is_failure():
# Don't expose internal error details to users
return "Invalid phone number format", 400
# Now safe to use validated phone number
return process_phone(result.value_or(None))Protect validation endpoints from abuse:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["100 per hour"]
)
@app.route('/validate')
@limiter.limit("10 per minute") # Strict limit for validation
def validate_input():
# Validation logic
passDon't expose internal validation details:
from valid8r import parsers
from valid8r.core.maybe import Success, Failure
result = parsers.parse_email(user_input)
match result:
case Success(email):
# Log successful validation (info level)
logger.info(f"Valid email processed: {email.domain}")
return email
case Failure(error):
# Log failure (warning level) but DON'T expose to user
logger.warning(f"Email validation failed: {error}")
# Generic user-facing message
return "Invalid email address format"Use timeouts and size limits for batch operations:
from celery import Celery
from valid8r import parsers
app = Celery('tasks')
@app.task(time_limit=5) # 5 second timeout per task
def validate_batch(phone_numbers):
# Limit batch size
if len(phone_numbers) > 1000:
raise ValueError("Batch too large")
results = []
for phone in phone_numbers:
# Pre-validation before parsing
if len(phone) > 100:
results.append({"error": "too long"})
continue
result = parsers.parse_phone(phone)
results.append({
"success": result.is_success(),
"error": None if result.is_success() else "invalid format"
})
return resultsFlask:
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 # 10KBDjango:
DATA_UPLOAD_MAX_MEMORY_SIZE = 10240 # 10KBFastAPI:
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["example.com"]
)Include security tests in your CI/CD:
# Run DoS prevention tests
uv run pytest tests/security/test_dos_prevention.py -v
# Run all security-marked tests
uv run pytest -m securityThis security policy covers:
- ✅ Valid8r library code (parsers, validators, Maybe monad)
- ✅ Input validation vulnerabilities
- ✅ Dependency vulnerabilities
- ❌ Vulnerabilities in user application code
- ❌ Misuse of the library by developers
For security issues: [email protected]
For general questions: Open a GitHub Discussion or Issue
Thank you for helping keep Valid8r and its users safe!