How the SecurityValidator Class Prevents Directory Traversal Attacks in Python

The SecurityValidator class in src/security/validators.py blocks directory traversal attacks through a three-layer defense system that filters dangerous patterns, resolves canonical paths, and enforces strict directory boundaries.

The SecurityValidator is the central security gatekeeper in the RichardAtCT/claude-code-telegram repository, ensuring that every user-supplied filesystem path is sanitized before any file operation occurs. By implementing coordinated checks within the validate_path() method, this class prevents attackers from escaping the approved directory sandbox using sequences like ../ or shell injection patterns.

Understanding Directory Traversal Vulnerabilities

Directory traversal (or path traversal) attacks exploit insufficient input validation to access files stored outside the intended directory. Attackers typically inject sequences like ../../../etc/passwd to climb up the filesystem hierarchy. Without proper validation, applications might inadvertently serve sensitive system files or execute arbitrary code.

How SecurityValidator Blocks Path Escapes

The SecurityValidator class implements a defense-in-depth strategy within src/security/validators.py. Rather than relying on a single check, it coordinates three distinct validation layers that must all pass before a path is considered safe. If any layer detects a potential traversal attempt, the method immediately returns (False, None, <error_message>), aborting the operation before file I/O occurs.

Three-Layer Defense Mechanism

Layer 1: Pattern-Based Filtering

Before any path resolution occurs, validate_path() scans the raw input string against DANGEROUS_PATTERNS. This set includes traversal indicators like .., shell expansion characters like ~, and command injection sequences such as $(, ;, &&, and |.

According to lines 61-75 of validators.py, if the input contains any forbidden pattern and security patterns are not explicitly disabled, the validator rejects the request with a clear error message. This early exit prevents malicious strings from ever reaching the filesystem layer.

Layer 2: Canonical Path Resolution

After pattern filtering, the input undergoes canonicalization. The validator converts the string into an absolute Path object and calls Path.resolve() (lines 84-88). This operation:

  • Eliminates symbolic links that could point outside the sandbox
  • Normalizes redundant separators and relative segments like ./ or ../
  • Produces an absolute, unambiguous filesystem location

By resolving the path completely before validation, the class ensures that obfuscated traversal attempts (such as foo/../../../etc/passwd resolved from within /app/approved) are caught in the next layer.

Layer 3: Directory Boundary Enforcement

The final check enforces the sandbox boundary. The private helper _is_within_directory() (lines 110-117) attempts to compute resolved_path.relative_to(approved_directory). If Python raises a ValueError, the path lies outside the approved directory tree, triggering an immediate rejection.

As shown in lines 90-97, only paths that successfully compute a relative relationship to the configured APPROVED_DIRECTORY pass validation. The method then returns (True, resolved_path, None), providing the sanitized absolute path for safe file operations.

Implementation Example

The following example demonstrates typical usage within a Telegram bot command handler:

from pathlib import Path
from src.security.validators import SecurityValidator

# Initialize with approved directory from configuration

validator = SecurityValidator(approved_directory=Path("/app/approved"))

def handle_read_file(user_input_path: str):
    # Validate user-supplied path

    is_valid, resolved_path, error = validator.validate_path(user_input_path)
    
    if not is_valid:
        return f"❌ Security violation: {error}"
    
    # Safe to open - resolved_path is guaranteed to be within /app/approved

    try:
        with open(resolved_path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        return "File not found within approved directory"

Testing the boundary check directly:


# Attempt to escape the sandbox

valid, path, err = validator.validate_path("../../../etc/passwd")
assert not valid
assert "outside approved directory" in err

# Valid path within sandbox

valid, path, err = validator.validate_path("documents/report.txt")
assert valid
assert path == Path("/app/approved/documents/report.txt")

Integration with the Telegram Bot

The SecurityValidator is instantiated in src/main.py using the APPROVED_DIRECTORY configuration setting, making it available throughout the application. Bot handlers in src/bot/handlers/ invoke validate_path() before any file operation, ensuring that malicious path inputs from Telegram messages cannot compromise the host system.

Summary

  • Pattern Filtering: The SecurityValidator scans input for dangerous sequences like .., ~, and shell operators before path resolution occurs in src/security/validators.py.
  • Canonical Resolution: The Path.resolve() method eliminates symbolic links and normalizes relative segments to produce absolute, unambiguous paths.
  • Boundary Enforcement: The _is_within_directory() helper uses relative_to() to ensure resolved paths remain strictly within the configured APPROVED_DIRECTORY.
  • Fail-Safe Design: Any validation failure returns (False, None, error), aborting file operations before they reach the filesystem.

Frequently Asked Questions

What specific patterns does SecurityValidator block?

The validator checks against DANGEROUS_PATTERNS including directory traversal sequences (..), shell expansion characters (~), and command injection symbols ($(, ;, &&, |). These patterns are detected in lines 61-75 of src/security/validators.py before any filesystem interaction occurs.

The Path.resolve() method (lines 84-88) fully resolves symbolic links to their actual targets during canonicalization. If a symlink points outside the approved directory, the subsequent boundary check in _is_within_directory() detects the escape attempt and rejects the path.

Can the pattern filtering be disabled?

Yes, the validate_path() method accepts a parameter to disable security pattern checks, though this is not recommended for production use. When enabled (the default), the dangerous pattern scan provides the first line of defense against traversal attempts and command injection.

What happens when a path fails validation?

The method returns a tuple of (False, None, error_message). The calling code (typically in bot handlers) receives the error message and can notify the user while aborting the file operation. This ensures that invalid paths never reach Python's open() or similar filesystem functions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →