Skip to content

Commit 4a0a9dc

Browse files
authored
Merge pull request #48 from ForgeOpus/copilot/fix-security-alerts-codeql
Fix path traversal, XSS, timing attack, and information disclosure vulnerabilities
2 parents 1cd005a + 5d39626 commit 4a0a9dc

File tree

5 files changed

+38
-15
lines changed

5 files changed

+38
-15
lines changed

project/block_manager/utils/file_cleanup.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,22 @@ def save_uploaded_file_temporarily(uploaded_file):
2323
upload_dir = Path(getattr(settings, 'TEMP_UPLOAD_DIR', '/tmp/visionforge_uploads'))
2424
upload_dir.mkdir(parents=True, exist_ok=True)
2525

26+
# Sanitize filename to prevent path traversal attacks
27+
# Extract only the basename to remove any path components
28+
original_name = os.path.basename(uploaded_file.name)
29+
# Remove path separators that might have been missed (defense in depth)
30+
safe_name = original_name.replace('/', '_').replace('\\', '_')
31+
# Remove null bytes which can cause issues
32+
safe_name = safe_name.replace('\x00', '')
33+
2634
timestamp = int(time.time())
27-
safe_filename = f"{timestamp}_{uploaded_file.name}"
35+
safe_filename = f"{timestamp}_{safe_name}"
2836
file_path = upload_dir / safe_filename
37+
38+
# Verify the resolved path is within the upload directory (additional security check)
39+
resolved_path = file_path.resolve()
40+
if not str(resolved_path).startswith(str(upload_dir.resolve())):
41+
raise ValueError("Invalid file path detected - potential path traversal attack")
2942

3043
with open(file_path, 'wb+') as destination:
3144
for chunk in uploaded_file.chunks():

project/block_manager/views/architecture_views.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,9 @@ def render_node_code(request):
399399
'format': 'class'
400400
})
401401
except Exception as e:
402-
import traceback
403-
traceback.print_exc() # Log to console for debugging
402+
logger.error(f"Error generating node code: {str(e)}", exc_info=True)
404403
return Response(
405-
{'success': False, 'error': f'Error generating node code: {str(e)}'},
404+
{'success': False, 'error': 'Error generating node code'},
406405
status=status.HTTP_500_INTERNAL_SERVER_ERROR
407406
)
408407

project/block_manager/views/maintenance_views.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
import logging
55
import time
6+
import hmac
67
from pathlib import Path
78
from django.http import JsonResponse
89
from django.views.decorators.csrf import csrf_exempt
@@ -96,9 +97,9 @@ def trigger_file_cleanup(request):
9697
data = json.loads(request.body)
9798
provided_secret = data.get('secret', '')
9899

99-
# Verify secret token
100+
# Verify secret token using constant-time comparison to prevent timing attacks
100101
expected_secret = settings.CLEANUP_SECRET_TOKEN
101-
if not expected_secret or provided_secret != expected_secret:
102+
if not expected_secret or not hmac.compare_digest(provided_secret, expected_secret):
102103
logger.warning(f"Unauthorized cleanup attempt from IP {request.META.get('REMOTE_ADDR')}")
103104
return JsonResponse({
104105
'error': 'Unauthorized',
@@ -143,9 +144,9 @@ def get_upload_stats(request):
143144
try:
144145
provided_secret = request.GET.get('secret', '')
145146

146-
# Verify secret token
147+
# Verify secret token using constant-time comparison to prevent timing attacks
147148
expected_secret = settings.CLEANUP_SECRET_TOKEN
148-
if not expected_secret or provided_secret != expected_secret:
149+
if not expected_secret or not hmac.compare_digest(provided_secret, expected_secret):
149150
logger.warning(f"Unauthorized stats access attempt from IP {request.META.get('REMOTE_ADDR')}")
150151
return JsonResponse({
151152
'error': 'Unauthorized',

project/block_manager/views/validation_views.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
from rest_framework.response import Response
33
from rest_framework import status
44
from rest_framework.permissions import AllowAny
5-
import traceback
5+
import logging
66

77
from block_manager.serializers import SaveArchitectureSerializer
88
from block_manager.services.validation import validate_architecture
99
from block_manager.services.inference import infer_dimensions
1010

11+
logger = logging.getLogger(__name__)
12+
1113

1214
@api_view(['POST'])
1315
@permission_classes([AllowAny])
@@ -43,13 +45,12 @@ def validate_model(request):
4345

4446
except Exception as e:
4547
# Log the error for debugging
46-
print(f"Validation error: {str(e)}")
47-
traceback.print_exc()
48+
logger.error(f"Validation error: {str(e)}", exc_info=True)
4849

4950
return Response(
5051
{
5152
'isValid': False,
52-
'errors': [{'message': f'Server error: {str(e)}', 'type': 'error'}],
53+
'errors': [{'message': 'Server error during validation', 'type': 'error'}],
5354
},
5455
status=status.HTTP_500_INTERNAL_SERVER_ERROR
5556
)

project/frontend/src/components/ui/chart.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ function ChartContainer({
4545
>["children"]
4646
}) {
4747
const uniqueId = useId()
48-
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48+
// Sanitize ID to prevent XSS - only allow alphanumeric, hyphens, and underscores
49+
const baseId = (id || uniqueId).replace(/[^a-zA-Z0-9-_]/g, '')
50+
const chartId = `chart-${baseId}`
4951

5052
return (
5153
<ChartContext.Provider value={{ config }}>
@@ -76,19 +78,26 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
7678
return null
7779
}
7880

81+
// Sanitize id to prevent CSS injection - only allow alphanumeric, hyphens, and underscores
82+
const sanitizedId = id.replace(/[^a-zA-Z0-9-_]/g, '')
83+
7984
return (
8085
<style
8186
dangerouslySetInnerHTML={{
8287
__html: Object.entries(THEMES)
8388
.map(
8489
([theme, prefix]) => `
85-
${prefix} [data-chart=${id}] {
90+
${prefix} [data-chart=${sanitizedId}] {
8691
${colorConfig
8792
.map(([key, itemConfig]) => {
93+
// Sanitize key to prevent CSS injection
94+
const sanitizedKey = key.replace(/[^a-zA-Z0-9-_]/g, '')
8895
const color =
8996
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
9097
itemConfig.color
91-
return color ? ` --color-${key}: ${color};` : null
98+
// Sanitize color value to prevent CSS injection
99+
const sanitizedColor = color?.replace(/[<>'"]/g, '')
100+
return sanitizedColor ? ` --color-${sanitizedKey}: ${sanitizedColor};` : null
92101
})
93102
.join("\n")}
94103
}

0 commit comments

Comments
 (0)