Conversation
This workflow file sets up CodeQL analysis for multiple languages on push and pull request events, as well as on a schedule.
This workflow automatically applies labels to pull requests based on modified paths.
This workflow installs Python dependencies, runs linting with flake8, and executes tests with pytest.
The script scans the repository for projects containing a README.md file and collects their details, including an optional icon file. The project information is then saved to a JSON file.
This script concatenates all README.md files from the repository into a single GLOBAL.md file, ignoring hidden directories and the .git folder.
Added installation of LaTeX engine for PDF conversion.
Updated the Python GitHub Actions workflow to support multiple Python versions and added pytest-cov for coverage reporting.
|
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. |
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
|
|
||
| filepath = os.path.join(app.config["UPLOAD_FOLDER"], file.filename) | ||
| try: | ||
| file.save(filepath) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, the fix is to ensure that any path used to save user-uploaded files does not allow for directory traversal or writing outside a safe upload directory. The industry standard for Flask apps is to use werkzeug.utils.secure_filename to sanitize user filenames, ensuring they are plain filenames without directory information or dangerous characters.
The single best way to fix this issue without changing existing functionality is:
- Before saving the uploaded file, use
secure_filenameto sanitizefile.filename. - Construct the path using only the sanitized filename.
- Optionally, as a further guard, normalize the resulting path and ensure it remains within the upload directory.
Required changes:
- Add an import:
from werkzeug.utils import secure_filename - Before calling
os.path.join, sanitizefile.filenamewithsecure_filename. - Use the sanitized version in all subsequent path uses.
All code changes are inside the shown code in unidad_3/Flask/app.py: the upload route and the import region.
| @@ -12,6 +12,7 @@ | ||
| redirect, url_for, flash, | ||
| session, jsonify, send_file | ||
| ) | ||
| from werkzeug.utils import secure_filename | ||
| from fpdf import FPDF | ||
| import pandas as pd | ||
| from pandas.errors import EmptyDataError, ParserError | ||
| @@ -103,7 +104,8 @@ | ||
| # Algunos navegadores usan tipos genéricos al subir .csv | ||
| print(f"Advertencia: content_type no estándar: {file.content_type}") | ||
|
|
||
| filepath = os.path.join(app.config["UPLOAD_FOLDER"], file.filename) | ||
| safe_filename = secure_filename(file.filename) | ||
| filepath = os.path.join(app.config["UPLOAD_FOLDER"], safe_filename) | ||
| try: | ||
| file.save(filepath) | ||
| data = pd.read_csv(filepath) |
| except Exception as e: | ||
| flash(f"Ocurrió un error inesperado al procesar el archivo: {e}", "error") | ||
| # limpiar archivo fallido | ||
| if os.path.exists(filepath): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To securely handle user file uploads, avoid using the raw filename provided by the user. Instead, use a function such as werkzeug.utils.secure_filename to strip dangerous characters (slashes, dots, etc) and prevent traversal attacks. Additionally, after joining the filename to the upload folder path, normalize the path and ensure it's still contained within the intended root directory. This guards against any potential bypasses that secure_filename may miss.
The fix will require:
- Importing
secure_filenamefromwerkzeug.utils. - Applying
secure_filenametofile.filenamebefore using it to constructfilepath. - Normalizing the result with
os.path.normpathand checking it starts with the upload directory. - Handling the case where a filename becomes empty or invalid after sanitization.
Changes should be made in the upload_csv function where filepath is constructed and where it is deleted in error-handling. The imports need to include secure_filename.
| @@ -12,6 +12,7 @@ | ||
| redirect, url_for, flash, | ||
| session, jsonify, send_file | ||
| ) | ||
| from werkzeug.utils import secure_filename | ||
| from fpdf import FPDF | ||
| import pandas as pd | ||
| from pandas.errors import EmptyDataError, ParserError | ||
| @@ -103,10 +104,20 @@ | ||
| # Algunos navegadores usan tipos genéricos al subir .csv | ||
| print(f"Advertencia: content_type no estándar: {file.content_type}") | ||
|
|
||
| filepath = os.path.join(app.config["UPLOAD_FOLDER"], file.filename) | ||
| safe_filename = secure_filename(file.filename) | ||
| if not safe_filename: | ||
| flash("El nombre del archivo no es válido después de la sanitización.", "error") | ||
| return redirect(request.url) | ||
| filepath = os.path.join(app.config["UPLOAD_FOLDER"], safe_filename) | ||
| # Normalizar el path y asegurar que siga dentro del upload folder | ||
| normalized_filepath = os.path.normpath(filepath) | ||
| upload_folder_abs = os.path.abspath(app.config["UPLOAD_FOLDER"]) | ||
| if not normalized_filepath.startswith(upload_folder_abs): | ||
| flash("Ruta de archivo no permitida.", "error") | ||
| return redirect(request.url) | ||
| try: | ||
| file.save(filepath) | ||
| data = pd.read_csv(filepath) | ||
| file.save(normalized_filepath) | ||
| data = pd.read_csv(normalized_filepath) | ||
| data.fillna("", inplace=True) | ||
| session["productos"] = data.to_dict(orient="records") | ||
| session["csv_filepath"] = filepath | ||
| @@ -118,10 +128,10 @@ | ||
| except Exception as e: | ||
| flash(f"Ocurrió un error inesperado al procesar el archivo: {e}", "error") | ||
| # limpiar archivo fallido | ||
| if os.path.exists(filepath): | ||
| if os.path.exists(normalized_filepath): | ||
| try: | ||
| os.remove(filepath) | ||
| print(f"Archivo fallido eliminado: {filepath}") | ||
| os.remove(normalized_filepath) | ||
| print(f"Archivo fallido eliminado: {normalized_filepath}") | ||
| except OSError: | ||
| pass | ||
| return redirect(url_for("upload_csv")) |
| # limpiar archivo fallido | ||
| if os.path.exists(filepath): | ||
| try: | ||
| os.remove(filepath) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix this problem, we need to ensure that any user-provided filename is sanitized before being used to construct a path and interact with the filesystem. The most robust method in this context is to use Flask/Werkzeug's secure_filename function to clean the filename of special or dangerous characters, followed by ensuring that the final path remains within the intended upload directory, even after joining and normalization.
Specifically, in each place where file.filename is used to construct a file path, replace file.filename with its sanitized version from werkzeug.utils.secure_filename, and after joining with UPLOAD_FOLDER, normalize the path with os.path.normpath, then verify that it starts with the expected base upload directory. This protects against both path traversal and absolute path attacks.
This change should be applied in all code regions where file.filename is used to create file paths, namely in the upload_csv function and also anywhere else in the same file where file.filename is used for file operations.
We need to:
- Import
secure_filenamefromwerkzeug.utils. - In the
upload_csvfunction, sanitize file.filename to obtain a safe name, join and normalize it, and verify it is inside the upload folder. - Use the sanitized path (
safe_filepath) in all subsequent file operations, including save,pd.read_csv, session assignment, and deletion.
| @@ -12,6 +12,7 @@ | ||
| redirect, url_for, flash, | ||
| session, jsonify, send_file | ||
| ) | ||
| from werkzeug.utils import secure_filename | ||
| from fpdf import FPDF | ||
| import pandas as pd | ||
| from pandas.errors import EmptyDataError, ParserError | ||
| @@ -103,13 +104,18 @@ | ||
| # Algunos navegadores usan tipos genéricos al subir .csv | ||
| print(f"Advertencia: content_type no estándar: {file.content_type}") | ||
|
|
||
| filepath = os.path.join(app.config["UPLOAD_FOLDER"], file.filename) | ||
| safe_filename = secure_filename(file.filename) | ||
| safe_filepath = os.path.normpath(os.path.join(app.config["UPLOAD_FOLDER"], safe_filename)) | ||
| base_upload_folder = os.path.abspath(app.config["UPLOAD_FOLDER"]) | ||
| if not os.path.commonprefix([os.path.abspath(safe_filepath), base_upload_folder]) == base_upload_folder: | ||
| flash("Nombre de archivo no permitido.", "error") | ||
| return redirect(request.url) | ||
| try: | ||
| file.save(filepath) | ||
| data = pd.read_csv(filepath) | ||
| file.save(safe_filepath) | ||
| data = pd.read_csv(safe_filepath) | ||
| data.fillna("", inplace=True) | ||
| session["productos"] = data.to_dict(orient="records") | ||
| session["csv_filepath"] = filepath | ||
| session["csv_filepath"] = safe_filepath | ||
| flash("Archivo CSV cargado con éxito.") | ||
| except EmptyDataError: | ||
| flash("El archivo CSV está vacío.", "error") | ||
| @@ -118,10 +121,10 @@ | ||
| except Exception as e: | ||
| flash(f"Ocurrió un error inesperado al procesar el archivo: {e}", "error") | ||
| # limpiar archivo fallido | ||
| if os.path.exists(filepath): | ||
| if os.path.exists(safe_filepath): | ||
| try: | ||
| os.remove(filepath) | ||
| print(f"Archivo fallido eliminado: {filepath}") | ||
| os.remove(safe_filepath) | ||
| print(f"Archivo fallido eliminado: {safe_filepath}") | ||
| except OSError: | ||
| pass | ||
| return redirect(url_for("upload_csv")) |
| # Ejecución principal | ||
| # ============================== | ||
| if __name__ == "__main__": | ||
| app.run(debug=True, host="0.0.0.0") No newline at end of file |
Check failure
Code scanning / CodeQL
Flask app is run in debug mode High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix this vulnerability, remove the debug=True argument from the app.run() call so Flask does not start in debug mode. This prevents exposure of the debugger in any deployment, especially when binding to all interfaces. Only remove the debug argument: do not otherwise alter execution or behavior. No new methods or imports are required; just change the arguments to app.run() on line 481. The fix is in unidad_3/Flask/app.py, within the main execution block.
| @@ -478,4 +478,4 @@ | ||
| # Ejecución principal | ||
| # ============================== | ||
| if __name__ == "__main__": | ||
| app.run(debug=True, host="0.0.0.0") | ||
| app.run(host="0.0.0.0") |
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| # 1. Clonar el repositorio | ||
| - name: Checkout repo | ||
| uses: actions/checkout@v4 | ||
|
|
||
| # 2. Configurar Python (para scripts de documentación) | ||
| - name: Setup Python | ||
| uses: actions/setup-python@v4 | ||
| with: | ||
| python-version: '3.11' | ||
|
|
||
| # 3. Instalar dependencias necesarias | ||
| - name: Install dependencies | ||
| run: | | ||
| pip install markdown pandoc | ||
|
|
||
| # 4. Generar projects.json recorriendo carpetas y subcarpetas | ||
| - name: Generate projects.json | ||
| run: python scripts/scan_projects.py | ||
|
|
||
| # 5. Concatenar todos los README.md en un GLOBAL.md | ||
| - name: Generate GLOBAL.md | ||
| run: python scripts/concat_readmes.py | ||
|
|
||
| # 6. Convertir GLOBAL.md a PDF | ||
| - name: Install Pandoc | ||
| run: sudo apt-get update && sudo apt-get install -y pandoc | ||
| - name: Install LaTeX engine | ||
| run: sudo apt-get install -y texlive texlive-xetex texlive-fonts-recommended | ||
| - name: Convert to PDF | ||
| run: pandoc GLOBAL.md -o GLOBAL.pdf | ||
|
|
||
| # 7. Subir artefactos generados (JSON y PDF) | ||
| - name: Upload artifacts | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: portfolio-docs | ||
| path: | | ||
| projects.json | ||
| GLOBAL.pdf |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| python-version: ["3.8", "3.9", "3.10"] | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 # 1. Clonar el repositorio | ||
|
|
||
| - name: Set up Python ${{ matrix.python-version }} | ||
| uses: actions/setup-python@v3 | ||
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
|
|
||
| - name: Instalar dependencias, Pylint y Black | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
|
|
||
| # Buscar y instalar dependencias desde requisitos.txt | ||
| REQS=$(find . -name requisitos.txt) | ||
| if [ -n "$REQS" ]; then | ||
| echo "Se encontraron archivos de requisitos:" | ||
| echo "$REQS" | ||
| for req in $REQS; do | ||
| echo "Instalando dependencias desde $req..." | ||
| pip install -r "$req" | ||
| done | ||
| else | ||
| echo "No se encontraron archivos requisitos.txt. Omitiendo instalación de dependencias." | ||
| fi | ||
|
|
||
| # 2. Instalar Pylint y Black | ||
| pip install pylint black | ||
|
|
||
| - name: Ejecutar formateador Black | ||
| run: | | ||
| echo "Verificando formato de código con Black..." | ||
| black --line-length 100 . | ||
|
|
||
| - name: Analizando el código con Pylint | ||
| run: | | ||
| # 4. Ejecutar Pylint en todos los archivos .py rastreados por Git | ||
| export PYTHONPATH=$PYTHONPATH:$(pwd)/unidad_7/pystock_management | ||
| pylint $(git ls-files '*.py') No newline at end of file |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix the problem, add a permissions block to the job definition to restrict GITHUB_TOKEN permissions to the minimum required. Since this workflow does not perform any operations that modify repository contents or interact with issues or pull requests, it is safe and secure to set contents: read. This should be added directly under the job (build:) in .github/workflows/pylint.yml, immediately after runs-on: ubuntu-latest, before steps:. No additional imports or packages are required; this is a YAML/Actions configuration change only. No existing functionality will be affected.
| @@ -9,6 +9,8 @@ | ||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| strategy: | ||
| matrix: | ||
| python-version: ["3.8", "3.9", "3.10"] |
| if request.method == "POST": | ||
| if "file" not in request.files or request.files["file"].filename == "": | ||
| flash("No se seleccionó ningún archivo.", "error") | ||
| return redirect(request.url) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix this, do not use request.url as the argument to redirect. Instead, directly redirect to a known-safe internal route, such as the index page, or use url_for("upload_csv") to ensure the user is always returned to the upload page. In this specific context, we want to redirect the user back to the CSV upload form upon error.
Change needed: On line 94, replace redirect(request.url) with redirect(url_for("upload_csv")).
No new imports are required since url_for is already imported.
| @@ -91,7 +91,7 @@ | ||
| if request.method == "POST": | ||
| if "file" not in request.files or request.files["file"].filename == "": | ||
| flash("No se seleccionó ningún archivo.", "error") | ||
| return redirect(request.url) | ||
| return redirect(url_for("upload_csv")) | ||
|
|
||
| file = request.files["file"] | ||
|
|
|
|
||
| if not file.filename.lower().endswith(".csv"): | ||
| flash("Por favor, sube un archivo CSV válido.", "error") | ||
| return redirect(request.url) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix this issue, avoid redirecting to request.url, which can be manipulated by an attacker. Instead, redirect to a safe, known route—typically by using Flask's url_for() function to redirect to a safe handler name, such as the same route that triggered the redirect (in this case, "upload_csv"). This prevents an attacker from controlling the redirection target. Update line 100 to use redirect(url_for("upload_csv")), which simply reloads the page in a safe and predictable manner. No additional imports or method definitions are required, and no loss of functionality should occur, as the user remains on the intended upload page.
| @@ -97,7 +97,7 @@ | ||
|
|
||
| if not file.filename.lower().endswith(".csv"): | ||
| flash("Por favor, sube un archivo CSV válido.", "error") | ||
| return redirect(request.url) | ||
| return redirect(url_for("upload_csv")) | ||
|
|
||
| if file.content_type not in ("text/csv", "application/vnd.ms-excel", "application/octet-stream"): | ||
| # Algunos navegadores usan tipos genéricos al subir .csv |
| return jsonify({"message": f"Inventario creado y guardado como {nombre_archivo_creado}"}), 200 | ||
|
|
||
| except (KeyError, ValueError, OSError) as e: | ||
| return jsonify({"message": f"Error en los datos del inventario: {e}"}), 400 |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix the problem, the error message sent to clients should be made generic and not include potentially sensitive details. Exception information should be logged server-side for diagnostics. Concretely:
- In
crear_inventario, within theexceptblock on lines 166–167, replace the return statement so that the response does not includee. - Instead, log the exception details using a safe mechanism (
app.logger.erroris standard for Flask apps). - Leave only a generic message in the API response to users.
- You need to add an appropriate import/use of Flask's logger, if not already present.
Affected region:
- unidad_3/Flask/app.py, lines 166–167 (in
crear_inventario).
You do not need to change any exception handling behavior or add new dependencies.
| @@ -164,7 +164,8 @@ | ||
| return jsonify({"message": f"Inventario creado y guardado como {nombre_archivo_creado}"}), 200 | ||
|
|
||
| except (KeyError, ValueError, OSError) as e: | ||
| return jsonify({"message": f"Error en los datos del inventario: {e}"}), 400 | ||
| app.logger.error(f"Error procesando el inventario: {e}", exc_info=True) | ||
| return jsonify({"message": "Error en los datos del inventario."}), 400 | ||
|
|
||
| inventario_temporal = session.get("inventario_creado", []) | ||
| return render_template("crear_inventario.html", inventario_creado=inventario_temporal) |
|
|
||
| except (KeyError, ValueError) as e: | ||
| print(f"Error de datos en /pedir_proveedor: {e}") | ||
| return jsonify({"message": f"Error al procesar datos: {e}"}), 400 |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix this issue, the code at line 355 should be modified so that the exception details ({e}) are not included in the response sent to the user. Instead, the error should be logged on the server for developers to review, and the response should provide a generic error message such as "Error al procesar datos.". This ensures that users do not receive potentially sensitive information about the server's internal workings. The change only affects the exception branch's response message and optionally adds a logging mechanism for debugging server-side. Specifically:
- In
pedir_proveedor, change the linereturn jsonify({"message": f"Error al procesar datos: {e}"}), 400toreturn jsonify({"message": "Error al procesar datos."}), 400. - Ensure the error is logged for developer diagnosis (existing
print(f"Error de datos en /pedir_proveedor: {e}")suffices, but for production code use Python'slogginglibrary).
No new method or import is strictly required for this code snippet.
| @@ -352,7 +352,7 @@ | ||
|
|
||
| except (KeyError, ValueError) as e: | ||
| print(f"Error de datos en /pedir_proveedor: {e}") | ||
| return jsonify({"message": f"Error al procesar datos: {e}"}), 400 | ||
| return jsonify({"message": "Error al procesar datos."}), 400 | ||
| except Exception as e: | ||
| print(f"Error inesperado en /pedir_proveedor: {e}") | ||
| return jsonify({"message": "Error interno del servidor."}), 500 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
No description provided.