Docling HTML Rendering Vulnerability: What You Need to Know
Executive Summary
A critical vulnerability has been identified in Docling versions 2.82.0 through 2.90.x that allows attackers to access arbitrary local files through HTML rendering. The flaw exists in the HTML backend’s failure to enforce the enable_local_fetch security option when rendering pages in headless browser mode. This vulnerability can lead to unauthorized disclosure of sensitive files, including environment variables, configuration files, and user credentials. Organizations using affected versions should prioritize upgrading to version 2.118.1 or later immediately.
Severity Level: High
CVSS Estimate: 7.5 (High)
Affected Versions: 2.82.0 – 2.90.x
Fixed Version: 2.118.1 and later
Understanding the Docling Vulnerability
What the Vulnerability Is and How It Works
Docling is a document intelligence library that processes various file formats, including HTML. When configured to render HTML pages using a headless browser (via HTMLBackendOptions(render_page=True)), the application should restrict access to local files through the enable_local_fetch configuration option.
However, in affected versions, this security control fails to function properly. Attackers can craft malicious HTML documents containing file:// URLs that reference local system files. When Docling renders these pages, the headless browser processes the file references without validation, allowing the attacker to read arbitrary files from the system.
The vulnerability occurs because:
- The
enable_local_fetchsetting is not properly enforced during HTML rendering operations - File URL validation is bypassed in render mode
- No restrictions prevent the headless browser from accessing the local filesystem
Why the enable_local_fetch Option Fails in Render Mode
The enable_local_fetch parameter is designed to control whether Docling can fetch resources from the local filesystem. In document conversion mode, this setting works as intended. However, when HTML rendering is enabled (render_page=True), the security check is not applied to the headless browser’s resource loading process.
This occurs because:
- Separate Processing Paths: The HTML rendering pipeline uses a different code path than standard document processing
- Browser-Level Access: The headless browser operates at a lower level and doesn’t inherit the application-level security restrictions
- Incomplete Implementation: The security control was not extended to cover all rendering scenarios
Which Versions Are Affected
The vulnerability impacts:
- Docling 2.82.0 through 2.90.x (inclusive)
- All configurations using
HTMLBackendOptions(render_page=True) - Applications processing untrusted or user-supplied HTML
Versions 2.118.1 and later include the security patch and are safe to use.
What Is Affected
Applications Processing Untrusted HTML with render_page=True
Any application that:
- Accepts HTML input from users or external sources
- Uses Docling’s HTML backend with rendering enabled
- Processes this HTML without strict validation
These applications are directly vulnerable to file disclosure attacks.
Shared Hosting Environments and Multi-Tenant Deployments
Organizations running multiple applications or customers on shared infrastructure face elevated risk:
- One compromised application could expose files from other applications
- Shared file permissions may allow cross-user file access
- Environment variables and secrets may be accessible across tenant boundaries
Systems Handling Sensitive Files or Environment Variables
Systems storing sensitive data in locations accessible to the Docling process are at particular risk:
.envfiles containing API keys and database credentials- Configuration files with authentication tokens
- SSH keys and private certificates
- Application source code
- Database connection strings
Attack Scenarios and Real-World Impact
How Attackers Exploit HTML Injection to Access Local Files
Scenario 1: Direct File Reference
An attacker submits the following HTML to a document processing application:
<html>
<body>
<img src="file:///etc/passwd" />
<img src="file:///.env" />
<img src="file:///home/user/.ssh/id_rsa" />
</body>
</html>
When Docling renders this HTML with render_page=True, the headless browser attempts to load these files. The rendered output image contains the file contents, which the attacker can retrieve.
Scenario 2: Environment Variable Exfiltration
An attacker crafts HTML that references environment variables through JavaScript:
<html>
<script>
// Attempt to access environment through various methods
document.body.innerHTML = process.env.DATABASE_URL;
</script>
</body>
</html>
While JavaScript execution may be limited, the attempt demonstrates the attack surface.
Data Exfiltration Through Rendered Page Images
The rendered output from Docling includes:
- Text content from accessed files
- Configuration data
- Credentials and tokens
- Source code snippets
Attackers can:
- Submit malicious HTML to the vulnerable application
- Retrieve the rendered page image
- Extract sensitive information from the image
- Use extracted credentials for further attacks
Cross-User File Access in Shared Environments
In multi-tenant or shared hosting scenarios:
- User A uploads HTML that references User B’s files
- If file permissions allow, User B’s data is exposed
- The vulnerability becomes a privilege escalation vector
- Attackers can map the filesystem and discover other users’ sensitive files
Immediate Actions: Upgrade and Patch
Upgrading to Docling 2.118.1 and Later
Step 1: Identify Your Current Version
pip show docling
Look for the version number in the output.
Step 2: Update Docling
pip install --upgrade docling>=2.118.1
Or if using a requirements file:
pip install -r requirements.txt --upgrade
Step 3: Verify the Upgrade
pip show docling
# Confirm version is 2.118.1 or higher
Verifying Your Current Version
Check your deployment across all environments:
import docling
print(docling.__version__)
For containerized deployments, verify the Dockerfile or image:
FROM python:3.10
RUN pip install docling>=2.118.1
Testing After Patching
After upgrading, conduct the following tests:
Test 1: Verify File Access Restrictions
from docling.backend.html import HTMLBackendOptions
from docling.document_converter import DocumentConverter
# This should now properly restrict file access
converter = DocumentConverter(
format_options={
"html": HTMLBackendOptions(
render_page=True,
enable_local_fetch=False # Should be enforced
)
}
)
Test 2: Test with Malicious HTML
malicious_html = """
<html>
<body>
<img src="file:///etc/passwd" />
</body>
</html>
In patched versions, this should fail gracefully without exposing file contents.
Test 3: Regression Testing
Ensure legitimate HTML rendering still works:
safe_html = """
<html>
<body>
<h1>Test Document</h1>
<p>This is a legitimate document.</p>
</body>
</html>
Mitigation Strategies for Your Environment
Disabling render_page=True for Untrusted HTML
The most effective mitigation is to disable HTML rendering for user-supplied content:
from docling.backend.html import HTMLBackendOptions
from docling.document_converter import DocumentConverter
# Safe configuration for untrusted HTML
converter = DocumentConverter(
format_options={
"html": HTMLBackendOptions(
render_page=False # Disable rendering for untrusted sources
)
}
)
When to use this approach:
- Processing user-uploaded HTML files
- Handling HTML from external APIs
- Any scenario where HTML source cannot be fully trusted
Using Streams Instead of File Paths for Input Handling
Instead of passing file paths, use streams to prevent filesystem access:
# Vulnerable approach (file path)
from pathlib import Path
result = converter.convert(Path("user_upload.html"))
# Safer approach (stream)
from io import BytesIO
with open("user_upload.html", "rb") as f:
html_content = f.read()
stream = BytesIO(html_content)
result = converter.convert(stream)
This approach:
- Prevents Docling from accessing the filesystem directly
- Allows you to validate content before processing
- Enables content filtering and sanitization
Implementing Strict File Access Controls
Configure operating system-level restrictions:
Linux/Unix File Permissions:
# Restrict access to sensitive files
chmod 600 .env
chmod 600 config.yml
chmod 700 /path/to/sensitive/directory
# Ensure application runs with minimal privileges
chown app:app /app/directory
chmod 750 /app/directory
Docker Security:
FROM python:3.10
# Run as non-root user
RUN useradd -m -u 1000 appuser
USER appuser
# Copy application files
COPY --chown=appuser:appuser . /app
WORKDIR /app
# Install dependencies
RUN pip install --user docling>=2.118.1
Restricting Sensitive File Permissions
Implement a defense-in-depth approach:
# Restrict environment files
chmod 600 .env
chmod 600 .env.local
chmod 600 .env.*.local
# Restrict configuration files
chmod 600 config/database.yml
chmod 600 config/secrets.yml
# Restrict SSH keys and certificates
chmod 600 ~/.ssh/id_rsa
chmod 600 /etc/ssl/private/*.key
# Restrict application directories
chmod 750 /var/www/app
chmod 750 /opt/application
Detection and Monitoring
Reviewing Application Logs for Suspicious File Access Patterns
Monitor for indicators of exploitation:
Log Patterns to Watch:
- Requests containing "file://" URLs
- HTML uploads with unusual file references
- Rendering operations on suspicious content
- Failed file access attempts
- Unusual file read operations
Example Log Analysis:
# Search for file:// references in application logs
grep -r "file://" /var/log/application/
# Look for rendering errors with file paths
grep -i "render.*error\|file.*access" /var/log/application/
# Monitor for suspicious HTML uploads
grep -i "html.*upload\|upload.*html" /var/log/application/
Implementing File Access Auditing
Enable system-level file access monitoring:
Linux auditd Configuration:
# Monitor access to sensitive files
auditctl -w /etc/passwd -p wa -k passwd_changes
auditctl -w /root/.ssh -p wa -k ssh_key_access
auditctl -w /home -p wa -k home_access
# Monitor application directory
auditctl -w /var/www/app -p wa -k app_access
# View audit logs
ausearch -k app_access
Application-Level Logging:
import logging
import os
# Configure detailed logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Log file access attempts
def safe_convert(file_path):
logger.info(f"Processing file: {file_path}")
logger.info(f"File size: {os.path.getsize(file_path)}")
try:
result = converter.convert(file_path)
logger.info("Conversion successful")
return result
except Exception as e:
logger.error(f"Conversion failed: {e}")
raise
Conducting Static Code Analysis for Vulnerable Configurations
Scan your codebase for vulnerable patterns:
Search for Vulnerable Code:
# Find render_page=True usage
grep -r "render_page\s*=\s*True" .
# Find HTMLBackendOptions usage
grep -r "HTMLBackendOptions" .
# Find Path-based conversions
grep -r "converter.convert(Path" .
Automated Analysis with Python:
import ast
import os
def find_vulnerable_patterns(directory):
vulnerable_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.py'):
filepath = os.path.join(root, file)
with open(filepath, 'r') as f:
try:
tree = ast.parse(f.read())
for node in ast.walk(tree):
if isinstance(node, ast.Call):
# Check for render_page=True
for keyword in node.keywords:
if keyword.arg == 'render_page':
if isinstance(keyword.value, ast.Constant):
if keyword.value.value is True:
vulnerable_files.append(filepath)
except:
pass
return vulnerable_files
vulnerable = find_vulnerable_patterns('.')
for file in vulnerable:
print(f"Review: {file}")

Long-Term Security Practices
Validating and Sanitizing HTML Inputs
Implement input validation before processing:
from html.parser import HTMLParser
import re
class HTMLValidator:
"""Validate HTML before processing with Docling"""
DANGEROUS_TAGS = ['script', 'iframe', 'object', 'embed']
DANGEROUS_ATTRIBUTES = ['onload', 'onerror', 'onclick', 'onmouseover']
@staticmethod
def is_safe(html_content):
"""Check if HTML contains dangerous elements"""
# Check for file:// URLs
if re.search(r'file://', html_content, re.IGNORECASE):
return False
# Check for dangerous tags
for tag in HTMLValidator.DANGEROUS_TAGS:
if re.search(f'<{tag}', html_content, re.IGNORECASE):
return False
# Check for dangerous attributes
for attr in HTMLValidator.DANGEROUS_ATTRIBUTES:
if re.search(f'{attr}\s*=', html_content, re.IGNORECASE):
return False
return True
# Usage
user_html = request.files['document'].read().decode('utf-8')
if not HTMLValidator.is_safe(user_html):
raise ValueError("HTML contains potentially dangerous content")
result = converter.convert(user_html)
Maintaining an Inventory of Docling Deployments
Track all systems using Docling:
Inventory Template:
| System | Version | Environment | Last Updated | Status |
|---|---|---|---|---|
| API Server 1 | 2.118.1 | Production | 2024-01-15 | Patched |
| API Server 2 | 2.118.1 | Production | 2024-01-15 | Patched |
| Dev Environment | 2.118.1 | Development | 2024-01-14 | Patched |
| Legacy System | 2.85.0 | Production | 2023-06-01 | Vulnerable |
Automated Inventory Script:
import subprocess
import json
from datetime import datetime
def get_docling_version():
"""Get installed Docling version"""
result = subprocess.run(
['pip', 'show', 'docling', '--format', 'json'],
capture_output=True,
text=True
)
return json.loads(result.stdout)
inventory = {
'timestamp': datetime.now().isoformat(),
'docling': get_docling_version(),
'python_version': subprocess.run(
['python', '--version'],
capture_output=True,
text=True
).stdout.strip()
}
print(json.dumps(inventory, indent=2))
Establishing a Patch Management Schedule
Create a formal process for security updates:
Patch Management Workflow:
- Detection (Immediate)
- Monitor security advisories
- Subscribe to Docling security notifications
- Review CVE databases
- Assessment (Within 24 hours)
- Identify affected systems
- Evaluate business impact
- Plan deployment
- Testing (Within 48 hours)
- Test patches in development environment
- Verify functionality
- Conduct regression testing
- Deployment (Within 1 week for critical vulnerabilities)
- Deploy to staging
- Final validation
- Deploy to production
- Monitor for issues
- Verification (Ongoing)
- Confirm all systems updated
- Update inventory
- Document completion
Monitoring Security Advisories for Future Updates
Stay informed about security issues:
Recommended Sources:
- GitHub Security Advisories: Watch the Docling repository for security alerts
- Python Package Index: Monitor PyPI for security notices
- Security Mailing Lists: Subscribe to relevant security communities
- Vendor Notifications: Enable notifications from Docling maintainers
Automated Monitoring:
import requests
from datetime import datetime, timedelta
def check_docling_security():
"""Check for Docling security advisories"""
# Check GitHub API for security advisories
url = "https://api.github.com/repos/docling/docling/security/advisories"
try:
response = requests.get(url)
advisories = response.json()
for advisory in advisories:
if advisory['state'] == 'published':
print(f"Advisory: {advisory['summary']}")
print(f"Severity: {advisory['severity']}")
print(f"Published: {advisory['published_at']}")
print()
except Exception as e:
print(f"Error checking advisories: {e}")
# Run periodically (e.g., daily)
check_docling_security()
Frequently Asked Questions
What is the primary risk of the Docling vulnerability?
The primary risk is unauthorized access to local files on systems running affected versions of Docling. Attackers can craft malicious HTML documents that reference local files using file:// URLs. When Docling renders these documents with render_page=True, the headless browser reads the files without proper validation, exposing sensitive information such as:
- Environment variables containing API keys and database credentials
- Configuration files with authentication tokens
- SSH private keys and certificates
- Application source code
- User data and personal information
In shared hosting or multi-tenant environments, this vulnerability can also enable cross-user file access, allowing one user to read another user’s sensitive files.
How do I check if my application is affected?
Follow these steps to determine if your application is vulnerable:
Step 1: Check Your Docling Version
pip show docling
If the version is between 2.82.0 and 2.90.x (inclusive), your installation is vulnerable.
Step 2: Review Your Code
Search your codebase for:
grep -r "render_page\s*=\s*True" .
grep -r "HTMLBackendOptions" .
If you find render_page=True in your code, you’re using the vulnerable feature.
Step 3: Assess Your Input Sources
Determine if your application processes HTML from untrusted sources:
- User uploads
- External APIs
- Web forms
- Third-party integrations
If you process untrusted HTML with rendering enabled, your application is vulnerable.
What should I do if I cannot upgrade immediately?
If you cannot upgrade to version 2.118.1 immediately, implement these mitigation measures:
Immediate Actions:
- Disable HTML Rendering
HTMLBackendOptions(render_page=False)
- Use Streams Instead of File Paths
from io import BytesIO
stream = BytesIO(html_content)
result = converter.convert(stream)
- Implement Input Validation
if "file://" in html_content:
raise ValueError("HTML contains file:// references")
- Restrict File Permissions
chmod 600 .env
chmod 600 config/*.yml
- Enable Monitoring
- Monitor application logs for suspicious activity
- Implement file access auditing
- Alert on unusual rendering operations
Timeline for Upgrade:
- Critical systems: Upgrade within 24-48 hours
- Production systems: Upgrade within 1 week
- Non-production systems: Upgrade within 2 weeks
Does this vulnerability affect all Docling versions?
No. The vulnerability affects specific versions:
Vulnerable Versions:
- 2.82.0 through 2.90.x (inclusive)
Safe Versions:
- 2.118.1 and later (patched)
- Versions prior to 2.82.0 (not affected)
Note: Versions prior to 2.82.0 may have other vulnerabilities. It’s recommended to use the latest stable version.
Can I safely use Docling with trusted HTML sources?
Yes, Docling can be safely used with HTML from trusted sources, even in affected versions, if you follow these guidelines:
Safe Usage Conditions:
- Disable Rendering for Untrusted Content
# Only enable rendering for trusted HTML
if is_trusted_source(html_source):
options = HTMLBackendOptions(render_page=True)
else:
options = HTMLBackendOptions(render_page=False)
- Validate HTML Content
if not contains_dangerous_patterns(html_content):
result = converter.convert(html_content)
- Use Streams for Input
# Safer than file paths
stream = BytesIO(trusted_html_content)
result = converter.convert(stream)
- Implement Access Controls
- Restrict file system permissions
- Run Docling with minimal privileges
- Isolate the process in containers
Best Practice: Even with trusted sources, upgrade to version 2.118.1 or later to eliminate the vulnerability entirely.
How do I implement file access monitoring?
Implement monitoring at multiple levels:
Operating System Level (Linux):
# Install auditd if not present
sudo apt-get install auditd
# Monitor sensitive files
sudo auditctl -w /etc/passwd -p wa -k passwd_changes
sudo auditctl -w /root/.ssh -p wa -k ssh_access
sudo auditctl -w /var/www/app -p wa -k app_access
# View logs
sudo ausearch -k app_access
Application Level (Python):
import logging
import functools
import os
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('file_access.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def log_file_access(func):
"""Decorator to log file access"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"Function called: {func.__name__}")
logger.info(f"Arguments: {args}, {kwargs}")
try:
result = func(*args, **kwargs)
logger.info(f"Function succeeded: {func.__name__}")
return result
except Exception as e:
logger.error(f"Function failed: {func.__name__} - {e}")
raise
return wrapper
@log_file_access
def convert_document(file_path):
"""Convert document with logging"""
logger.info(f"Processing file: {file_path}")
logger.info(f"File size: {os.path.getsize(file_path)} bytes")
return converter.convert(file_path)
Container Level (Docker):
FROM python:3.10
# Enable security logging
RUN apt-get update && apt-get install -y auditd
# Copy application
COPY . /app
WORKDIR /app
# Install dependencies
RUN pip install docling>=2.118.1
# Run with security options
CMD ["python", "app.py"]
What is the difference between using streams versus file paths?
File Paths Approach:
from pathlib import Path
# Vulnerable approach
file_path = Path("user_upload.html")
result = converter.convert(file_path)
Risks:
- Docling accesses the filesystem directly
- No opportunity to validate content before processing
- Vulnerable to directory traversal attacks
- Harder to control what files are accessed
Streams Approach:
from io import BytesIO
# Safer approach
with open("user_upload.html", "rb") as f:
content = f.read()
stream = BytesIO(content)
result = converter.convert(stream)
Benefits:
- You control file access
- Opportunity to validate content before processing
- Can implement content filtering
- Easier to audit and monitor
- Works with in-memory content
- Prevents directory traversal
Comparison Table:
| Aspect | File Path | Stream |
|---|---|---|
| Filesystem Access | Direct | Controlled |
| Content Validation | After processing | Before processing |
| Security | Lower | Higher |
| Performance | Slightly faster | Slightly slower |
| Flexibility | Limited | High |
| Auditability | Harder | Easier |
Recommendation: Use streams for all untrusted content, especially user uploads and external sources.
References
- Official Docling Repository: github.com/docling/docling
- Docling Issue #3948: github.com/docling/docling/issues/3948
- Python Security Best Practices: python.readthedocs.io/en/latest/library/security_warnings.html
- OWASP File Upload Security: owasp.org/www-community/vulnerabilities/UnrestrictedFileUpload
- CWE-434: Unrestricted Upload of File with Dangerous Type: cwe.mitre.org/data/definitions/434.html
Conclusion
The Docling HTML rendering vulnerability represents a significant security risk for organizations processing untrusted HTML content. The immediate priority is to upgrade to version 2.118.1 or later. For systems that cannot be upgraded immediately, disabling HTML rendering and implementing strict input validation provide effective interim protection.
By following the mitigation strategies outlined in this advisory, maintaining an inventory of Docling deployments, and establishing a formal patch management process, organizations can significantly reduce their exposure to this and future vulnerabilities. Regular monitoring and adherence to security best practices will help ensure the continued security of your systems.
For questions or additional information, contact your security team or consult the official Docling documentation.
Document Version: 1.0
Last Updated: January 2024
Classification: Security Advisory