-
Notifications
You must be signed in to change notification settings - Fork 334
Fix Path Traversal in validate_assignment via Path Normalization #1978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
cb21dd4
43c4041
a8c4774
49b4e74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
There was a problem hiding this comment.
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
pathmight 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: