Skip to content

Conversation

@skye-cyber
Copy link
Owner

sync main and dev

Skye and others added 5 commits April 21, 2024 19:20
Signed-off-by: Skye Wambua <swskye17@gmail.com>
download_urls = []

for file_path in cli_args[1:]: # Skip the command argument
if os.path.isfile(file_path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

The vulnerability arises because untrusted user input (via form data or other means) can supply file paths in cli_args, some of which may not have been validated or sanitized. While uploaded filenames are vetted and restricted to a specific temp directory, form data could also provide paths that are concatenated into cli_args, especially for certain tools or extended usage.

General fix:
For every file path extracted from cli_args for local access (such as with os.path.isfile in execute_cli_command), ensure the path is:

  • Normalized,
  • Confirmed to reside within the designated temp_dir (or other root directory).

Best fix:
In execute_cli_command, when iterating over file paths (line 224), normalize each path and check that it is strictly within temp_dir before accessing it. If a file path does not meet this criterion, skip it or raise an appropriate error. Also, generate output files only in temp_dir, never next to the original if it could be elsewhere.

Files/regions to change:
Only modify fweb/core/views.py, specifically in the method execute_cli_command, lines around 224-225.
Add path normalization and containment checks, ensuring that file access is only within temp_dir.

Needed imports/methods:
You may need to use os.path.normpath, os.path.abspath, and retain os as imported already.


Suggested changeset 1
fweb/core/views.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/fweb/core/views.py b/fweb/core/views.py
--- a/fweb/core/views.py
+++ b/fweb/core/views.py
@@ -222,15 +222,21 @@
         download_urls = []
 
         for file_path in cli_args[1:]:  # Skip the command argument
-            if os.path.isfile(file_path):
-                # Create mock output file
-                output_file = file_path + ".converted"
+            # Normalize the file path and ensure it's within temp_dir
+            abs_temp_dir = os.path.abspath(temp_dir)
+            norm_file_path = os.path.normpath(os.path.abspath(file_path))
+            if not norm_file_path.startswith(abs_temp_dir + os.sep):
+                logger.warning(f"Skipped unsafe path: {file_path}")
+                continue
+            if os.path.isfile(norm_file_path):
+                # Create mock output file (always in temp_dir)
+                output_file = os.path.join(abs_temp_dir, os.path.basename(norm_file_path) + ".converted")
                 with open(output_file, "w") as f:
-                    f.write(f"Converted version of {file_path}")
+                    f.write(f"Converted version of {norm_file_path}")
 
                 results.append(
                     {
-                        "original_name": os.path.basename(file_path),
+                        "original_name": os.path.basename(norm_file_path),
                         "converted_name": os.path.basename(output_file),
                         "status": "success",
                         "size": "1.2 MB",
EOF
@@ -222,15 +222,21 @@
download_urls = []

for file_path in cli_args[1:]: # Skip the command argument
if os.path.isfile(file_path):
# Create mock output file
output_file = file_path + ".converted"
# Normalize the file path and ensure it's within temp_dir
abs_temp_dir = os.path.abspath(temp_dir)
norm_file_path = os.path.normpath(os.path.abspath(file_path))
if not norm_file_path.startswith(abs_temp_dir + os.sep):
logger.warning(f"Skipped unsafe path: {file_path}")
continue
if os.path.isfile(norm_file_path):
# Create mock output file (always in temp_dir)
output_file = os.path.join(abs_temp_dir, os.path.basename(norm_file_path) + ".converted")
with open(output_file, "w") as f:
f.write(f"Converted version of {file_path}")
f.write(f"Converted version of {norm_file_path}")

results.append(
{
"original_name": os.path.basename(file_path),
"original_name": os.path.basename(norm_file_path),
"converted_name": os.path.basename(output_file),
"status": "success",
"size": "1.2 MB",
Copilot is powered by AI and may make mistakes. Always verify output.
if os.path.isfile(file_path):
# Create mock output file
output_file = file_path + ".converted"
with open(output_file, "w") as f:

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

The best way to fix this problem is to validate the output file path before writing to it, ensuring that it resides within the temporary processing directory (temp_dir). This can be done by normalizing the path (with os.path.normpath) and checking that it starts with the absolute path of temp_dir plus a path separator (to prevent prefix attacks). Specifically, before opening output_file for writing, verify that it is contained within temp_dir. If the check fails, skip file writing and optionally log a warning or error. This fix can be made within the execute_cli_command function, right before line 228.

Suggested changeset 1
fweb/core/views.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/fweb/core/views.py b/fweb/core/views.py
--- a/fweb/core/views.py
+++ b/fweb/core/views.py
@@ -224,9 +224,13 @@
         for file_path in cli_args[1:]:  # Skip the command argument
             if os.path.isfile(file_path):
                 # Create mock output file
-                output_file = file_path + ".converted"
-                with open(output_file, "w") as f:
-                    f.write(f"Converted version of {file_path}")
+                output_file = os.path.normpath(file_path + ".converted")
+                # Ensure output_file is strictly within temp_dir
+                if output_file.startswith(os.path.abspath(temp_dir) + os.sep):
+                    with open(output_file, "w") as f:
+                        f.write(f"Converted version of {file_path}")
+                else:
+                    logger.warning(f"Attempted to write output file outside of temp_dir: {output_file}")
 
                 results.append(
                     {
EOF
@@ -224,9 +224,13 @@
for file_path in cli_args[1:]: # Skip the command argument
if os.path.isfile(file_path):
# Create mock output file
output_file = file_path + ".converted"
with open(output_file, "w") as f:
f.write(f"Converted version of {file_path}")
output_file = os.path.normpath(file_path + ".converted")
# Ensure output_file is strictly within temp_dir
if output_file.startswith(os.path.abspath(temp_dir) + os.sep):
with open(output_file, "w") as f:
f.write(f"Converted version of {file_path}")
else:
logger.warning(f"Attempted to write output file outside of temp_dir: {output_file}")

results.append(
{
Copilot is powered by AI and may make mistakes. Always verify output.
"""Serve processed files for download"""
file_path = os.path.join(settings.MEDIA_ROOT, "processed", filename)

if os.path.exists(file_path):

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
file_path = os.path.join(settings.MEDIA_ROOT, "processed", filename)

if os.path.exists(file_path):
with open(file_path, "rb") as f:

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
Comment on lines +106 to +111
{
"success": True,
"message": result["message"],
"results": result.get("results", []),
"download_urls": result.get("download_urls", []),
}

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 2 months ago

To fix the problem, all instances where exception messages are returned directly to the user must be replaced with a generic message, while logging the actual exception (and stack trace) on the server for debugging purposes. Specifically:

  • In process_with_cli, replace {"success": False, "error": str(e)} with a generic message (e.g., "error": "CLI processing failed."), while logging the full exception (optionally with exc_info=True for full stack trace).
  • In process_tool, any usage of values in result["error"] (which may contain an exception message) should not be exposed directly to the user; replace it with a generic message (e.g., "An internal error has occurred.").
  • Always ensure logging retains as much information as needed to debug.

No change is required for successful operations (result["message"] et al) since these come from normal, non-exceptional flow.
No new imports are needed, as logging is already imported.
Edits should be made in fweb/core/views.py in both process_tool and process_with_cli as described.


Suggested changeset 1
fweb/core/views.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/fweb/core/views.py b/fweb/core/views.py
--- a/fweb/core/views.py
+++ b/fweb/core/views.py
@@ -115,13 +115,13 @@
                     }
                 )
             else:
-                return JsonResponse(
-                    {"success": False, "error": result["error"]}, status=500
-                )
+                # Avoid exposing internal error details; log error and return generic message
+                logger.error(f"Tool processing failed: {result.get('error', 'Unknown error')}")
+                return JsonResponse({"success": False, "error": "An internal error has occurred."}, status=500)
 
     except Exception as e:
-        logger.error(f"Error processing tool {tool_id}: {str(e)}")
-        return JsonResponse({"error": str(e)}, status=500)
+        logger.error(f"Error processing tool {tool_id}: {str(e)}", exc_info=True)
+        return JsonResponse({"error": "An internal error has occurred."}, status=500)
 
 
 def process_with_cli(tool_id, file_paths, form_data, temp_dir):
@@ -139,8 +137,8 @@
         return result
 
     except Exception as e:
-        logger.error(f"CLI processing error: {str(e)}")
-        return {"success": False, "error": str(e)}
+        logger.error(f"CLI processing error: {str(e)}", exc_info=True)
+        return {"success": False, "error": "CLI processing failed."}
 
 
 def map_tool_to_cli_args(tool_id, file_paths, form_data):
EOF
@@ -115,13 +115,13 @@
}
)
else:
return JsonResponse(
{"success": False, "error": result["error"]}, status=500
)
# Avoid exposing internal error details; log error and return generic message
logger.error(f"Tool processing failed: {result.get('error', 'Unknown error')}")
return JsonResponse({"success": False, "error": "An internal error has occurred."}, status=500)

except Exception as e:
logger.error(f"Error processing tool {tool_id}: {str(e)}")
return JsonResponse({"error": str(e)}, status=500)
logger.error(f"Error processing tool {tool_id}: {str(e)}", exc_info=True)
return JsonResponse({"error": "An internal error has occurred."}, status=500)


def process_with_cli(tool_id, file_paths, form_data, temp_dir):
@@ -139,8 +137,8 @@
return result

except Exception as e:
logger.error(f"CLI processing error: {str(e)}")
return {"success": False, "error": str(e)}
logger.error(f"CLI processing error: {str(e)}", exc_info=True)
return {"success": False, "error": "CLI processing failed."}


def map_tool_to_cli_args(tool_id, file_paths, form_data):
Copilot is powered by AI and may make mistakes. Always verify output.
)
else:
return JsonResponse(
{"success": False, "error": result["error"]}, status=500

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 2 months ago

To resolve this issue, generic error messages rather than raw exception messages should be sent to users. All exception messages and stack traces should be logged server-side for debugging/auditing purposes using the available logger (logger.error(...)), but the API should respond only with a generic error message. This applies to both process_tool and its helper process_with_cli, which currently propagate exception details in the response.

We should:

  • Replace {"success": False, "error": result["error"]} (and similar) with a generic error message to the user, such as {"success": False, "error": "An internal error has occurred."}
  • Log detailed errors and exceptions server-side.
  • If there is legitimate user-facing error information (e.g., validation errors), separate those from internal errors.

Edits are needed at:

  • line 119 (and possibly 143 if a web-facing message ever directly propagates from process_with_cli to the client)

No new imports are needed because logging is already set up.


Suggested changeset 1
fweb/core/views.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/fweb/core/views.py b/fweb/core/views.py
--- a/fweb/core/views.py
+++ b/fweb/core/views.py
@@ -116,7 +116,7 @@
                 )
             else:
                 return JsonResponse(
-                    {"success": False, "error": result["error"]}, status=500
+                    {"success": False, "error": "An internal error has occurred."}, status=500
                 )
 
     except Exception as e:
@@ -140,7 +140,7 @@
 
     except Exception as e:
         logger.error(f"CLI processing error: {str(e)}")
-        return {"success": False, "error": str(e)}
+        return {"success": False, "error": "An internal error has occurred."}
 
 
 def map_tool_to_cli_args(tool_id, file_paths, form_data):
EOF
@@ -116,7 +116,7 @@
)
else:
return JsonResponse(
{"success": False, "error": result["error"]}, status=500
{"success": False, "error": "An internal error has occurred."}, status=500
)

except Exception as e:
@@ -140,7 +140,7 @@

except Exception as e:
logger.error(f"CLI processing error: {str(e)}")
return {"success": False, "error": str(e)}
return {"success": False, "error": "An internal error has occurred."}


def map_tool_to_cli_args(tool_id, file_paths, form_data):
Copilot is powered by AI and may make mistakes. Always verify output.

except Exception as e:
logger.error(f"Error processing tool {tool_id}: {str(e)}")
return JsonResponse({"error": str(e)}, status=500)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 2 months ago

To fix this problem, we need to replace the user-facing error message in the exception handler (in process_tool) to return a generic message, rather than exposing the exception string. The server-side logging should retain the detailed exception for debugging. Only the JsonResponse on line 124 needs to change: remove str(e) from the response body and substitute a generic error message such as "An internal error occurred.". No other functional logic needs changing. No new imports or dependencies are needed because Django logging and JsonResponse are already present.


Suggested changeset 1
fweb/core/views.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/fweb/core/views.py b/fweb/core/views.py
--- a/fweb/core/views.py
+++ b/fweb/core/views.py
@@ -121,7 +121,7 @@
 
     except Exception as e:
         logger.error(f"Error processing tool {tool_id}: {str(e)}")
-        return JsonResponse({"error": str(e)}, status=500)
+        return JsonResponse({"error": "An internal error has occurred."}, status=500)
 
 
 def process_with_cli(tool_id, file_paths, form_data, temp_dir):
EOF
@@ -121,7 +121,7 @@

except Exception as e:
logger.error(f"Error processing tool {tool_id}: {str(e)}")
return JsonResponse({"error": str(e)}, status=500)
return JsonResponse({"error": "An internal error has occurred."}, status=500)


def process_with_cli(tool_id, file_paths, form_data, temp_dir):
Copilot is powered by AI and may make mistakes. Always verify output.
};

if (toolConfigs[toolId]) {
toolConfigs[toolId]();

Check failure

Code scanning / CodeQL

Unvalidated dynamic method call High

Invocation of method with
user-controlled
name may dispatch to unexpected target and cause an exception.

Copilot Autofix

AI 2 months ago

To fix this issue, we should ensure that the value looked up in toolConfigs is both an own property (not inherited from the prototype) and actually a function before calling it. This is best done by replacing if (toolConfigs[toolId]) with an explicit hasOwnProperty check and verifying the type of the property. The fix should occur in the initializeTool method, in file fweb/core/static/js/ToolsHandler.js, specifically lines 117-121. No external imports are needed since hasOwnProperty and typeof are both built-in JavaScript operations.

Suggested changeset 1
fweb/core/static/js/ToolsHandler.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/fweb/core/static/js/ToolsHandler.js b/fweb/core/static/js/ToolsHandler.js
--- a/fweb/core/static/js/ToolsHandler.js
+++ b/fweb/core/static/js/ToolsHandler.js
@@ -114,7 +114,10 @@
       bulk_ocr: () => this.initBulkOCR(),
     };
 
-    if (toolConfigs[toolId]) {
+    if (
+      Object.prototype.hasOwnProperty.call(toolConfigs, toolId) &&
+      typeof toolConfigs[toolId] === "function"
+    ) {
       toolConfigs[toolId]();
     } else {
       console.warn(`No initialization found for tool: ${toolId}`);
EOF
@@ -114,7 +114,10 @@
bulk_ocr: () => this.initBulkOCR(),
};

if (toolConfigs[toolId]) {
if (
Object.prototype.hasOwnProperty.call(toolConfigs, toolId) &&
typeof toolConfigs[toolId] === "function"
) {
toolConfigs[toolId]();
} else {
console.warn(`No initialization found for tool: ${toolId}`);
Copilot is powered by AI and may make mistakes. Always verify output.
};

if (setupFunctions[toolId]) {
setupFunctions[toolId]();

Check failure

Code scanning / CodeQL

Unvalidated dynamic method call High

Invocation of method with
user-controlled
name may dispatch to unexpected target and cause an exception.

Copilot Autofix

AI 2 months ago

To safely perform a dynamic method call based on user input, we should strictly validate that the requested method name is both an own property of the methods dictionary (not inherited from a prototype) and that the mapped value is a function. The most direct way to fix the issue in initializeTool(toolId) is to replace the current if (setupFunctions[toolId]) check with:

  • First, check that toolId is an own property of setupFunctions using Object.prototype.hasOwnProperty.call(setupFunctions, toolId)
  • Then, check that setupFunctions[toolId] is of type "function".

Only if both conditions are true should the code invoke setupFunctions[toolId](). Otherwise, do nothing, or optionally show an error if needed.

You only need to change the relevant check and invocation in the initializeTool(toolId) function. No imports or extra definitions are needed; just use plain JS.

Suggested changeset 1
fweb/core/static/js/utils.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/fweb/core/static/js/utils.js b/fweb/core/static/js/utils.js
--- a/fweb/core/static/js/utils.js
+++ b/fweb/core/static/js/utils.js
@@ -147,7 +147,10 @@
       setupFileDropZone("bulk-ocr-drop-zone", "bulk-ocr-file-input", true),
   };
 
-  if (setupFunctions[toolId]) {
+  if (
+    Object.prototype.hasOwnProperty.call(setupFunctions, toolId) &&
+    typeof setupFunctions[toolId] === "function"
+  ) {
     setupFunctions[toolId]();
   }
 }
EOF
@@ -147,7 +147,10 @@
setupFileDropZone("bulk-ocr-drop-zone", "bulk-ocr-file-input", true),
};

if (setupFunctions[toolId]) {
if (
Object.prototype.hasOwnProperty.call(setupFunctions, toolId) &&
typeof setupFunctions[toolId] === "function"
) {
setupFunctions[toolId]();
}
}
Copilot is powered by AI and may make mistakes. Always verify output.

// Execute the initialization function for the current tool
if (initializationFunctions[toolId]) {
initializationFunctions[toolId]();

Check failure

Code scanning / CodeQL

Unvalidated dynamic method call High

Invocation of method with
user-controlled
name may dispatch to unexpected target and cause an exception.

Copilot Autofix

AI 2 months ago

To fix the problem, we must ensure that the user-controlled toolId is only allowed to select valid initialization functions and that the dynamic lookup cannot resolve to prototype methods or unintentional targets. The best way is to check that toolId is an own property of initializationFunctions (not inherited), and that the resolved property is a function before calling it. Ideally, we would use a Map, but since the code uses a plain object, a strict check via hasOwnProperty and typeof is sufficient. Specifically, in initializeTool (lines 342-344), replace:

if (initializationFunctions[toolId]) {
  initializationFunctions[toolId]();
}

with:

if (
  Object.prototype.hasOwnProperty.call(initializationFunctions, toolId) &&
  typeof initializationFunctions[toolId] === "function"
) {
  initializationFunctions[toolId]();
}

Apply the same logic to the earlier setupFunctions dispatch (lines 150-152). No new imports are needed as this uses built-in JS features.

Suggested changeset 1
fweb/core/static/js/utils.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/fweb/core/static/js/utils.js b/fweb/core/static/js/utils.js
--- a/fweb/core/static/js/utils.js
+++ b/fweb/core/static/js/utils.js
@@ -147,7 +147,10 @@
       setupFileDropZone("bulk-ocr-drop-zone", "bulk-ocr-file-input", true),
   };
 
-  if (setupFunctions[toolId]) {
+  if (
+    Object.prototype.hasOwnProperty.call(setupFunctions, toolId) &&
+    typeof setupFunctions[toolId] === "function"
+  ) {
     setupFunctions[toolId]();
   }
 }
@@ -339,7 +342,10 @@
   };
 
   // Execute the initialization function for the current tool
-  if (initializationFunctions[toolId]) {
+  if (
+    Object.prototype.hasOwnProperty.call(initializationFunctions, toolId) &&
+    typeof initializationFunctions[toolId] === "function"
+  ) {
     initializationFunctions[toolId]();
   }
 
EOF
@@ -147,7 +147,10 @@
setupFileDropZone("bulk-ocr-drop-zone", "bulk-ocr-file-input", true),
};

if (setupFunctions[toolId]) {
if (
Object.prototype.hasOwnProperty.call(setupFunctions, toolId) &&
typeof setupFunctions[toolId] === "function"
) {
setupFunctions[toolId]();
}
}
@@ -339,7 +342,10 @@
};

// Execute the initialization function for the current tool
if (initializationFunctions[toolId]) {
if (
Object.prototype.hasOwnProperty.call(initializationFunctions, toolId) &&
typeof initializationFunctions[toolId] === "function"
) {
initializationFunctions[toolId]();
}

Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +80 to +92
fileItem.innerHTML = `
<div class="flex items-center flex-1 min-w-0">
<i class="fas fa-file text-gray-400 mr-3 flex-shrink-0"></i>
<span class="text-sm font-medium truncate">${file.name}</span>
</div>
<div class="flex items-center space-x-3 flex-shrink-0">
<span class="text-xs text-gray-500 dark:text-gray-400">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
<button type="button" onclick="fileHandler.removeFile('${dropZoneId}', ${index})"
class="text-red-500 hover:text-red-700 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>
`;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 2 months ago

To fix this vulnerability, ensure that all variables that are interpolated into HTML via innerHTML are properly escaped, especially user-controllable values. In this case, escape file.name and (file.size / 1024 / 1024).toFixed(2) (even though the latter is always a number, safe input handling suggests escaping anyway for completeness). The best approach is to create a helper function (e.g., escapeHTML) that converts special HTML characters (&, <, >, ", ', `) into their corresponding HTML entities. Use this function to escape all interpolated user data (${file.name}, and for completeness, also ${dropZoneId} in an onclick, though it's less directly risky here because the index is always a number and the dropZoneId comes from your own application context).

Make these changes only within the provided snippet of fweb/core/static/js/FileHandler.js:

  • Add an escapeHTML(str) utility function to the class.
  • Change the construction of the HTML template in createFileItem to escape file.name before inclusion.

Suggested changeset 1
fweb/core/static/js/FileHandler.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/fweb/core/static/js/FileHandler.js b/fweb/core/static/js/FileHandler.js
--- a/fweb/core/static/js/FileHandler.js
+++ b/fweb/core/static/js/FileHandler.js
@@ -80,7 +80,7 @@
     fileItem.innerHTML = `
     <div class="flex items-center flex-1 min-w-0">
     <i class="fas fa-file text-gray-400 mr-3 flex-shrink-0"></i>
-    <span class="text-sm font-medium truncate">${file.name}</span>
+    <span class="text-sm font-medium truncate">${this.escapeHTML(file.name)}</span>
     </div>
     <div class="flex items-center space-x-3 flex-shrink-0">
     <span class="text-xs text-gray-500 dark:text-gray-400">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
@@ -93,6 +93,17 @@
     return fileItem;
   }
 
+  // Escape user-controlled string for safe HTML insertion
+  escapeHTML(str) {
+    return String(str)
+      .replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/"/g, '&quot;')
+      .replace(/'/g, '&#39;')
+      .replace(/`/g, '&#96;');
+  }
+
   removeFile(dropZoneId, index) {
     const fileInput = document.querySelector(
       `#${dropZoneId.replace("-drop-zone", "-file-input")}`,
EOF
@@ -80,7 +80,7 @@
fileItem.innerHTML = `
<div class="flex items-center flex-1 min-w-0">
<i class="fas fa-file text-gray-400 mr-3 flex-shrink-0"></i>
<span class="text-sm font-medium truncate">${file.name}</span>
<span class="text-sm font-medium truncate">${this.escapeHTML(file.name)}</span>
</div>
<div class="flex items-center space-x-3 flex-shrink-0">
<span class="text-xs text-gray-500 dark:text-gray-400">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
@@ -93,6 +93,17 @@
return fileItem;
}

// Escape user-controlled string for safe HTML insertion
escapeHTML(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/`/g, '&#96;');
}

removeFile(dropZoneId, index) {
const fileInput = document.querySelector(
`#${dropZoneId.replace("-drop-zone", "-file-input")}`,
Copilot is powered by AI and may make mistakes. Always verify output.
skye-cyber and others added 7 commits November 14, 2025 15:42
Signed-off-by: Skye Wambua <swskye17@gmail.com>
…as HTML

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…n path expression

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Comment on lines +39 to +45
fileItem.innerHTML = `
<div class="flex items-center">
<i class="fas fa-file text-gray-400 mr-2"></i>
<span class="text-sm truncate">${file.name}</span>
</div>
<span class="text-xs text-gray-500">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
`;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI about 2 months ago

How to fix the problem in general terms:
Any data taken from untrusted sources which is to be output into innerHTML (or equivalent sinks in the DOM) must be escaped, or, better, should be output using safe DOM methods. Instead of using .innerHTML and template strings containing dynamic user data, use text nodes to insert data (textContent or createTextNode). This prevents meta-characters from being interpreted as HTML.

Detailed fix for this case:

  • In function updateFileList, where the DOM for each file is being built (lines 39-45), switch to creating the structure with DOM methods.
  • Specifically, create a <span> element for the file name, set its textContent (not innerHTML), and assemble the other parts (icons, file size) likewise.
  • Avoid inserting any values from file.name or file.size via .innerHTML or equivalent unsanitized interpolation.
  • Only use innerHTML for fixed markup, or not at all.

Implementation plan:

  • Replace the block where fileItem.innerHTML is set with code that builds the markup using DOM manipulation (createElement).
  • Assemble and append nodes for the file icon, file name (as text), file size (as text).
  • Ensure all existing CSS classes and layout remain unchanged.

Required:
Only standard DOM API, no imports or external dependencies.

File/region to change:

  • fweb/static/js/FileHandler.js, lines 39–45.

Suggested changeset 1
fweb/static/js/FileHandler.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/fweb/static/js/FileHandler.js b/fweb/static/js/FileHandler.js
--- a/fweb/static/js/FileHandler.js
+++ b/fweb/static/js/FileHandler.js
@@ -36,13 +36,26 @@
       const fileItem = document.createElement("div");
       fileItem.className =
         "flex items-center justify-between p-2 bg-gray-50 rounded";
-      fileItem.innerHTML = `
-                        <div class="flex items-center">
-                            <i class="fas fa-file text-gray-400 mr-2"></i>
-                            <span class="text-sm truncate">${file.name}</span>
-                        </div>
-                        <span class="text-xs text-gray-500">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
-                    `;
+      // Build the file item structure using DOM methods for safety against XSS.
+      const fileInfoDiv = document.createElement("div");
+      fileInfoDiv.className = "flex items-center";
+
+      const fileIcon = document.createElement("i");
+      fileIcon.className = "fas fa-file text-gray-400 mr-2";
+
+      const fileNameSpan = document.createElement("span");
+      fileNameSpan.className = "text-sm truncate";
+      fileNameSpan.textContent = file.name;
+
+      fileInfoDiv.appendChild(fileIcon);
+      fileInfoDiv.appendChild(fileNameSpan);
+
+      const fileSizeSpan = document.createElement("span");
+      fileSizeSpan.className = "text-xs text-gray-500";
+      fileSizeSpan.textContent = `${(file.size / 1024 / 1024).toFixed(2)} MB`;
+
+      fileItem.appendChild(fileInfoDiv);
+      fileItem.appendChild(fileSizeSpan);
       fileList.appendChild(fileItem);
     });
   }
