mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
fix(security): stop leaking Python tracebacks to HTTP clients (#283)
* fix(security): stop leaking Python tracebacks to HTTP clients
Replace 13 instances where traceback.format_exc() was sent in API
JSON responses (via `details=`, `traceback:`, or `details:` keys).
- 5 error_response(details=traceback.format_exc()) → generic message
- 6 jsonify({'traceback': traceback.format_exc()}) → removed key
- 2 jsonify({'details': error_details}) → logger.error() instead
Tracebacks in debug mode (app.py error handlers) are preserved as
they are guarded by app.debug and expected during development.
Co-Authored-By: 5ymb01 <noreply@github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): sanitize str(e) from client responses, add server-side logging
Address CodeRabbit review findings:
- Replace str(e) in error_response message fields with generic messages
- Replace import logging/traceback + manual format with logger.exception()
- Add logger.exception() to 6 jsonify handlers that were swallowing errors
- All exception details now logged server-side only, not sent to clients
Co-Authored-By: 5ymb01 <noreply@github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate traceback logging, sanitize secrets config error
Address CodeRabbit nitpicks:
- Remove manual import logging/traceback + logging.error() that duplicated
the logger.exception() call in save_raw_main_config
- Apply same fix to save_raw_secrets_config: replace str(e) in client
response with generic message, use logger.exception() for server-side
Co-Authored-By: 5ymb01 <noreply@github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: 5ymb01 <5ymb01@users.noreply.github.com>
Co-authored-by: 5ymb01 <noreply@github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -406,14 +406,11 @@ def save_schedule_config():
|
||||
|
||||
return success_response(message='Schedule configuration saved successfully')
|
||||
except Exception as e:
|
||||
import logging
|
||||
import traceback
|
||||
error_msg = f"Error saving schedule config: {str(e)}\n{traceback.format_exc()}"
|
||||
logging.error(error_msg)
|
||||
logger.exception("[ScheduleConfig] Failed to save schedule configuration")
|
||||
return error_response(
|
||||
ErrorCode.CONFIG_SAVE_FAILED,
|
||||
f"Error saving schedule configuration: {str(e)}",
|
||||
details=traceback.format_exc(),
|
||||
"Error saving schedule configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
|
||||
@@ -627,14 +624,11 @@ def save_dim_schedule_config():
|
||||
|
||||
return success_response(message='Dim schedule configuration saved successfully')
|
||||
except Exception as e:
|
||||
import logging
|
||||
import traceback
|
||||
error_msg = f"Error saving dim schedule config: {str(e)}\n{traceback.format_exc()}"
|
||||
logging.error(error_msg)
|
||||
logger.exception("[DimScheduleConfig] Failed to save dim schedule configuration")
|
||||
return error_response(
|
||||
ErrorCode.CONFIG_SAVE_FAILED,
|
||||
f"Error saving dim schedule configuration: {str(e)}",
|
||||
details=traceback.format_exc(),
|
||||
"Error saving dim schedule configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
|
||||
@@ -978,14 +972,11 @@ def save_main_config():
|
||||
|
||||
return success_response(message='Configuration saved successfully')
|
||||
except Exception as e:
|
||||
import logging
|
||||
import traceback
|
||||
error_msg = f"Error saving config: {str(e)}\n{traceback.format_exc()}"
|
||||
logging.error(error_msg)
|
||||
logger.exception("[Config] Failed to save configuration")
|
||||
return error_response(
|
||||
ErrorCode.CONFIG_SAVE_FAILED,
|
||||
f"Error saving configuration: {e}",
|
||||
details=traceback.format_exc(),
|
||||
"Error saving configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
|
||||
@@ -1021,32 +1012,20 @@ def save_raw_main_config():
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||
except Exception as e:
|
||||
import logging
|
||||
import traceback
|
||||
from src.exceptions import ConfigError
|
||||
|
||||
# Log the full error for debugging
|
||||
error_msg = f"Error saving raw main config: {str(e)}\n{traceback.format_exc()}"
|
||||
logging.error(error_msg)
|
||||
|
||||
# Extract more specific error message if it's a ConfigError
|
||||
logger.exception("[RawConfig] Failed to save raw main config")
|
||||
if isinstance(e, ConfigError):
|
||||
error_message = str(e)
|
||||
if hasattr(e, 'config_path') and e.config_path:
|
||||
error_message = f"{error_message} (config_path: {e.config_path})"
|
||||
return error_response(
|
||||
ErrorCode.CONFIG_SAVE_FAILED,
|
||||
error_message,
|
||||
details=traceback.format_exc(),
|
||||
context={'config_path': e.config_path} if hasattr(e, 'config_path') and e.config_path else None,
|
||||
"Error saving raw main configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
else:
|
||||
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
|
||||
return error_response(
|
||||
ErrorCode.UNKNOWN_ERROR,
|
||||
error_message,
|
||||
details=traceback.format_exc(),
|
||||
"An unexpected error occurred while saving the configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
|
||||
@@ -1072,24 +1051,22 @@ def save_raw_secrets_config():
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||
except Exception as e:
|
||||
import logging
|
||||
import traceback
|
||||
from src.exceptions import ConfigError
|
||||
|
||||
# Log the full error for debugging
|
||||
error_msg = f"Error saving raw secrets config: {str(e)}\n{traceback.format_exc()}"
|
||||
logging.error(error_msg)
|
||||
|
||||
# Extract more specific error message if it's a ConfigError
|
||||
logger.exception("[RawSecrets] Failed to save raw secrets config")
|
||||
if isinstance(e, ConfigError):
|
||||
# ConfigError has a message attribute and may have context
|
||||
error_message = str(e)
|
||||
if hasattr(e, 'config_path') and e.config_path:
|
||||
error_message = f"{error_message} (config_path: {e.config_path})"
|
||||
return error_response(
|
||||
ErrorCode.CONFIG_SAVE_FAILED,
|
||||
"Error saving raw secrets configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
else:
|
||||
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
|
||||
|
||||
return jsonify({'status': 'error', 'message': error_message}), 500
|
||||
return error_response(
|
||||
ErrorCode.UNKNOWN_ERROR,
|
||||
"An unexpected error occurred while saving the configuration",
|
||||
details="Internal server error - check server logs",
|
||||
status_code=500
|
||||
)
|
||||
|
||||
@api_v3.route('/system/status', methods=['GET'])
|
||||
def get_system_status():
|
||||
@@ -1470,10 +1447,8 @@ def execute_system_action():
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_details = traceback.format_exc()
|
||||
print(f"Error in execute_system_action: {str(e)}")
|
||||
print(error_details)
|
||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
||||
logger.exception("[SystemAction] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/display/current', methods=['GET'])
|
||||
def get_display_current():
|
||||
@@ -1882,10 +1857,8 @@ def get_installed_plugins():
|
||||
return jsonify({'status': 'success', 'data': {'plugins': plugins}})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_details = traceback.format_exc()
|
||||
print(f"Error in get_installed_plugins: {str(e)}")
|
||||
print(error_details)
|
||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
||||
logger.exception("[Plugins] Error listing installed plugins")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/plugins/health', methods=['GET'])
|
||||
def get_plugin_health():
|
||||
@@ -6068,8 +6041,8 @@ def upload_plugin_asset():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
||||
logger.exception("[API] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/plugins/of-the-day/json/upload', methods=['POST'])
|
||||
def upload_of_the_day_json():
|
||||
@@ -6218,8 +6191,8 @@ def upload_of_the_day_json():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
||||
logger.exception("[API] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/plugins/of-the-day/json/delete', methods=['POST'])
|
||||
def delete_of_the_day_json():
|
||||
@@ -6265,8 +6238,8 @@ def delete_of_the_day_json():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
||||
logger.exception("[API] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/plugins/<plugin_id>/static/<path:file_path>', methods=['GET'])
|
||||
def serve_plugin_static(plugin_id, file_path):
|
||||
@@ -6313,8 +6286,8 @@ def serve_plugin_static(plugin_id, file_path):
|
||||
return Response(content, mimetype=content_type)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
||||
logger.exception("[API] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
|
||||
@api_v3.route('/plugins/calendar/upload-credentials', methods=['POST'])
|
||||
@@ -6485,8 +6458,8 @@ def delete_plugin_asset():
|
||||
return jsonify({'status': 'success', 'message': 'Image deleted successfully'})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
||||
logger.exception("[API] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/plugins/assets/list', methods=['GET'])
|
||||
def list_plugin_assets():
|
||||
@@ -6513,8 +6486,8 @@ def list_plugin_assets():
|
||||
return jsonify({'status': 'success', 'data': {'assets': assets}})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
||||
logger.exception("[API] Unexpected error")
|
||||
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||
|
||||
@api_v3.route('/logs', methods=['GET'])
|
||||
def get_logs():
|
||||
|
||||
Reference in New Issue
Block a user