Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions FileFlow/backend/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from werkzeug.utils import secure_filename
try:
from backend.models.database import db, File
from backend.utils.validators import Validators
except ImportError:
from models.database import db, File
from utils.validators import Validators
from pathlib import Path

files_bp = Blueprint('files_bp', __name__)
Expand Down Expand Up @@ -111,11 +113,18 @@ def rename_file(file_id):
new_name = data.get('new_name')

if not new_name:
abort(400)

file_to_rename.filename = new_name
abort(400, description="New name is required")

# Validate and sanitize the filename
if not Validators.is_valid_filename(new_name):
abort(400, description="Invalid filename")

# Sanitize the filename to remove any dangerous characters
sanitized_name = Validators.sanitize_filename(new_name)

file_to_rename.filename = sanitized_name
db.session.commit()
return jsonify({'success': True, 'new_name': new_name})
return jsonify({'success': True, 'new_name': sanitized_name})

@files_bp.route('/move_file/<int:file_id>', methods=['POST'])
@login_required
Expand Down
9 changes: 3 additions & 6 deletions FileFlow/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,6 @@ def open_folder(folder_id):
app.logger.error(f"Error in open_folder: {str(e)}")
abort(500, description="Error occurred while opening folder")

from datetime import datetime

# ... (rest of the imports)

# ... (rest of the code)

@app.route('/create_folder', methods=['POST'])
@login_required
def create_folder():
Expand Down Expand Up @@ -280,18 +274,21 @@ def init_db_command():
from backend.api.folders import folders_bp
from backend.api.search import search_bp
from backend.api.upload import upload_bp
from backend.api.compression import compression_bp
except ImportError:
from api.auth import auth_bp
from api.files import files_bp
from api.folders import folders_bp
from api.search import search_bp
from api.upload import upload_bp
from api.compression import compression_bp

