Skip to content

catch up django 3 branch with master changes#65

Open
amschaal wants to merge 170 commits intodjango3.2from
master
Open

catch up django 3 branch with master changes#65
amschaal wants to merge 170 commits intodjango3.2from
master

Conversation

@amschaal
Copy link
Copy Markdown
Owner

catch up django 3 branch with master changes

amschaal and others added 30 commits May 24, 2021 11:23
Updates the requirements on [django](https://github.com/django/django) to permit the latest version.
- [Release notes](https://github.com/django/django/releases)
- [Commits](django/django@1.0...4.1.4)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
troubleshooting rsync wrapper
fix ssh key upload bug
amschaal added 27 commits March 12, 2025 15:46
…racking user creation, especially now that the "share_read_only" permission lowers the bar for account creation. #94
Update django requirement from <4.0 to <5.0
@github-advanced-security
Copy link
Copy Markdown

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

Comment thread bioshareX/api/views.py Fixed
Comment thread bioshareX/api/views.py Fixed
Comment thread bioshareX/forms.py
if not paths_contain(settings.LINK_TO_DIRECTORIES,path):
raise forms.ValidationError('Path not allowed.')
raise forms.ValidationError('Path not whitelisted. Contact the site admin if you believe this to be an error.')
if not os.path.isdir(path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

The problem is accessing a filesystem location based on user input without robust normalization and confinement. The safest general approach is to normalize the user-supplied path using os.path.normpath (or os.path.realpath if symlinks are of concern), then check that the normalized path remains within a set of allowed (safe) root directories. This prevents path traversal attacks, even if the input passes regex or whitelist checks.

To implement the fix:

  • Before performing os.path.isdir(path), normalize path with os.path.normpath and join it to its root parent (the parent as determined by the whitelisting/validated path).
  • Verify that the final normalized absolute path is still inside one of the whitelisted directories (from settings.LINK_TO_DIRECTORIES or similar).
  • Use the normalized path instead of the raw user input for any filesystem access.
  • Minimal changes are required within the method; add normalization code within clean_link_to_path, and use full_path (the result) in place of path for os.path.isdir().

No dependencies are needed; only Python's standard library (os.path) is used.


Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -57,10 +57,14 @@
                 self.fp = FilePath.objects.get(path=parent_path)
                 if not self.fp.is_valid(path):
                     raise forms.ValidationError('Path not allowed.  Must match begin with {} and match one of the expressions: {}'.format(self.fp.path,', '.join(['"'+r+'"' for r in self.fp.regexes])))
-            if not paths_contain(settings.LINK_TO_DIRECTORIES,path):
+            # Normalize and join path with parent_path to prevent traversal attacks
+            full_path = os.path.normpath(os.path.join(parent_path, os.path.relpath(path, start=parent_path)))
+            # Ensure normalized path remains inside any of the allowed directories
+            allowed = any(os.path.commonpath([full_path, allowed_dir]) == allowed_dir for allowed_dir in settings.LINK_TO_DIRECTORIES)
+            if not allowed:
                 raise forms.ValidationError('Path not whitelisted.  Contact the site admin if you believe this to be an error.')
-            if not os.path.isdir(path):
-                raise forms.ValidationError('Path: Directory "%s" does not exist'%path)
+            if not os.path.isdir(full_path):
+                raise forms.ValidationError('Path: Directory "%s" does not exist' % full_path)
         if self.the_instance:
             if self.the_instance.link_to_path and not path:
                 raise forms.ValidationError('It is not possible to change a linked share to a regular share.')
EOF
@@ -57,10 +57,14 @@
self.fp = FilePath.objects.get(path=parent_path)
if not self.fp.is_valid(path):
raise forms.ValidationError('Path not allowed. Must match begin with {} and match one of the expressions: {}'.format(self.fp.path,', '.join(['"'+r+'"' for r in self.fp.regexes])))
if not paths_contain(settings.LINK_TO_DIRECTORIES,path):
# Normalize and join path with parent_path to prevent traversal attacks
full_path = os.path.normpath(os.path.join(parent_path, os.path.relpath(path, start=parent_path)))
# Ensure normalized path remains inside any of the allowed directories
allowed = any(os.path.commonpath([full_path, allowed_dir]) == allowed_dir for allowed_dir in settings.LINK_TO_DIRECTORIES)
if not allowed:
raise forms.ValidationError('Path not whitelisted. Contact the site admin if you believe this to be an error.')
if not os.path.isdir(path):
raise forms.ValidationError('Path: Directory "%s" does not exist'%path)
if not os.path.isdir(full_path):
raise forms.ValidationError('Path: Directory "%s" does not exist' % full_path)
if self.the_instance:
if self.the_instance.link_to_path and not path:
raise forms.ValidationError('It is not possible to change a linked share to a regular share.')
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
raise forms.ValidationError('Path not allowed. Must match begin with {} and match one of the expressions: {}'.format(self.fp.path,', '.join(['"'+r+'"' for r in self.fp.regexes])))
if not paths_contain(settings.LINK_TO_DIRECTORIES,path):
raise forms.ValidationError('Path not whitelisted. Contact the site admin if you believe this to be an error.')
if not os.path.isdir(path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

The most direct and secure way to fix the problem is to ensure that after any path manipulations based on user input, the code normalizes the computed path (using os.path.normpath or os.path.realpath) and checks that it is strictly inside an approved "root" directory (such as one of the allowed file_paths or paths from settings.LINK_TO_DIRECTORIES). This check should be performed immediately before using the path in a file operation like os.path.isdir.

Specifically:

  • After all existing validation in clean_target, and just before using os.path.isdir(path), normalize (realpath preferred to resolve symlinks) both the candidate path and each allowed root, then check if the normalized candidate path starts with any of the allowed root paths.
  • If not, raise a ValidationError.
  • This will require importing os.path (already present).
  • Since file_paths may be many and settings.LINK_TO_DIRECTORIES a list, check the normalized path against all permitted roots.

Edits required:

  • Insert normalization and containment check after all prior validation but before any use of os.path.isdir(path), i.e., just before line 201 in the clean_target method of SymlinkForm (inside bioshareX/forms.py).
  • No additional imports needed (already imported os).
  • No changes to other files necessary.

Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -198,7 +198,13 @@
                     raise forms.ValidationError('Path not allowed.  Must match begin with {} and match one of the expressions: {}'.format(self.fp.path,', '.join(['"'+r+'"' for r in self.fp.regexes])))
             if not paths_contain(settings.LINK_TO_DIRECTORIES,path):
                 raise forms.ValidationError('Path not whitelisted.  Contact the site admin if you believe this to be an error.')
-            if not os.path.isdir(path):
+            # Normalize and check that path is within allowed roots
+            normalized_path = os.path.realpath(path)
+            allowed_roots = [os.path.realpath(fp_path) for fp_path in [fp.path for fp in self.file_paths]]
+            allowed_roots += [os.path.realpath(link_root) for link_root in settings.LINK_TO_DIRECTORIES]
+            if not any(normalized_path.startswith(root + os.sep) or normalized_path == root for root in allowed_roots):
+                raise forms.ValidationError('Normalized path not allowed. Path traversal or access outside allowed roots detected.')
+            if not os.path.isdir(normalized_path):
                 raise forms.ValidationError('Path: Directory "%s" does not exist'%path)
             try:
                 check_symlinks_dfs(path)# search_illegal_symlinks(path) #recursively check for illegal paths
EOF
@@ -198,7 +198,13 @@
raise forms.ValidationError('Path not allowed. Must match begin with {} and match one of the expressions: {}'.format(self.fp.path,', '.join(['"'+r+'"' for r in self.fp.regexes])))
if not paths_contain(settings.LINK_TO_DIRECTORIES,path):
raise forms.ValidationError('Path not whitelisted. Contact the site admin if you believe this to be an error.')
if not os.path.isdir(path):
# Normalize and check that path is within allowed roots
normalized_path = os.path.realpath(path)
allowed_roots = [os.path.realpath(fp_path) for fp_path in [fp.path for fp in self.file_paths]]
allowed_roots += [os.path.realpath(link_root) for link_root in settings.LINK_TO_DIRECTORIES]
if not any(normalized_path.startswith(root + os.sep) or normalized_path == root for root in allowed_roots):
raise forms.ValidationError('Normalized path not allowed. Path traversal or access outside allowed roots detected.')
if not os.path.isdir(normalized_path):
raise forms.ValidationError('Path: Directory "%s" does not exist'%path)
try:
check_symlinks_dfs(path)# search_illegal_symlinks(path) #recursively check for illegal paths
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
# Make sure that everything leading up to the link is a regular old directory. If it doesn't exist, one will be made.
while base_path:
print('base_path', base_path)
if os.path.exists(base_path) and (not os.path.isdir(base_path) or os.path.islink(base_path)):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix the uncontrolled path error:

  • Normalize and resolve the combined path (using os.path.normpath and/or os.path.realpath) resulting from the user's name input.
  • Ensure the resolved path is strictly contained within the intended root directory (i.e., self.base_directory, which is set as either self.share.get_path() or os.path.join(self.share.get_path(), self.subdir)).
  • If the normalized path escapes the intended root, raise a validation error.
  • Apply this check within clean_name() (lines 212–229), right after calculating self.link_path, but before operating on base_path or potentially creating/changing directories.
  • This fix does not require any third-party libraries, only Python's standard os.path.

Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -214,6 +214,11 @@
         else:
             self.base_directory = self.share.get_path()
         self.link_path = os.path.join(self.base_directory, name)
+        # Normalize and check containment
+        normalized_link_path = os.path.normpath(self.link_path)
+        base_directory_norm = os.path.normpath(self.base_directory)
+        if not normalized_link_path.startswith(base_directory_norm + os.sep):
+            raise forms.ValidationError('The final link path escapes the allowed directory.')
         base_path, link_name = os.path.split(self.link_path)
         # Make sure that everything leading up to the link is a regular old directory.  If it doesn't exist, one will be made.
         while base_path:
EOF
@@ -214,6 +214,11 @@
else:
self.base_directory = self.share.get_path()
self.link_path = os.path.join(self.base_directory, name)
# Normalize and check containment
normalized_link_path = os.path.normpath(self.link_path)
base_directory_norm = os.path.normpath(self.base_directory)
if not normalized_link_path.startswith(base_directory_norm + os.sep):
raise forms.ValidationError('The final link path escapes the allowed directory.')
base_path, link_name = os.path.split(self.link_path)
# Make sure that everything leading up to the link is a regular old directory. If it doesn't exist, one will be made.
while base_path:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
if new_path == base_path:
break
base_path = new_path
if os.path.exists(self.link_path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix this, we must ensure that the computed path (self.link_path) is always inside a designated safe root directory -- namely, the share's path (with optional subdir). After joining user-supplied name to the base path, we should normalize or resolve the final path and check that it starts with the expected root directory. This prevents path traversal and ensures users cannot create or target unintended filesystem locations, regardless of user input (even with allowed slashes or weird path segments).

Detailed fix:

  • After construction of self.link_path (in clean_name), normalize the path (using os.path.normpath).
  • Check that the normalized path starts with the intended base directory.
  • If not, raise a validation error.
  • Do not allow the user to escape the base dir by using nested ../ or similar constructs.

Changes needed:

  • In clean_name, after computing self.link_path, normalize and enforce its location with respect to self.base_directory.
  • Imports are sufficient as os.path is already imported.

Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -214,6 +214,11 @@
         else:
             self.base_directory = self.share.get_path()
         self.link_path = os.path.join(self.base_directory, name)
+        # Normalize and ensure link_path stays within base_directory
+        self.link_path = os.path.normpath(self.link_path)
+        base_directory_norm = os.path.normpath(self.base_directory)
+        if not self.link_path.startswith(base_directory_norm + os.sep) and self.link_path != base_directory_norm:
+            raise forms.ValidationError('The link path escapes the intended directory.')
         base_path, link_name = os.path.split(self.link_path)
         # Make sure that everything leading up to the link is a regular old directory.  If it doesn't exist, one will be made.
         while base_path:
EOF
@@ -214,6 +214,11 @@
else:
self.base_directory = self.share.get_path()
self.link_path = os.path.join(self.base_directory, name)
# Normalize and ensure link_path stays within base_directory
self.link_path = os.path.normpath(self.link_path)
base_directory_norm = os.path.normpath(self.base_directory)
if not self.link_path.startswith(base_directory_norm + os.sep) and self.link_path != base_directory_norm:
raise forms.ValidationError('The link path escapes the intended directory.')
base_path, link_name = os.path.split(self.link_path)
# Make sure that everything leading up to the link is a regular old directory. If it doesn't exist, one will be made.
while base_path:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
for i in range(len(path_components)):
subpath = os.sep.join(path_components[:i + 1])
full_path = os.path.join(self.base_directory, subpath)
if not os.path.isdir(full_path) and not os.path.exists(base_path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix the issue, we should ensure that any directory created by the user (in create_subdirectories) will be within the intended base directory (self.base_directory). We do this by normalizing the final directory path (full_path), then confirming it starts with (is contained within) the base directory. If it is not, we raise a validation error or skip creation. This requires adding a check with os.path.normpath() or os.path.abspath(), and ensuring the base path is absolute as well. Only proceed with os.mkdir() if full_path is a subdirectory of self.base_directory. Necessary changes should be made within the create_subdirectories method in bioshareX/forms.py. No external libraries are required, only the built-in Python os and perhaps os.path.

Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -236,7 +236,12 @@
             for i in range(len(path_components)):
                 subpath = os.sep.join(path_components[:i + 1])
                 full_path = os.path.join(self.base_directory, subpath)
-                if not os.path.isdir(full_path) and not os.path.exists(base_path):
+                # Ensure full_path is contained within self.base_directory
+                abs_base = os.path.abspath(self.base_directory)
+                abs_full = os.path.abspath(full_path)
+                if not abs_full.startswith(abs_base + os.sep):
+                    raise forms.ValidationError(f"Illegal directory creation attempt: '{subpath}' is outside of base directory.")
+                if not os.path.isdir(full_path) and not os.path.exists(full_path):
                     os.mkdir(full_path)
                     self.directories_created.append(subpath)
     def create_link(self):
EOF
@@ -236,7 +236,12 @@
for i in range(len(path_components)):
subpath = os.sep.join(path_components[:i + 1])
full_path = os.path.join(self.base_directory, subpath)
if not os.path.isdir(full_path) and not os.path.exists(base_path):
# Ensure full_path is contained within self.base_directory
abs_base = os.path.abspath(self.base_directory)
abs_full = os.path.abspath(full_path)
if not abs_full.startswith(abs_base + os.sep):
raise forms.ValidationError(f"Illegal directory creation attempt: '{subpath}' is outside of base directory.")
if not os.path.isdir(full_path) and not os.path.exists(full_path):
os.mkdir(full_path)
self.directories_created.append(subpath)
def create_link(self):
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
for i in range(len(path_components)):
subpath = os.sep.join(path_components[:i + 1])
full_path = os.path.join(self.base_directory, subpath)
if not os.path.isdir(full_path) and not os.path.exists(base_path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

The best fix is to ensure that any directories or symlinks created by SymlinkForm are strictly within the intended base directory (the one corresponding to the constructed share/subdir path). The fix should normalize the final target path after joining user input with the base directory and then validate that the normalized path starts with the base directory path, preventing directory traversal attacks. Specifically:

  • In the methods handling user input for path creation (clean_name, create_subdirectories, and create_link), after combining user input with the base directory, always normalize the resulting path.
  • Check that the normalized path begins with the base directory path. If it does not, raise a validation error and refuse to create the directory or symlink.
  • Make minimal additional changes: just normalization and containment checks before any filesystem operation involving a user-derived path.

This requires importing os.path if not already imported, using os.path.normpath and possibly os.path.realpath for extra strictness, and for the check, using os.path.commonpath or a startswith comparison between paths.

Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -213,7 +213,11 @@
             self.base_directory = os.path.join(self.share.get_path(), self.subdir)
         else:
             self.base_directory = self.share.get_path()
-        self.link_path = os.path.join(self.base_directory, name)
+        self.link_path = os.path.normpath(os.path.join(self.base_directory, name))
+        # Ensure link_path is within base_directory
+        base_dir_norm = os.path.normpath(self.base_directory)
+        if not self.link_path.startswith(base_dir_norm + os.sep):
+            raise forms.ValidationError('Symlink path escapes the share directory.')
         base_path, link_name = os.path.split(self.link_path)
         # Make sure that everything leading up to the link is a regular old directory.  If it doesn't exist, one will be made.
         while base_path:
@@ -235,8 +239,11 @@
             path_components = normalized_path.split(os.sep)
             for i in range(len(path_components)):
                 subpath = os.sep.join(path_components[:i + 1])
-                full_path = os.path.join(self.base_directory, subpath)
-                if not os.path.isdir(full_path) and not os.path.exists(base_path):
+                full_path = os.path.normpath(os.path.join(self.base_directory, subpath))
+                base_dir_norm = os.path.normpath(self.base_directory)
+                if not full_path.startswith(base_dir_norm + os.sep):
+                    raise forms.ValidationError('Subdirectory path escapes the share directory.')
+                if not os.path.isdir(full_path) and not os.path.exists(full_path):
                     os.mkdir(full_path)
                     self.directories_created.append(subpath)
     def create_link(self):
@@ -246,6 +253,10 @@
         # if not os.path.isdir(base_path) and not os.path.exists(base_path):
         #     os.makedirs(base_path)
         #     self.directories_created = True
+        # Validate again that link_path is within allowed base directory
+        base_dir_norm = os.path.normpath(self.base_directory)
+        if not self.link_path.startswith(base_dir_norm + os.sep):
+            raise forms.ValidationError('Symlink path escapes the share directory.')
         os.symlink(self.cleaned_data['target'], self.link_path)
         return self.link_path
 
EOF
@@ -213,7 +213,11 @@
self.base_directory = os.path.join(self.share.get_path(), self.subdir)
else:
self.base_directory = self.share.get_path()
self.link_path = os.path.join(self.base_directory, name)
self.link_path = os.path.normpath(os.path.join(self.base_directory, name))
# Ensure link_path is within base_directory
base_dir_norm = os.path.normpath(self.base_directory)
if not self.link_path.startswith(base_dir_norm + os.sep):
raise forms.ValidationError('Symlink path escapes the share directory.')
base_path, link_name = os.path.split(self.link_path)
# Make sure that everything leading up to the link is a regular old directory. If it doesn't exist, one will be made.
while base_path:
@@ -235,8 +239,11 @@
path_components = normalized_path.split(os.sep)
for i in range(len(path_components)):
subpath = os.sep.join(path_components[:i + 1])
full_path = os.path.join(self.base_directory, subpath)
if not os.path.isdir(full_path) and not os.path.exists(base_path):
full_path = os.path.normpath(os.path.join(self.base_directory, subpath))
base_dir_norm = os.path.normpath(self.base_directory)
if not full_path.startswith(base_dir_norm + os.sep):
raise forms.ValidationError('Subdirectory path escapes the share directory.')
if not os.path.isdir(full_path) and not os.path.exists(full_path):
os.mkdir(full_path)
self.directories_created.append(subpath)
def create_link(self):
@@ -246,6 +253,10 @@
# if not os.path.isdir(base_path) and not os.path.exists(base_path):
# os.makedirs(base_path)
# self.directories_created = True
# Validate again that link_path is within allowed base directory
base_dir_norm = os.path.normpath(self.base_directory)
if not self.link_path.startswith(base_dir_norm + os.sep):
raise forms.ValidationError('Symlink path escapes the share directory.')
os.symlink(self.cleaned_data['target'], self.link_path)
return self.link_path

Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
subpath = os.sep.join(path_components[:i + 1])
full_path = os.path.join(self.base_directory, subpath)
if not os.path.isdir(full_path) and not os.path.exists(base_path):
os.mkdir(full_path)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix the issue, ensure that all paths created under create_subdirectories are guaranteed to remain within the expected root directory (self.base_directory). After joining and normalizing the path, add a check so that the normalized absolute path starts with the absolute version of self.base_directory. If the check fails, raise a validation error or skip directory creation.

Specifically:

  • In create_subdirectories, after computing full_path, normalize and make it absolute, then check it starts with the absolute base directory path followed by a path separator ("os.path.join(self.base_directory, '')" or using "os.path.commonpath").
  • If the check fails, raise a ValidationError or Exception.
  • Only proceed to create the directory if the path is valid.

You only need to edit the code in bioshareX/forms.py within the region implementing SymlinkForm.create_subdirectories.


Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -236,8 +236,13 @@
             for i in range(len(path_components)):
                 subpath = os.sep.join(path_components[:i + 1])
                 full_path = os.path.join(self.base_directory, subpath)
-                if not os.path.isdir(full_path) and not os.path.exists(base_path):
-                    os.mkdir(full_path)
+                # Ensure the directory to be created is within base_directory
+                normalized_full_path = os.path.realpath(full_path)
+                base_directory_real = os.path.realpath(self.base_directory)
+                if not normalized_full_path.startswith(base_directory_real + os.sep) and normalized_full_path != base_directory_real:
+                    raise forms.ValidationError('Attempted directory creation outside of base directory.')
+                if not os.path.isdir(full_path) and not os.path.exists(full_path):
+                    os.mkdir(normalized_full_path)
                     self.directories_created.append(subpath)
     def create_link(self):
         self.create_subdirectories()
EOF
@@ -236,8 +236,13 @@
for i in range(len(path_components)):
subpath = os.sep.join(path_components[:i + 1])
full_path = os.path.join(self.base_directory, subpath)
if not os.path.isdir(full_path) and not os.path.exists(base_path):
os.mkdir(full_path)
# Ensure the directory to be created is within base_directory
normalized_full_path = os.path.realpath(full_path)
base_directory_real = os.path.realpath(self.base_directory)
if not normalized_full_path.startswith(base_directory_real + os.sep) and normalized_full_path != base_directory_real:
raise forms.ValidationError('Attempted directory creation outside of base directory.')
if not os.path.isdir(full_path) and not os.path.exists(full_path):
os.mkdir(normalized_full_path)
self.directories_created.append(subpath)
def create_link(self):
self.create_subdirectories()
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread bioshareX/forms.py
# if not os.path.isdir(base_path) and not os.path.exists(base_path):
# os.makedirs(base_path)
# self.directories_created = True
os.symlink(self.cleaned_data['target'], self.link_path)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To resolve the vulnerability, the target path should be validated before being used to create a symlink. At minimum, ensure it is not an absolute path and does not contain any path traversal (..) elements. The recommended strategy is to normalize the user-supplied path using os.path.normpath, reject absolute paths, and, if you wish to limit symlinking to contents in a particular directory, verify that the final target path resides inside a trusted base directory.

Implementation steps:

  • In the clean_target method, add checks to normalize the target path, refuse any absolute paths, and refuse path traversals.
  • Optionally, check that the target exists or meets any further business rules.
  • Raise a forms.ValidationError if validation fails.
  • No need for new imports since only the standard os.path functions are used.

All edits are within bioshareX/forms.py, specifically inside the SymlinkForm class.


Suggested changeset 1
bioshareX/forms.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bioshareX/forms.py b/bioshareX/forms.py
--- a/bioshareX/forms.py
+++ b/bioshareX/forms.py
@@ -178,6 +178,21 @@
     name = forms.RegexField(regex=r'^[\w\d\ \-_\/]+$',error_messages={'invalid':'Illegal character in folder name'})
     target = forms.CharField()
     def clean_target(self):
+        target = self.cleaned_data['target']
+        # Normalize the target path
+        normalized_target = os.path.normpath(target)
+        # Prevent absolute paths
+        if os.path.isabs(normalized_target):
+            raise forms.ValidationError('Illegal absolute symlink target path (must be relative).')
+        # Prevent path traversal
+        if normalized_target.startswith('..') or '..' in normalized_target.split(os.sep):
+            raise forms.ValidationError('Illegal path traversal in symlink target.')
+        # Optionally, further check: ensure the target exists, or complies with share restrictions
+        # base_directory = self.share.get_path() if not self.subdir else os.path.join(self.share.get_path(), self.subdir)
+        # target_path = os.path.normpath(os.path.join(base_directory, normalized_target))
+        # if not target_path.startswith(base_directory):
+        #     raise forms.ValidationError('Symlink target must be within the allowed directory.')
+        return normalized_target
         path = self.cleaned_data['target']
         file_paths = [fp.path for fp in self.file_paths]
         if not self.user.can_link:
@@ -246,7 +261,7 @@
         # if not os.path.isdir(base_path) and not os.path.exists(base_path):
         #     os.makedirs(base_path)
         #     self.directories_created = True
-        os.symlink(self.cleaned_data['target'], self.link_path)
+        os.symlink(self.cleaned_data['target'], self.link_path)  # self.cleaned_data['target'] is already normalized & validated
         return self.link_path
 
 
EOF
@@ -178,6 +178,21 @@
name = forms.RegexField(regex=r'^[\w\d\ \-_\/]+$',error_messages={'invalid':'Illegal character in folder name'})
target = forms.CharField()
def clean_target(self):
target = self.cleaned_data['target']
# Normalize the target path
normalized_target = os.path.normpath(target)
# Prevent absolute paths
if os.path.isabs(normalized_target):
raise forms.ValidationError('Illegal absolute symlink target path (must be relative).')
# Prevent path traversal
if normalized_target.startswith('..') or '..' in normalized_target.split(os.sep):
raise forms.ValidationError('Illegal path traversal in symlink target.')
# Optionally, further check: ensure the target exists, or complies with share restrictions
# base_directory = self.share.get_path() if not self.subdir else os.path.join(self.share.get_path(), self.subdir)
# target_path = os.path.normpath(os.path.join(base_directory, normalized_target))
# if not target_path.startswith(base_directory):
# raise forms.ValidationError('Symlink target must be within the allowed directory.')
return normalized_target
path = self.cleaned_data['target']
file_paths = [fp.path for fp in self.file_paths]
if not self.user.can_link:
@@ -246,7 +261,7 @@
# if not os.path.isdir(base_path) and not os.path.exists(base_path):
# os.makedirs(base_path)
# self.directories_created = True
os.symlink(self.cleaned_data['target'], self.link_path)
os.symlink(self.cleaned_data['target'], self.link_path) # self.cleaned_data['target'] is already normalized & validated
return self.link_path


Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants