Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions nbgrader/server_extensions/validate_assignment/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from tornado import web
from textwrap import dedent
from pathlib import Path

from jupyter_server.utils import url_path_join as ujoin
from jupyter_server.base.handlers import JupyterHandler
Expand Down Expand Up @@ -38,8 +39,12 @@ def load_config(self):
return app.config

def validate_notebook(self, path):
fullpath = os.path.join(self.root_dir, path)

root = Path(self.root_dir).resolve()
target = (root / path).resolve()
if not target.is_relative_to(root):
raise web.HTTPError(403, "Access denied: path outside allowed directory")
fullpath = str(target)
Comment on lines 41 to +46
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding validation to handle edge cases where the user-supplied path might be None or empty. While these cases may be prevented by the calling code (lines 98-102), defensive programming would suggest checking for these conditions before path operations to provide clearer error messages. For example:

if not path:
    raise web.HTTPError(400, "Path parameter is required")

Copilot uses AI. Check for mistakes.

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path traversal security fix should have dedicated unit tests to verify that malicious paths (like ../../../etc/passwd, absolute paths, or paths with null bytes) are properly rejected. While UI tests exist for this handler, they don't appear to test these security scenarios. Consider adding test cases that verify:

  1. Normal paths within root_dir work correctly
  2. Paths with ../ sequences attempting to escape root_dir are blocked with 403
  3. Absolute paths are handled correctly
  4. Edge cases like empty paths, paths with null bytes, or Windows drive letters (if applicable) are handled safely
Suggested change
root = Path(self.root_dir).resolve()
target = (root / path).resolve()
if not target.is_relative_to(root):
raise web.HTTPError(403, "Access denied: path outside allowed directory")
fullpath = str(target)
# Basic validation of the incoming path
if not isinstance(path, str):
raise web.HTTPError(400, "Invalid path type")
if not path:
raise web.HTTPError(400, "Missing path")
if "\x00" in path:
# Explicitly reject paths containing null bytes
raise web.HTTPError(400, "Invalid path")
root = Path(self.root_dir).resolve()
try:
target = (root / path).resolve()
except (TypeError, ValueError):
# Catch invalid or malformed paths before they reach the validator
raise web.HTTPError(400, "Invalid path")
root_str = os.fspath(root)
target_str = os.fspath(target)
try:
common = os.path.commonpath([root_str, target_str])
except ValueError:
# On Windows, commonpath raises ValueError for different drives
raise web.HTTPError(403, "Access denied: path outside allowed directory")
if common != root_str:
raise web.HTTPError(403, "Access denied: path outside allowed directory")
fullpath = target_str

Copilot uses AI. Check for mistakes.
Comment thread
yueyueL marked this conversation as resolved.
Outdated
try:
config = self.load_config()
validator = Validator(config=config)
Expand Down Expand Up @@ -144,4 +149,4 @@ def load_jupyter_server_extension(nbapp):
webapp.add_handlers(".*$", [
(ujoin(base_url, pat), handler)
for pat, handler in default_handlers
])
])
Loading