diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9115cf5..2eb85a27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ a proposal for your work first, to be sure that we can use it. ## Submission Guidelines ### Submitting an Issue -Before you submit an issue, search the archive, maybe your question was already answered. +Before you submit an issue, search the archive, maybe your question has already been answered. If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize the effort we can spend fixing issues and adding new diff --git a/End_to_end_Solutions/AOAISearchDemo/app/backend/app.py b/End_to_end_Solutions/AOAISearchDemo/app/backend/app.py index 35c33a30..f107e13f 100644 --- a/End_to_end_Solutions/AOAISearchDemo/app/backend/app.py +++ b/End_to_end_Solutions/AOAISearchDemo/app/backend/app.py @@ -340,7 +340,7 @@ def chat(): return jsonify(response.to_item()), 400 except Exception as e: logger.exception(f"Exception in /chat: {e}", extra=properties) - response = ChatResponse(answer=Answer(), error=str(e), show_retry=True) + response = ChatResponse(answer=Answer(), error="An internal error has occurred.", show_retry=True) return jsonify(response.to_item()), 500 @@ -352,7 +352,7 @@ def get_all_user_profiles(): return jsonify(user_profiles_dict) except Exception as e: logger.exception(f"Exception in /user-profiles: {e}") - return jsonify({"error": str(e)}), 500 + return jsonify({"error": "An internal error has occurred."}), 500 @app.route("/chat-sessions//", methods=["DELETE"]) @@ -369,7 +369,7 @@ def clear_chat_session(user_id: str, conversation_id: str): logger.exception( f"Exception in /chat-sessions//: {e}" ) - return jsonify({"error": str(e)}), 500 + return jsonify({"error": "An internal error has occurred."}), 500 @app.route("/search-settings", methods=["GET"]) @@ -391,7 +391,7 @@ def get_search_settings(): return jsonify(search_settings.to_item()) except Exception as e: logger.exception(f"Exception in /search-settings: {e}") - return jsonify({"error": str(e)}), 500 + return jsonify({"error": "An internal error has occurred."}), 500 if __name__ == "__main__": diff --git a/End_to_end_Solutions/AOAISearchDemo/app/backend/data_client/data_client.py b/End_to_end_Solutions/AOAISearchDemo/app/backend/data_client/data_client.py index 16beca3c..db83ea46 100644 --- a/End_to_end_Solutions/AOAISearchDemo/app/backend/data_client/data_client.py +++ b/End_to_end_Solutions/AOAISearchDemo/app/backend/data_client/data_client.py @@ -12,6 +12,26 @@ from typing import List, Optional from common.logging.log_helper import CustomLogger +from urllib.parse import urlparse +from urllib.parse import urlparse, urljoin + +def _validate_base_uri(self, base_uri: str): + parsed_uri = urlparse(base_uri) + if parsed_uri.scheme not in ["http", "https"]: + raise ValueError("Invalid URI scheme") + if not parsed_uri.netloc: + raise ValueError("Invalid URI netloc") + +def _validate_base_uri(self, base_uri: str): + parsed_uri = urlparse(base_uri) + if parsed_uri.scheme not in ["http", "https"]: + raise ValueError("Invalid URI scheme") + if not parsed_uri.netloc: + raise ValueError("Invalid URI netloc") + +def _validate_path(self, path: str): + if not path.startswith("/"): + raise ValueError("Invalid path") class DataClient: class HttpMethod(Enum): @@ -21,8 +41,22 @@ class HttpMethod(Enum): DELETE="DELETE" def __init__(self, base_uri: str, logger: CustomLogger): + self._validate_base_uri(base_uri) self.base_uri = base_uri + self.base_uri = self._validate_base_uri(base_uri) self.logger = logger + + def _validate_base_uri(self, base_uri: str) -> str: + # Ensure the base_uri is a trusted URL + if not base_uri.startswith("https://trusted-domain.com"): + raise ValueError("Invalid base URI") + return base_uri + + def _sanitize_path(self, path: str) -> str: + # Sanitize the path to prevent malicious input + if ".." in path or path.startswith("/"): + raise ValueError("Invalid path") + return path def check_chat_session(self, user_id: str, conversation_id: str) -> bool: path = f"/check-chat-session/{user_id}/{conversation_id}" @@ -113,6 +147,8 @@ def get_user_resources(self, user_id: str) -> List[ResourceProfile]: @retry(reraise=True, stop = stop_after_attempt(3), wait = wait_exponential(multiplier = 1, max = 60)) def _make_request(self, path: str, method: HttpMethod, payload: Optional[dict] = None) -> str: + path = self._sanitize_path(path) + self._validate_path(path) headers = self.logger.get_converation_and_dialog_ids() properties = self.logger.get_updated_properties(headers) diff --git a/End_to_end_Solutions/AOAISearchDemo/app/data/app.py b/End_to_end_Solutions/AOAISearchDemo/app/data/app.py index d5377e9a..14c193eb 100644 --- a/End_to_end_Solutions/AOAISearchDemo/app/data/app.py +++ b/End_to_end_Solutions/AOAISearchDemo/app/data/app.py @@ -11,8 +11,9 @@ from data.managers.entities.api.manager import EntitiesManager from datetime import datetime from flask import Flask, Response, request +import html from typing import List, Set - +import logging # initialize config DefaultConfig.initialize() @@ -61,13 +62,13 @@ def create_chat_session(user_id: str, conversation_id: str): return Response(response=json.dumps(session.to_item()), status=201) except (TypeError, NullValueError, MissingPropertyError) as e: logger.exception(f"create-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=400) + return Response(response="A bad request error occurred.", status=400) except CosmosConflictError as e: logger.exception(f"create-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=409) + return Response(response="Conflict occurred while creating chat session.", status=409) except Exception as e: logger.exception(f"create-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=500) + return Response(response="An internal server error occurred.", status=500) @app.route('/chat-sessions//', methods=['GET']) def get_chat_session(user_id: str, conversation_id: str): @@ -84,14 +85,14 @@ def get_chat_session(user_id: str, conversation_id: str): properties = logger.get_updated_properties(addl_dim) if session is None: - logger.info(f"get-chat-session: session with conversation_id {conversation_id} not found", extra=properties) - return Response(response=f"Chat session with conversation_id {conversation_id} not found.", status=404) + logger.info(f"get-chat-session: session with conversation_id {html.escape(conversation_id)} not found", extra=properties) + return Response(response=f"Chat session with conversation_id {html.escape(conversation_id)} not found.", status=404) else: logger.info("get-chat-session: session found", extra=properties) return Response(response=json.dumps(session.to_item()), status=200) except Exception as e: logger.exception(f"get-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=500) + return Response(response="An internal server error occurred.", status=500) @app.route('/check-chat-session//', methods=['GET']) def check_chat_session(user_id: str, conversation_id: str): @@ -113,7 +114,7 @@ def check_chat_session(user_id: str, conversation_id: str): return Response(response="true", status=200) except Exception as e: logger.exception(f"check-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=500) + return Response(response="An internal error has occurred.", status=500) @app.route('/chat-sessions//', methods=['PUT']) def update_chat_session(user_id: str, conversation_id: str): @@ -147,13 +148,13 @@ def update_chat_session(user_id: str, conversation_id: str): return Response(response=json.dumps(session.to_item()), status=200) except (TypeError, NullValueError, MissingPropertyError, ValueError) as e: logger.exception(f"update-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=400) + return Response(response="An error occurred while processing your request.", status=400) except SessionNotFoundError as e: logger.exception(f"update-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=404) + return Response(response="Chat session not found.", status=404) except Exception as e: logger.exception(f"update-chat-session: error: {e} ", extra=properties) - return Response(response=str(e), status=500) + return Response(response="An internal server error occurred.", status=500) @app.route('/chat-sessions//', methods=['DELETE']) def clear_chat_session(user_id: str, conversation_id: str): @@ -161,9 +162,11 @@ def clear_chat_session(user_id: str, conversation_id: str): chat_manager.clear_chat_session(user_id, conversation_id) return Response(status=200) except SessionNotFoundError as e: - return Response(response=str(e), status=404) + logger.exception(f"clear-chat-session: error: {e} ") + return Response(response="Chat session not found.", status=404) except Exception as e: - return Response(response=str(e), status=500) + logger.exception(f"clear-chat-session: error: {e} ") + return Response(response="An internal server error occurred.", status=500) @app.route('/user-profiles/', methods=['POST']) def create_user_profile(user_id: str): @@ -184,11 +187,12 @@ def create_user_profile(user_id: str): user_profile = entities_manager.create_user_profile(user_id, user_name, description, sample_questions) return Response(response=json.dumps(user_profile.to_item()), status=201) except (TypeError, NullValueError, MissingPropertyError) as e: - return Response(response=str(e), status=400) + return Response(response="Invalid request data.", status=400) except CosmosConflictError as e: - return Response(response=str(e), status=409) + return Response(response="Conflict occurred while creating user profile.", status=409) except Exception as e: - return Response(response=str(e), status=500) + logger.exception(f"create-user-profile: error: {e}") + return Response(response="An internal server error occurred.", status=500) @app.route('/user-profiles/', methods=['GET']) def get_user_profile(user_id: str): @@ -196,11 +200,13 @@ def get_user_profile(user_id: str): try: user_profile = entities_manager.get_user_profile(user_id) if user_profile is None: - return Response(response=f"User profile with user_id {user_id} not found.", status=404) + escaped_user_id = html.escape(user_id) + return Response(response=f"User profile with user_id {escaped_user_id} not found.", status=404) else: return Response(response=json.dumps(user_profile.to_item()), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while fetching user profile: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/user-profiles', methods=['GET']) def get_all_user_profiles(): @@ -209,7 +215,8 @@ def get_all_user_profiles(): json_user_profiles = [user_profile.to_item() for user_profile in user_profiles] return Response(response=json.dumps(json_user_profiles), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while fetching all user profiles: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/user-groups/', methods=['POST']) def create_user_group(group_id: str): @@ -229,33 +236,39 @@ def create_user_group(group_id: str): user_group = entities_manager.create_user_group(group_id, group_name, users) return Response(response=json.dumps(user_group.to_item()), status=201) except (TypeError, NullValueError, MissingPropertyError) as e: - return Response(response=str(e), status=400) + logging.error("A validation error occurred: %s", e, exc_info=True) + return Response(response="A validation error has occurred.", status=400) except CosmosConflictError as e: - return Response(response=str(e), status=409) + logging.error("A conflict error occurred while creating user group: %s", e, exc_info=True) + return Response(response="A conflict error has occurred.", status=409) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while creating user group: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/user-groups/', methods=['GET']) def get_user_group(group_id: str): try: user_group = entities_manager.get_user_group(group_id) if user_group is None: - return Response(response=f"User group with group_id {group_id} not found.", status=404) + escaped_group_id = html.escape(group_id) + return Response(response=f"User group with group_id {escaped_group_id} not found.", status=404) else: return Response(response=json.dumps(user_group.to_item()), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while fetching user group: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/user-groups/user/', methods=['GET']) def get_user_member_groups(user_id: str): try: user_groups = entities_manager.get_user_member_groups(user_id) if user_groups is None: - return Response(response=f"User with user_id {user_id} not found.", status=404) + return Response(response=f"User with user_id {html.escape(user_id)} not found.", status=404) else: return Response(response=json.dumps([user_group.to_item_no_users() for user_group in user_groups]), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while fetching user member groups: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/user-groups/', methods=['PUT']) def update_user_group(group_id: str): @@ -273,11 +286,14 @@ def update_user_group(group_id: str): user_group = entities_manager.add_users_to_user_group(group_id, new_users) return Response(response=json.dumps(user_group.to_item()), status=200) except (TypeError, NullValueError, MissingPropertyError, ValueError) as e: - return Response(response=str(e), status=400) + logging.error("An error occurred while updating user group: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=400) except SessionNotFoundError as e: - return Response(response=str(e), status=404) + logging.error("Session not found: %s", e, exc_info=True) + return Response(response="Session not found.", status=404) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while updating user group: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/resources/', methods=['POST']) def create_resource(resource_id: str): @@ -292,29 +308,33 @@ def create_resource(resource_id: str): resource = entities_manager.create_resource(resource_id, resource_type) return Response(response=json.dumps(resource.to_item()), status=201) except (TypeError, NullValueError, MissingPropertyError) as e: - return Response(response=str(e), status=400) + logging.error("An error occurred while creating resource: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=400) except CosmosConflictError as e: - return Response(response=str(e), status=409) + logging.error("A conflict occurred while creating resource: %s", e, exc_info=True) + return Response(response="A conflict occurred while creating the resource.", status=409) except Exception as e: - return Response(response=str(e), status=500) + logging.error("An error occurred while creating resource: %s", e, exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/resources/', methods=['GET']) def get_resource(resource_id: str): try: resource = entities_manager.get_resource(resource_id) if resource is None: - return Response(response=f"Resource with resource_id {resource_id} not found.", status=404) + return Response(response=f"Resource with resource_id {html.escape(resource_id)} not found.", status=404) else: return Response(response=json.dumps(resource.to_item()), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error(f"Error in get_resource: {e}", exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/resources/user/', methods=['GET']) def get_user_resources(user_id: str): try: user_profile = entities_manager.get_user_profile(user_id) if user_profile is None: - return Response(response=f"User with user_id {user_id} not found.", status=404) + return Response(response=f"User with user_id {html.escape(user_id)} not found.", status=404) user_groups = entities_manager.get_user_member_groups(user_id) resources = permissions_manager.get_user_resources(user_profile, user_groups) @@ -327,7 +347,8 @@ def get_user_resources(user_id: str): return Response(response="Could not find resource profile for resource ID {resource.resource_id}.", status=500) return Response(response=json.dumps([resource_profile.to_item() for resource_profile in resource_profiles]), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error(f"Error in get_user_resources: {e}", exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/access-rules/', methods=['POST']) def create_access_rule(rule_id: str): @@ -350,22 +371,26 @@ def create_access_rule(rule_id: str): access_rule = permissions_manager.create_access_rule(rule_id, resources, members) return Response(response=json.dumps(access_rule.to_item()), status=201) except (TypeError, NullValueError, MissingPropertyError) as e: - return Response(response=str(e), status=400) + logging.error(f"Validation error in create_access_rule: {e}", exc_info=True) + return Response(response="Invalid input provided.", status=400) except CosmosConflictError as e: - return Response(response=str(e), status=409) + logging.error(f"Cosmos conflict error in create_access_rule: {e}", exc_info=True) + return Response(response="A conflict occurred while processing your request.", status=409) except Exception as e: - return Response(response=str(e), status=500) + logging.error(f"Error in create_access_rule: {e}", exc_info=True) + return Response(response="An internal error has occurred.", status=500) @app.route('/access-rules/', methods=['GET']) def get_access_rule(rule_id: str): try: access_rule = permissions_manager.get_access_rule(rule_id) if access_rule is None: - return Response(response=f"Access rule with rule_id {rule_id} not found.", status=404) + return Response(response=f"Access rule with rule_id {html.escape(rule_id)} not found.", status=404) else: return Response(response=json.dumps(access_rule.to_item()), status=200) except Exception as e: - return Response(response=str(e), status=500) + logging.error(f"Error in get_access_rule: {e}", exc_info=True) + return Response(response="An internal error has occurred.", status=500) def get_log_properties(request, user_id: str) -> dict: conversation_id = request.headers.get('Conversation-Id') diff --git a/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js b/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js index 5f84d461..18c6b32a 100644 --- a/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js +++ b/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js @@ -1073,7 +1073,7 @@ return; } - var target = $(selector)[0]; + var target = $.find(selector)[0]; if (!target || !$(target).hasClass(ClassName$2.CAROUSEL)) { return; diff --git a/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js index cdf7fb73..d88d9cbd 100644 --- a/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js +++ b/End_to_end_Solutions/AOAIVirtualAssistant/src/botwebapp/Bot.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js @@ -146,6 +146,11 @@ selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''; } + // Sanitize the selector to prevent XSS + var tempDiv = document.createElement('div'); + tempDiv.appendChild(document.createTextNode(selector)); + selector = tempDiv.innerHTML; + try { return document.querySelector(selector) ? selector : null; } catch (err) { diff --git a/End_to_end_Solutions/InsightsGenerator/insights_generator/core/OAI_client.py b/End_to_end_Solutions/InsightsGenerator/insights_generator/core/OAI_client.py index 0ac8d020..f427761b 100644 --- a/End_to_end_Solutions/InsightsGenerator/insights_generator/core/OAI_client.py +++ b/End_to_end_Solutions/InsightsGenerator/insights_generator/core/OAI_client.py @@ -3,10 +3,18 @@ import os import pdb import tiktoken +import urllib.parse + +def is_valid_url(url): + parsed_url = urllib.parse.urlparse(url) + return parsed_url.scheme in ["http", "https"] and parsed_url.netloc != "" def make_prompt_request(prompt, max_tokens = 2048, timeout = 4): - #url = "https://api.openai.com/v1/embeddings" + # Whitelist of allowed URLs + allowed_urls = ["https://api.openai.com/v1/embeddings", "https://another-trusted-url.com"] url = os.getenv("AOAI_ENDPOINT") + if not is_valid_url(url) or url not in allowed_urls: + raise ValueError("The provided URL is not allowed.") key = os.getenv("AOAI_KEY") payload_dict = {"prompt": prompt,