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')
|
return success_response(message='Schedule configuration saved successfully')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
logger.exception("[ScheduleConfig] Failed to save schedule configuration")
|
||||||
import traceback
|
|
||||||
error_msg = f"Error saving schedule config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
f"Error saving schedule configuration: {str(e)}",
|
"Error saving schedule configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -627,14 +624,11 @@ def save_dim_schedule_config():
|
|||||||
|
|
||||||
return success_response(message='Dim schedule configuration saved successfully')
|
return success_response(message='Dim schedule configuration saved successfully')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
logger.exception("[DimScheduleConfig] Failed to save dim schedule configuration")
|
||||||
import traceback
|
|
||||||
error_msg = f"Error saving dim schedule config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
f"Error saving dim schedule configuration: {str(e)}",
|
"Error saving dim schedule configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -978,14 +972,11 @@ def save_main_config():
|
|||||||
|
|
||||||
return success_response(message='Configuration saved successfully')
|
return success_response(message='Configuration saved successfully')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
logger.exception("[Config] Failed to save configuration")
|
||||||
import traceback
|
|
||||||
error_msg = f"Error saving config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
f"Error saving configuration: {e}",
|
"Error saving configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1021,32 +1012,20 @@ def save_raw_main_config():
|
|||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
from src.exceptions import ConfigError
|
from src.exceptions import ConfigError
|
||||||
|
logger.exception("[RawConfig] Failed to save raw main config")
|
||||||
# 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
|
|
||||||
if isinstance(e, ConfigError):
|
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(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
error_message,
|
"Error saving raw main configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
context={'config_path': e.config_path} if hasattr(e, 'config_path') and e.config_path else None,
|
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.UNKNOWN_ERROR,
|
ErrorCode.UNKNOWN_ERROR,
|
||||||
error_message,
|
"An unexpected error occurred while saving the configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1072,24 +1051,22 @@ def save_raw_secrets_config():
|
|||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
from src.exceptions import ConfigError
|
from src.exceptions import ConfigError
|
||||||
|
logger.exception("[RawSecrets] Failed to save raw secrets config")
|
||||||
# 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
|
|
||||||
if isinstance(e, ConfigError):
|
if isinstance(e, ConfigError):
|
||||||
# ConfigError has a message attribute and may have context
|
return error_response(
|
||||||
error_message = str(e)
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
if hasattr(e, 'config_path') and e.config_path:
|
"Error saving raw secrets configuration",
|
||||||
error_message = f"{error_message} (config_path: {e.config_path})"
|
details="Internal server error - check server logs",
|
||||||
|
status_code=500
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
|
return error_response(
|
||||||
|
ErrorCode.UNKNOWN_ERROR,
|
||||||
return jsonify({'status': 'error', 'message': error_message}), 500
|
"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'])
|
@api_v3.route('/system/status', methods=['GET'])
|
||||||
def get_system_status():
|
def get_system_status():
|
||||||
@@ -1470,10 +1447,8 @@ def execute_system_action():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
error_details = traceback.format_exc()
|
logger.exception("[SystemAction] Unexpected error")
|
||||||
print(f"Error in execute_system_action: {str(e)}")
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
print(error_details)
|
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
|
||||||
|
|
||||||
@api_v3.route('/display/current', methods=['GET'])
|
@api_v3.route('/display/current', methods=['GET'])
|
||||||
def get_display_current():
|
def get_display_current():
|
||||||
@@ -1882,10 +1857,8 @@ def get_installed_plugins():
|
|||||||
return jsonify({'status': 'success', 'data': {'plugins': plugins}})
|
return jsonify({'status': 'success', 'data': {'plugins': plugins}})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
error_details = traceback.format_exc()
|
logger.exception("[Plugins] Error listing installed plugins")
|
||||||
print(f"Error in get_installed_plugins: {str(e)}")
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
print(error_details)
|
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
|
||||||
|
|
||||||
@api_v3.route('/plugins/health', methods=['GET'])
|
@api_v3.route('/plugins/health', methods=['GET'])
|
||||||
def get_plugin_health():
|
def get_plugin_health():
|
||||||
@@ -6068,8 +6041,8 @@ def upload_plugin_asset():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/of-the-day/json/upload', methods=['POST'])
|
@api_v3.route('/plugins/of-the-day/json/upload', methods=['POST'])
|
||||||
def upload_of_the_day_json():
|
def upload_of_the_day_json():
|
||||||
@@ -6218,8 +6191,8 @@ def upload_of_the_day_json():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/of-the-day/json/delete', methods=['POST'])
|
@api_v3.route('/plugins/of-the-day/json/delete', methods=['POST'])
|
||||||
def delete_of_the_day_json():
|
def delete_of_the_day_json():
|
||||||
@@ -6265,8 +6238,8 @@ def delete_of_the_day_json():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/<plugin_id>/static/<path:file_path>', methods=['GET'])
|
@api_v3.route('/plugins/<plugin_id>/static/<path:file_path>', methods=['GET'])
|
||||||
def serve_plugin_static(plugin_id, file_path):
|
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)
|
return Response(content, mimetype=content_type)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
|
|
||||||
@api_v3.route('/plugins/calendar/upload-credentials', methods=['POST'])
|
@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'})
|
return jsonify({'status': 'success', 'message': 'Image deleted successfully'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/assets/list', methods=['GET'])
|
@api_v3.route('/plugins/assets/list', methods=['GET'])
|
||||||
def list_plugin_assets():
|
def list_plugin_assets():
|
||||||
@@ -6513,8 +6486,8 @@ def list_plugin_assets():
|
|||||||
return jsonify({'status': 'success', 'data': {'assets': assets}})
|
return jsonify({'status': 'success', 'data': {'assets': assets}})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/logs', methods=['GET'])
|
@api_v3.route('/logs', methods=['GET'])
|
||||||
def get_logs():
|
def get_logs():
|
||||||
|
|||||||
Reference in New Issue
Block a user