EOF
@@ -36,13 +36,26 @@
const fileItem = document.createElement("div");
fileItem.className =
"flex items-center justify-between p-2 bg-gray-50 rounded";
fileItem.innerHTML = `
<div class="flex items-center">
<i class="fas fa-file text-gray-400 mr-2"></i>
<span class="text-sm truncate">${file.name}</span>
</div>
<span class="text-xs text-gray-500">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
`;
// Build the file item structure using DOM methods for safety against XSS.
const fileInfoDiv = document.createElement("div");
fileInfoDiv.className = "flex items-center";

const fileIcon = document.createElement("i");
fileIcon.className = "fas fa-file text-gray-400 mr-2";

const fileNameSpan = document.createElement("span");
fileNameSpan.className = "text-sm truncate";
fileNameSpan.textContent = file.name;

fileInfoDiv.appendChild(fileIcon);
fileInfoDiv.appendChild(fileNameSpan);

const fileSizeSpan = document.createElement("span");
fileSizeSpan.className = "text-xs text-gray-500";
fileSizeSpan.textContent = `${(file.size / 1024 / 1024).toFixed(2)} MB`;

fileItem.appendChild(fileInfoDiv);
fileItem.appendChild(fileSizeSpan);
fileList.appendChild(fileItem);
});
}
Copilot is powered by AI and may make mistakes. Always verify output.
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
rename cli file from main to cli
sync setup to changes

Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
allow file chaining for major type conversions

Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
Signed-off-by: Skye Wambua <swskye17@gmail.com>
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