app.register_blueprint(files_bp)
app.register_blueprint(folders_bp)
app.register_blueprint(search_bp)
app.register_blueprint(upload_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(compression_bp)

import threading
try:
Expand Down
17 changes: 15 additions & 2 deletions FileFlow/backend/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import os
import warnings

class Config:
SECRET_KEY = os.urandom(24)
SQLALCHEMY_DATABASE_URI = 'sqlite:///fileflow.db'
# SECRET_KEY should be set via environment variable in production
# A random fallback is used only for development; this will invalidate
# sessions on every restart
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
warnings.warn(
"SECRET_KEY not set in environment. Using random key. "
"Sessions will be invalidated on restart. "
"Set SECRET_KEY environment variable for production use.",
RuntimeWarning
)
SECRET_KEY = os.urandom(24)
Comment on lines +8 to +16
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SECRET_KEY is regenerated on every restart when not set via environment variable, which will invalidate all existing user sessions. While the warning message mentions this, the implementation stores the random key in a class variable that persists for the app lifetime but is lost on restart. Consider using a persistent fallback (like storing in a file) for development environments, or make the warning more prominent to ensure production deployments always set the environment variable.

Copilot uses AI. Check for mistakes.

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///fileflow.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
UPLOAD_FOLDER = 'user_files'
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file upload
54 changes: 53 additions & 1 deletion FileFlow/backend/services/compression_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@
from pathlib import Path

class CompressionService:
@staticmethod
def _is_safe_path(base_path, target_path):
"""Check if target path is within base path (prevents directory traversal)

Note: This validates the path string without resolving symlinks to prevent
symlink-based attacks. The check uses string comparison after normalization.
"""
try:
base = Path(base_path).resolve()
# Normalize target without fully resolving to prevent symlink attacks
# Join base with target and check if it stays within base
Comment on lines +11 to +17
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "Normalize target without fully resolving to prevent symlink attacks" but the code then calls .resolve() on the full path on line 18, which does resolve symlinks. This creates a contradiction between the comment and the implementation. Either the comment should be updated to reflect that symlinks are resolved, or the implementation should be changed to avoid resolving symlinks (though resolving is actually the safer approach).

Suggested change
Note: This validates the path string without resolving symlinks to prevent
symlink-based attacks. The check uses string comparison after normalization.
"""
try:
base = Path(base_path).resolve()
# Normalize target without fully resolving to prevent symlink attacks
# Join base with target and check if it stays within base
Note: This resolves both the base path and the combined target path to
absolute, symlink-resolved paths and then verifies the target remains
within the base directory.
"""
try:
base = Path(base_path).resolve()
# Resolve the combined path and ensure it stays within the resolved base

Copilot uses AI. Check for mistakes.
full_path = (base / target_path).resolve()
return full_path.is_relative_to(base)
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_relative_to() method was added in Python 3.9. For compatibility with earlier Python versions, this will raise an AttributeError. Consider adding a compatibility check or documenting the minimum Python version requirement in requirements.txt or README.

Suggested change
return full_path.is_relative_to(base)
try:
# If full_path is not within base, this will raise ValueError
full_path.relative_to(base)
return True
except ValueError:
return False

Copilot uses AI. Check for mistakes.
except (ValueError, RuntimeError):
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handler on line 20 catches ValueError and RuntimeError but not AttributeError, which will be raised on Python versions before 3.9 when calling is_relative_to(). This method was only added in Python 3.9. The exception tuple should include AttributeError to handle older Python versions gracefully: except (ValueError, RuntimeError, AttributeError):

Suggested change
except (ValueError, RuntimeError):
except (ValueError, RuntimeError, AttributeError):

Copilot uses AI. Check for mistakes.
return False

@staticmethod
def _safe_extract_member(archive_path, member_name, extract_to):
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first parameter archive_path is not used in the function body. This parameter should be removed from the function signature as it serves no purpose in the validation logic.

Copilot uses AI. Check for mistakes.
"""Validate that extracted file stays within extract_to directory

Rejects paths with:
- Absolute paths
- Parent directory references (..)
- Paths that escape the extraction directory
"""
# Reject absolute paths
if Path(member_name).is_absolute():
raise ValueError(f"Absolute path in archive is not allowed: {member_name}")

# Reject paths with parent directory references
if '..' in Path(member_name).parts:
raise ValueError(f"Path traversal attempt detected: {member_name}")

# Validate the final path stays within extraction directory
if not CompressionService._is_safe_path(extract_to, member_name):
raise ValueError(f"Attempted path traversal in archive: {member_name}")
return True

@staticmethod
def create_zip(file_paths, archive_path, password=None):
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
Expand Down Expand Up @@ -32,16 +70,30 @@ def extract_zip(archive_path, extract_to, password=None):
with zipfile.ZipFile(archive_path, 'r') as zipf:
if password:
zipf.setpassword(password.encode())
# Validate all members before extraction
for member in zipf.namelist():
CompressionService._safe_extract_member(archive_path, member, extract_to)
zipf.extractall(extract_to)

@staticmethod
def extract_tar(archive_path, extract_to):
with tarfile.open(archive_path, 'r:*') as tarf:
tarf.extractall(extract_to)
# Validate all members before extraction to prevent path traversal
for member in tarf.getmembers():
CompressionService._safe_extract_member(archive_path, member.name, extract_to)
# Use data filter for Python 3.11.4+ or validate manually for earlier versions
try:
tarf.extractall(extract_to, filter='data')
except TypeError:
# Python < 3.11.4 doesn't support filter parameter, but we already validated
tarf.extractall(extract_to)

@staticmethod
def extract_7z(archive_path, extract_to, password=None):
with py7zr.SevenZipFile(archive_path, 'r', password=password) as szf:
# Validate all members before extraction
for member in szf.getnames():
CompressionService._safe_extract_member(archive_path, member, extract_to)
Comment on lines +74 to +96
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calls to _safe_extract_member pass archive_path as the first parameter, but this parameter is unused in the function. All three extraction methods (extract_zip, extract_tar, extract_7z) should be updated to remove this unnecessary parameter.

Copilot uses AI. Check for mistakes.
szf.extractall(extract_to)

@staticmethod
Expand Down
Empty file.
6 changes: 1 addition & 5 deletions FileFlow/frontend/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</div>
<div class="view-options">
<button type="button" class="btn active" id="list-view-btn" aria-pressed="true" aria-label="List view"><i class="fas fa-list" aria-hidden="true"></i></button>
<button class="btn" id="grid-view-btn"><i class="fas fa-th"></i></button>
<button type="button" class="btn" id="grid-view-btn" aria-pressed="false" aria-label="Grid view"><i class="fas fa-th" aria-hidden="true"></i></button>
</div>
</div>
<div id="file-browser" class="file-browser-list-view">
Expand Down Expand Up @@ -64,15 +64,11 @@
</div>
</div>

</form>

<form action="{{ url_for('upload_bp.upload_file') }}" method="POST" enctype="multipart/form-data" id="upload-form" style="display:none;">
<input type="hidden" name="folder_id" id="upload-folder-id" value="{{ current_folder_id if current_folder_id else '' }}">
<input type="file" name="file" id="file-input">
</form>

</form>

<form action="{{ url_for('folders_bp.create_folder') }}" method="POST" id="create-folder-form" style="display:none;">
<input type="hidden" name="parent_folder_id" id="parent-folder-id" value="{{ current_folder_id if current_folder_id else '' }}">
<input type="text" name="folder_name" id="folder-name-input" required>
Expand Down
13 changes: 8 additions & 5 deletions FileFlow/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ echo -e "${BLUE} FileFlow Application Startup${NC}"
echo -e "${BLUE}=====================================${NC}"
echo

# Get the directory where the script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Check if database is initialized
if [ ! -f "backend/instance/fileflow.db" ]; then
if [ ! -f "$SCRIPT_DIR/backend/instance/fileflow.db" ]; then
echo -e "${GREEN}Initializing database...${NC}"
cd /workspaces/file-uploader-viewing-mode-is-under-process-/FileFlow
cd "$SCRIPT_DIR"
export FLASK_APP=backend.app
flask init-db
fi

# Create user_files directory if it doesn't exist
mkdir -p backend/user_files
mkdir -p "$SCRIPT_DIR/backend/user_files"

echo
echo -e "${GREEN}Starting Flask backend on port 5000...${NC}"
cd /workspaces/file-uploader-viewing-mode-is-under-process-/FileFlow
cd "$SCRIPT_DIR"
export FLASK_APP=backend.app
export FLASK_ENV=development
flask run --host=0.0.0.0 --port=5000 &
Expand All @@ -36,7 +39,7 @@ echo
sleep 2

echo -e "${GREEN}Starting React frontend on port 3000...${NC}"
cd /workspaces/file-uploader-viewing-mode-is-under-process-/FileFlow/frontend
cd "$SCRIPT_DIR/frontend"
npm start &
FRONTEND_PID=$!

Expand Down
38 changes: 0 additions & 38 deletions READMED.md

This file was deleted.

7 changes: 5 additions & 2 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ fi

echo "✅ Prerequisites check passed"

# Get the directory where the script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Install backend dependencies
echo "📦 Installing backend dependencies..."
cd FileFlow/backend
cd "$SCRIPT_DIR/FileFlow/backend"
pip install -r ../requirements.txt

# Initialize database
Expand All @@ -35,7 +38,7 @@ BACKEND_PID=$!

# Install frontend dependencies
echo "📦 Installing frontend dependencies..."
cd ../frontend
cd "$SCRIPT_DIR/FileFlow/frontend"
npm install

# Start frontend development server
Expand Down
Loading