-
Notifications
You must be signed in to change notification settings - Fork 0
Dev #1
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
base: main
Are you sure you want to change the base?
Dev #1
Conversation
impro0 merge
improv0 merge 2
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
user-provided value
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified lines R225-R233 -
Copy modified line R235 -
Copy modified line R239
| @@ -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", |
| 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
user-provided value
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified lines R227-R233
| @@ -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( | ||
| { |
| """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
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
user-provided value
| { | ||
| "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
Stack trace information
Show autofix suggestion
Hide autofix suggestion
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 withexc_info=Truefor full stack trace). - In
process_tool, any usage of values inresult["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.
-
Copy modified lines R118-R120 -
Copy modified lines R123-R124 -
Copy modified lines R140-R141
| @@ -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): |
| ) | ||
| else: | ||
| return JsonResponse( | ||
| {"success": False, "error": result["error"]}, status=500 |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Stack trace information
Stack trace information
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified line R119 -
Copy modified line R143
| @@ -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): |
|
|
||
| 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
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified line R124
| @@ -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): |
| }; | ||
|
|
||
| if (toolConfigs[toolId]) { | ||
| toolConfigs[toolId](); |
Check failure
Code scanning / CodeQL
Unvalidated dynamic method call High
user-controlled
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified lines R117-R120
| @@ -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}`); |
| }; | ||
|
|
||
| if (setupFunctions[toolId]) { | ||
| setupFunctions[toolId](); |
Check failure
Code scanning / CodeQL
Unvalidated dynamic method call High
user-controlled
Show autofix suggestion
Hide autofix suggestion
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
toolIdis an own property ofsetupFunctionsusingObject.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.
-
Copy modified lines R150-R153
| @@ -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](); | ||
| } | ||
| } |
|
|
||
| // Execute the initialization function for the current tool | ||
| if (initializationFunctions[toolId]) { | ||
| initializationFunctions[toolId](); |
Check failure
Code scanning / CodeQL
Unvalidated dynamic method call High
user-controlled
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified lines R150-R153 -
Copy modified lines R345-R348
| @@ -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](); | ||
| } | ||
|
|
| 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
DOM text
DOM text
Show autofix suggestion
Hide autofix suggestion
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
createFileItemto escapefile.namebefore inclusion.
-
Copy modified line R83 -
Copy modified lines R96-R106
| @@ -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, '&') | ||
| .replace(/</g, '<') | ||
| .replace(/>/g, '>') | ||
| .replace(/"/g, '"') | ||
| .replace(/'/g, ''') | ||
| .replace(/`/g, '`'); | ||
| } | ||
|
|
||
| removeFile(dropZoneId, index) { | ||
| const fileInput = document.querySelector( | ||
| `#${dropZoneId.replace("-drop-zone", "-file-input")}`, |
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>
| 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
DOM text
Show autofix suggestion
Hide autofix suggestion
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 itstextContent(notinnerHTML), and assemble the other parts (icons, file size) likewise. - Avoid inserting any values from
file.nameorfile.sizevia.innerHTMLor equivalent unsanitized interpolation. - Only use
innerHTMLfor fixed markup, or not at all.
Implementation plan:
- Replace the block where
fileItem.innerHTMLis 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.
-
Copy modified lines R39-R58
| @@ -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); | ||
| }); | ||
| } |
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>
sync main and dev