all2md.utils.security
Security utilities for all2md conversion modules.
This module provides security validation functions to prevent unauthorized access to local files and malicious ZIP archive processing.
Functions
resolve_file_url_to_path: Resolve file:// URL to canonical filesystem path
validate_local_file_access: Check if access to a local file path is allowed
validate_zip_archive: Pre-validate ZIP archives for security threats
sanitize_language_identifier: Sanitize code fence language identifiers
- all2md.utils.security.sanitize_null_bytes(content: str) str
Remove null bytes and zero-width characters that can bypass XSS filters.
This function removes various null-like and zero-width Unicode characters that attackers may use to bypass XSS sanitization filters. These characters can be used to hide malicious payloads or break parser assumptions.
Removed characters: - \x00 (NULL byte) - \ufeff (BOM/Zero Width No-Break Space) - \u200b (Zero Width Space) - \u200c (Zero Width Non-Joiner) - \u200d (Zero Width Joiner) - \u2060 (Word Joiner)
- Parameters:
content (str) – Content to sanitize
- Returns:
Sanitized content with dangerous characters removed
- Return type:
str
Examples
>>> sanitize_null_bytes("Hello\\x00World") 'HelloWorld' >>> sanitize_null_bytes("Test\\u200bZero\\u200cWidth") 'TestZeroWidth' >>> sanitize_null_bytes("Normal text") 'Normal text'
Notes
This function is used by HTML and other parsers to prevent XSS attacks that rely on null bytes or zero-width characters to bypass security filters.
See also
HtmlToAstConverter.convert_to_astUses this function to sanitize HTML input
- all2md.utils.security.resolve_file_url_to_path(file_url: str) Path
Resolve a file:// URL to a canonical filesystem path.
This function provides consistent path resolution for file:// URLs across the codebase. It handles various file URL formats including relative paths, Windows paths, and UNC paths. All paths are resolved to absolute canonical form to prevent path traversal attacks.
Supported file:// URL formats: - Unix/Linux absolute: file:///path/to/file - Windows drive letters: file:///C:/path/to/file - Windows UNC paths: file://server/share/file - Relative paths: file://./file or file://../file - Relative to CWD: file://filename
- Parameters:
file_url (str) – The file:// URL to resolve
- Returns:
Resolved absolute path object
- Return type:
Path
- Raises:
ValueError – If file_url is not a file:// URL
Examples
>>> resolve_file_url_to_path("file:///etc/passwd") Path('/etc/passwd') >>> resolve_file_url_to_path("file://./image.png") Path('/current/working/directory/image.png') >>> resolve_file_url_to_path("file:///C:/Users/file.txt") # Windows Path('C:/Users/file.txt')
Notes
This function is used by both validate_local_file_access() and HTML parser to ensure consistent path resolution and prevent TOCTOU vulnerabilities.
Always call validate_local_file_access() before using the resolved path to access files, to ensure security policies are enforced.
- all2md.utils.security.validate_local_file_access(file_url: str, allow_local_files: bool = False, local_file_allowlist: list[str] | None = None, local_file_denylist: list[str] | None = None, allow_cwd_files: bool = True) bool
Validate if access to a local file URL is allowed based on security settings.
Supports various file URL formats including: - Unix/Linux: file:///path/to/file - Windows drive letters: file:///C:/path/to/file - Windows UNC paths: file://server/share/file - Relative paths: file://./file or file://../file
- Parameters:
file_url (str) – The file:// URL to validate
allow_local_files (bool, default False) – Master switch for local file access. If False, no local files are allowed.
local_file_allowlist (list[str] | None, default None) – List of directories allowed for local file access (when allow_local_files=True)
local_file_denylist (list[str] | None, default None) – List of directories denied for local file access
allow_cwd_files (bool, default True) – Allow local files from current working directory and subdirectories. Only applies when allow_local_files=True.
- Returns:
True if access is allowed, False otherwise
- Return type:
bool
Examples
>>> validate_local_file_access("file:///etc/passwd", allow_local_files=False) False >>> validate_local_file_access("file://./image.png", allow_local_files=False) False >>> validate_local_file_access("file://./image.png", allow_local_files=True, allow_cwd_files=True) True >>> validate_local_file_access("file:///C:/Users/file.txt", allow_local_files=True) # Windows True
- all2md.utils.security.validate_zip_archive(file_path: str | Path, max_compression_ratio: float = 100.0, max_uncompressed_size: int = 1073741824, max_entries: int = 10000) None
Validate a ZIP archive for security threats before processing.
This function performs pre-validation checks on ZIP archives to detect potential security threats like zip bombs, path traversal attacks, and excessive resource consumption.
- Parameters:
file_path (str or Path) – Path to the ZIP archive to validate
max_compression_ratio (float, default 100.0) – Maximum allowed compression ratio (uncompressed/compressed)
max_uncompressed_size (int, default 1073741824) – Maximum total uncompressed size in bytes (default: 1GB)
max_entries (int, default 10000) – Maximum number of entries in the archive
- Raises:
ZipFileSecurityError – If the archive fails security validation
MalformedFileError – If the archive cannot be read for some other reason
Examples
>>> validate_zip_archive("document.docx") # Passes if the file is a safe ZIP archive
>>> validate_zip_archive("malicious.zip") ZipFileSecurityError: ZIP archive has suspicious compression ratio: 1000:1
- all2md.utils.security.validate_tar_archive(file_path: str | Path, max_compression_ratio: float = 100.0, max_uncompressed_size: int = 1073741824, max_entries: int = 10000) None
Validate a TAR archive for security threats before processing.
This function performs pre-validation checks on TAR archives to detect potential security threats like tar bombs, path traversal attacks, and excessive resource consumption.
- Parameters:
file_path (str or Path) – Path to the TAR archive to validate
max_compression_ratio (float, default 100.0) – Maximum allowed compression ratio (uncompressed/compressed)
max_uncompressed_size (int, default 1073741824) – Maximum total uncompressed size in bytes (default: 1GB)
max_entries (int, default 10000) – Maximum number of entries in the archive
- Raises:
ArchiveSecurityError – If the archive fails security validation
MalformedFileError – If the archive cannot be read for some other reason
- all2md.utils.security.validate_7z_archive(file_path: str | Path, max_compression_ratio: float = 100.0, max_uncompressed_size: int = 1073741824, max_entries: int = 10000) None
Validate a 7Z archive for security threats before processing.
This function performs pre-validation checks on 7Z archives to detect potential security threats like archive bombs, path traversal attacks, and excessive resource consumption.
- Parameters:
file_path (str or Path) – Path to the 7Z archive to validate
max_compression_ratio (float, default 100.0) – Maximum allowed compression ratio (uncompressed/compressed)
max_uncompressed_size (int, default 1073741824) – Maximum total uncompressed size in bytes (default: 1GB)
max_entries (int, default 10000) – Maximum number of entries in the archive
- Raises:
ArchiveSecurityError – If the archive fails security validation
MalformedFileError – If the archive cannot be read for some other reason
- all2md.utils.security.validate_rar_archive(file_path: str | Path, max_compression_ratio: float = 100.0, max_uncompressed_size: int = 1073741824, max_entries: int = 10000) None
Validate a RAR archive for security threats before processing.
This function performs pre-validation checks on RAR archives to detect potential security threats like archive bombs, path traversal attacks, and excessive resource consumption.
- Parameters:
file_path (str or Path) – Path to the RAR archive to validate
max_compression_ratio (float, default 100.0) – Maximum allowed compression ratio (uncompressed/compressed)
max_uncompressed_size (int, default 1073741824) – Maximum total uncompressed size in bytes (default: 1GB)
max_entries (int, default 10000) – Maximum number of entries in the archive
- Raises:
ArchiveSecurityError – If the archive fails security validation
MalformedFileError – If the archive cannot be read for some other reason
- all2md.utils.security.validate_safe_extraction_path(output_dir: str | Path, zip_entry_name: str) Path
Validate and return a safe extraction path for a ZIP/archive entry to prevent path traversal.
This function prevents Zip Slip attacks by ensuring that extracted files cannot escape the intended output directory through absolute paths or parent directory traversal (..). Works for ZIP and other archive formats.
- Parameters:
output_dir (str or Path) – The base directory where files should be extracted
zip_entry_name (str) – The filename from the archive entry (ZipInfo.filename or archive member name)
- Returns:
A safe, validated absolute path for extraction
- Return type:
Path
- Raises:
ArchiveSecurityError – If the path contains dangerous patterns or would escape output_dir
Examples
>>> validate_safe_extraction_path("/tmp/out", "subdir/file.txt") Path('/tmp/out/subdir/file.txt')
>>> validate_safe_extraction_path("/tmp/out", "../etc/passwd") ArchiveSecurityError: Unsafe path in archive entry: ../etc/passwd
>>> validate_safe_extraction_path("/tmp/out", "/etc/passwd") ArchiveSecurityError: Unsafe path in archive entry: /etc/passwd
Notes
This function is critical for preventing Zip Slip vulnerabilities (CVE-2018-1000117 and related). Always use this when extracting archive entries to the filesystem.
See also
validate_zip_archivePre-validate ZIP archives for security threats
validate_tar_archivePre-validate TAR archives for security threats
- all2md.utils.security.sanitize_language_identifier(language: str) str
Sanitize code fence language identifier to prevent markdown injection.
Code fence language identifiers must only contain safe characters to prevent markdown injection via malicious language strings. This function validates and sanitizes language strings by checking against a safe pattern of alphanumeric characters, underscores, hyphens, and plus signs.
- Parameters:
language (str) – Raw language identifier string to sanitize
- Returns:
Sanitized language identifier, or empty string if invalid
- Return type:
str
Examples
>>> sanitize_language_identifier("python") 'python' >>> sanitize_language_identifier("c++") 'c++' >>> sanitize_language_identifier("python\\nmalicious") '' >>> sanitize_language_identifier("python javascript") '' >>> sanitize_language_identifier("x" * 100) ''
Notes
This function is used by both HTML and Markdown parsers to ensure consistent security validation of code block language identifiers.
- all2md.utils.security.validate_safe_output_directory(output_dir: str | Path, allowed_base_dirs: list[str | Path] | None = None, block_sensitive_paths: bool = True) Path
Validate that an output directory is safe for file operations.
This function prevents path traversal attacks by detecting and blocking relative paths that escape the current working directory (e.g., ../../etc/). Absolute paths are allowed but can be validated against an allowlist.
Security Model
By default, this function: - BLOCKS: Relative paths that traverse outside CWD (e.g., ../../../etc/) - BLOCKS: Paths to sensitive system directories (optional, via block_sensitive_paths) - ALLOWS: Relative paths within CWD (e.g., ./attachments, subdir/files) - ALLOWS: Absolute paths (explicit intent, but check sensitive paths)
- param output_dir:
The output directory to validate
- type output_dir:
str or Path
- param allowed_base_dirs:
Optional allowlist of base directories. If provided, output_dir must be within one of these directories. When None, uses the default security model described above.
- type allowed_base_dirs:
list[str | Path] | None, default None
- param block_sensitive_paths:
Block paths to common sensitive system directories like /etc, /sys, /proc. Only applies when allowed_base_dirs is None.
- type block_sensitive_paths:
bool, default True
- returns:
The validated, resolved absolute path
- rtype:
Path
- raises SecurityError:
If the path contains path traversal patterns or targets sensitive locations
Examples
>>> validate_safe_output_directory("./attachments") Path('/current/working/directory/attachments')
>>> validate_safe_output_directory("../../../etc/") SecurityError: Path traversal detected
>>> validate_safe_output_directory("/tmp/safe-output") Path('/tmp/safe-output')
>>> validate_safe_output_directory("/tmp/out", allowed_base_dirs=["/tmp"]) Path('/tmp/out')
Notes
This function focuses on preventing PATH TRAVERSAL attacks (the actual vulnerability identified in the security review) rather than restricting all paths outside CWD, which would break legitimate use cases.
- all2md.utils.security.validate_user_regex_pattern(pattern: str) None
Validate user-supplied regex pattern to prevent ReDoS attacks.
This function checks user-supplied regex patterns for dangerous constructs that could lead to catastrophic backtracking (Regular Expression Denial of Service - ReDoS attacks). Patterns with nested quantifiers or other backtracking-prone structures are rejected.
- Parameters:
pattern (str) – The regex pattern to validate
- Raises:
SecurityError – If the pattern is too long or contains dangerous constructs
Examples
>>> validate_user_regex_pattern(r"^/docs/") # Returns None (safe pattern)
>>> validate_user_regex_pattern(r"(a+)+") SecurityError: Regex pattern contains dangerous nested quantifiers
>>> validate_user_regex_pattern("x" * 1000) SecurityError: Regex pattern exceeds maximum length
Notes
This function is conservative and may reject some complex but safe patterns. This is intentional to ensure security. Common safe patterns include: - Simple anchors: ^, $ - Character classes: [a-z], [0-9] - Single-level quantifiers: a+, b*, c{2,5} - Alternations without quantifiers: (cat|dog) - Simple groups: (abc)+
Dangerous patterns that are rejected include: - Nested quantifiers: (a+)+, (b*)* - Quantified groups with inner quantifiers: (a+){2,} - Lookaheads/lookbehinds with quantifiers: (?=.*)+, (?!test)* - Lookaheads/lookbehinds containing quantifiers: (?=.*a) - Overlapping alternations: (a|ab)*, (foo|foobar)+ - Multiple nested groups: ((a+) - Greedy wildcards with quantifiers: .*+, .+*
For more information on ReDoS attacks, see the OWASP documentation on Regular Expression Denial of Service attacks.
- all2md.utils.security.is_relative_url(url: str) bool
Check if a URL is a relative URL.
Relative URLs do not have a scheme and typically start with #, /, ./, ../, or ?.
- Parameters:
url (str) – URL to check
- Returns:
True if URL is relative, False otherwise
- Return type:
bool
Examples
>>> is_relative_url("#section") True >>> is_relative_url("/path/to/file") True >>> is_relative_url("./file.html") True >>> is_relative_url("../parent/file.html") True >>> is_relative_url("?query=param") True >>> is_relative_url("https://example.com") False >>> is_relative_url("javascript:alert(1)") False
- all2md.utils.security.is_url_scheme_dangerous(url: str) bool
Check if a URL uses a dangerous scheme.
Dangerous schemes include javascript:, vbscript:, data:text/html, and others that can be used for XSS attacks or malicious code execution.
- Parameters:
url (str) – URL to check
- Returns:
True if URL uses a dangerous scheme, False otherwise
- Return type:
bool
Examples
>>> is_url_scheme_dangerous("https://example.com") False >>> is_url_scheme_dangerous("javascript:alert('xss')") True >>> is_url_scheme_dangerous("data:text/html,<script>alert('xss')</script>") True >>> is_url_scheme_dangerous("/relative/path") False >>> is_url_scheme_dangerous("vbscript:msgbox('xss')") True
- all2md.utils.security.validate_url_scheme_safe(url: str, context: str = 'URL') None
Validate that a URL does not use a dangerous scheme.
This function raises a ValueError if the URL uses a dangerous scheme, making it suitable for strict validation contexts.
- Parameters:
url (str) – URL to validate
context (str, default "URL") – Context description for error messages (e.g., “Link”, “Image”)
- Raises:
ValueError – If URL uses a dangerous scheme
Examples
>>> validate_url_scheme_safe("https://example.com") # No exception raised
>>> validate_url_scheme_safe("javascript:alert(1)") ValueError: URL uses dangerous scheme
>>> validate_url_scheme_safe("/relative/path") # No exception raised