prompt_template/web/path_validator.py

80 lines
2.5 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Pfad-Validierung für Template-Dateien.
Verhindert Path-Traversal-Angriffe und validiert, dass angeforderte
Pfade innerhalb des erlaubten Verzeichnisses liegen.
"""
import os
from typing import Optional
from urllib.parse import unquote, urlparse
class PathValidator:
"""Validiert und auflöst URL-Pfade zu Dateisystem-Pfaden."""
def __init__(self, root_dir: str, web_dir: str):
"""
Args:
root_dir: Projekt-Root-Verzeichnis (enthält templates/)
web_dir: web/-Verzeichnis (enthält templates.json)
"""
self.root_dir = root_dir
self.web_dir = web_dir
def resolve_template_path(self, path: str) -> Optional[str]:
"""
Resolve einen Request-Pfad zu einem Dateisystem-Pfad.
Gibt None zurück, falls der Pfad ungültig oder nicht autorisiert ist.
Beispiele:
/templates.json -> <web_dir>/templates.json
/templates/system/foo.json -> <root_dir>/templates/system/foo.json
"""
parsed = urlparse(path).path
path = parsed
# /templates.json ist ein Sonderfall - exakter Pfad im web/-Verzeichnis
if path == "/templates.json":
return os.path.join(self.web_dir, "templates.json")
# Nur /templates/* Pfade erlauben
if not path.startswith("/templates/"):
return None
# Extrahiere den relativen Pfad nach /templates/
rel_path = path[len("/templates/") :]
rel_path = unquote(rel_path)
# Ablehnen bei leeren Pfaden oder absoluten Pfaden
if not rel_path or os.path.isabs(rel_path):
return None
# Ablehnen bei '..' Sequenzen (vor der Normalisierung!)
if ".." in rel_path.split(os.sep):
return None
# Pfad normalisieren
normalized_rel = os.path.normpath(rel_path)
# Nach Normalisierung nochmal prüfen
if normalized_rel.startswith("..") or os.path.isabs(normalized_rel):
return None
# Vollständigen Pfad konstruieren
full_path = os.path.join(self.root_dir, "templates", normalized_rel)
# Explizite Prüfung, dass der Pfad innerhalb von ROOT_DIR/templates/ liegt
templates_base = os.path.abspath(os.path.join(self.root_dir, "templates"))
full_path_abs = os.path.abspath(full_path)
if (
not full_path_abs.startswith(templates_base + os.sep)
and full_path_abs != templates_base
):
return None
return full_path_abs