From c5caa0848b96670c363f7891345457e4e8f3a639 Mon Sep 17 00:00:00 2001
From: Tanishq Dubey <dubey@dws.rip>
Date: Thu, 27 Feb 2025 21:36:22 -0500
Subject: [PATCH] more path validation

---
 src/routes/routes.py | 39 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/src/routes/routes.py b/src/routes/routes.py
index e506e4d..db96295 100644
--- a/src/routes/routes.py
+++ b/src/routes/routes.py
@@ -4,12 +4,42 @@ from src.rendering.renderer import render_page, render_error_page
 from flask import send_file 
 from src.rendering.image import generate_thumbnail
 from functools import lru_cache
+import os
 
 
 class RouteManager:
     def __init__(self, config: Configuration):
         self.config = config
 
+
+    def _validate_and_sanitize_path(self, base_dir, requested_path):
+        """
+        Validate and sanitize the requested path to ensure it does not traverse above the base directory.
+
+        :param base_dir: The base directory that the requested path should be within.
+        :param requested_path: The requested file path to validate.
+        :return: A secure version of the requested path if valid, otherwise None.
+        """
+        # Normalize both paths
+        base_dir = os.path.abspath(base_dir)
+        requested_path = os.path.abspath(requested_path)
+
+        # Check if the requested path is within the base directory
+        if not requested_path.startswith(base_dir):
+            return None
+
+        # Ensure the path does not contain any '..' or '.' components
+        secure_path = os.path.relpath(requested_path, base_dir)
+        secure_path_parts = secure_path.split(os.sep)
+
+        for part in secure_path_parts:
+            if part == '.' or part == '..':
+                return None
+
+        # Reconstruct the secure path
+        secure_path = os.path.join(base_dir, *secure_path_parts)
+        return secure_path
+
     def _ensure_route(self, path: str):
         """
         Escapes the path for anything like
@@ -28,6 +58,9 @@ class RouteManager:
         for part in file_path.parts:
             if part.startswith("__"):
                 raise Exception("Illegal path")
+            
+        if not self._validate_and_sanitize_path(self.config.content_dir, str(file_path)):
+            raise Exception("Illegal path")
 
     def default_route(self, path: str):
         try:
@@ -38,9 +71,9 @@ class RouteManager:
         except Exception as e:
             print(e)
             return render_error_page(
-                403,
-                "Forbidden",
-                "You do not have permission to access this resource.",
+                404,
+                "Not Found",
+                "The requested resource was not found on this server.",
                 self.config.templates_dir,
             )
         file_path: Path = self.config.content_dir / (path if path else "index.md")