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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.250.215"
VERSION = "0.250.216"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
166 changes: 166 additions & 0 deletions application/single_app/route_backend_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@
build_sql_server_odbc_connection_string,
connect_with_sql_server_odbc_fallback,
)
from semantic_kernel_plugins.rocksdb_plugin import (

Check warning on line 20 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
AUTH_SCHEME_API_KEY,

Check warning on line 21 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
AUTH_SCHEME_BEARER,

Check warning on line 22 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
AUTH_SCHEME_NONE,
DEFAULT_API_KEY_HEADER,

Check warning on line 24 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
SUPPORTED_AUTH_SCHEMES,
normalize_rocksdb_base_url,
)
from functions_settings import get_settings, is_tabular_processing_enabled, update_settings
from functions_authentication import *
from functions_appinsights import log_event
from swagger_wrapper import swagger_route, get_auth_security
import logging
import os
import requests
from functions_debug import debug_print
import importlib.util
from functions_plugins import get_merged_plugin_settings
Expand Down Expand Up @@ -456,6 +465,23 @@
},
'metadata': {'description': 'Example Cosmos query plugin'}
}
elif 'rocksdb' in module_name.lower():
safe_manifest = {
'endpoint': 'https://rocksdb.example.com/api',

Check warning on line 470 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.
'auth': {'type': 'NoAuth'},
'additionalFields': {
'base_url': 'https://rocksdb.example.com/api',

Check warning on line 473 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.
'auth_scheme': AUTH_SCHEME_NONE,
'column_family': 'default',
'key_encoding': 'utf8',
'value_encoding': 'utf8',
'read_only': True,
'max_results': 100,
'max_value_bytes': 32768,
'timeout': 30,
},
'metadata': {'description': 'Example RocksDB key-value action'}
}
elif 'blob_storage' in module_name.lower():
safe_manifest = {
'endpoint': 'https://example.blob.core.windows.net',
Expand Down Expand Up @@ -2702,3 +2728,143 @@
client.close()
except Exception:
pass


@bpap.route('/api/plugins/test-rocksdb-connection', methods=['POST'])

Check warning on line 2733 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
@swagger_route(security=get_auth_security())

Check warning on line 2734 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
@login_required

Check warning on line 2735 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
@user_required

Check warning on line 2736 in application/single_app/route_backend_plugins.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
def test_rocksdb_connection():
"""Probe a RocksDB HTTP service and confirm it answers with the expected contract."""
data = request.get_json(silent=True) or {}
user_id = get_current_user_id()
auth_scheme = (data.get('auth_scheme') or AUTH_SCHEME_NONE).strip().lower()
api_key_header = (data.get('api_key_header') or '').strip() or DEFAULT_API_KEY_HEADER
auth_key = (data.get('auth_key') or '').strip()
timeout = min(max(int(data.get('timeout', 10) or 10), 1), 30)

try:
base_url = normalize_rocksdb_base_url(data.get('base_url') or '')
except ValueError:
return jsonify({
'success': False,
'error': 'The RocksDB service base URL is invalid. Use an http or https URL that includes a host name.'
}), 400

if not base_url:
return jsonify({'success': False, 'error': 'A RocksDB service base URL is required.'}), 400
if auth_scheme not in SUPPORTED_AUTH_SCHEMES:
return jsonify({'success': False, 'error': "Auth scheme must be one of 'none', 'bearer', or 'api_key'."}), 400

try:
existing_plugin = _load_existing_plugin_for_test(data.get('existing_plugin'), user_id)
except PermissionError:
return jsonify({'success': False, 'error': 'You do not have access to the selected action.'}), 403
except LookupError:
return jsonify({'success': False, 'error': 'The selected action could not be found.'}), 404
except ValueError:
return jsonify({'success': False, 'error': 'The selected action reference is invalid.'}), 400

if auth_scheme != AUTH_SCHEME_NONE:
existing_auth = {}
if isinstance(existing_plugin, dict) and isinstance(existing_plugin.get('auth'), dict):
existing_auth = existing_plugin['auth']

if auth_key == ui_trigger_word:
auth_key = existing_auth.get('key', '')
if auth_key == ui_trigger_word:
return jsonify({
'success': False,
'error': 'The stored RocksDB service token could not be resolved for testing. Re-enter the token.'
}), 400

try:
auth_key = _resolve_secret_value_for_plugin_test(auth_key, 'auth.key', plugin_label='RocksDB')
except ValueError:
return jsonify({
'success': False,
'error': 'The stored RocksDB service token could not be resolved. Re-enter the token.'
}), 400

if not auth_key:
return jsonify({
'success': False,
'error': 'A service token is required when the auth scheme is bearer or api_key.'
}), 400

headers = {'Accept': 'application/json'}
if auth_scheme == AUTH_SCHEME_BEARER:
headers['Authorization'] = f'Bearer {auth_key}'
elif auth_scheme == AUTH_SCHEME_API_KEY:
headers[api_key_header] = auth_key

try:
response = requests.get(
f'{base_url}/health',
headers=headers,
timeout=timeout,
)

if response.status_code == 404:
# Minimal services may not implement /health, so fall back to a single-item scan.
response = requests.post(
f'{base_url}/scan',
json={'limit': 1},
headers=headers,
timeout=timeout,
)

if response.status_code in (401, 403):
return jsonify({
'success': False,
'error': 'The RocksDB service rejected the supplied credentials. Verify the auth scheme and token.'
}), 403

if response.status_code >= 400:
return jsonify({
'success': False,
'error': f'The RocksDB service returned HTTP {response.status_code}.'
}), 400

try:
response.json()
except ValueError:
return jsonify({
'success': False,
'error': 'The RocksDB service returned a non-JSON response. Confirm the base URL points at the service API root.'
}), 400

log_event(
'[PLUGINS] RocksDB connection test succeeded',
extra={'user_id': user_id, 'auth_scheme': auth_scheme, 'status_code': response.status_code},
level=logging.INFO,
)
return jsonify({
'success': True,
'message': f'Successfully reached the RocksDB service at {base_url}.'
})
except requests.exceptions.SSLError:
return jsonify({
'success': False,
'error': 'TLS verification failed for the RocksDB service. Install the issuing certificate authority in the application trust store.'
}), 400
except requests.exceptions.Timeout:
return jsonify({
'success': False,
'error': f'The RocksDB service did not respond within {timeout} seconds.'
}), 400
except Exception as exc:
log_event(
'[PLUGINS] RocksDB connection test failed',
extra={
'user_id': user_id,
'auth_scheme': auth_scheme,
'error_type': type(exc).__name__,
},
level=logging.WARNING,
exceptionTraceback=True,
)
return jsonify({
'success': False,
'error': 'The RocksDB service could not be reached. Verify the base URL and that the service is running.'
}), 400
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@
validate_mcp_endpoint_for_transport,
)
from functions_simplechat_operations import SIMPLECHAT_DEFAULT_ENDPOINT
from semantic_kernel_plugins.rocksdb_plugin import (
AUTH_SCHEME_NONE,
MAX_RESULTS_CEILING,
MAX_TIMEOUT,
MAX_VALUE_BYTES_CEILING,
ROCKSDB_PLUGIN_TYPE,
SUPPORTED_AUTH_SCHEMES,
SUPPORTED_KEY_ENCODINGS,
SUPPORTED_VALUE_ENCODINGS,
normalize_rocksdb_base_url,
)


class PluginHealthChecker:
Expand Down Expand Up @@ -346,7 +357,10 @@ def validate_plugin_manifest(manifest: Dict[str, Any], plugin_type: str) -> Tupl
errors.append("Cosmos plugin only supports auth.type values 'identity' and 'key'")
if auth_type == 'key' and not auth.get('key'):
errors.append("Cosmos plugin requires auth.key when auth.type='key'")


elif plugin_type == ROCKSDB_PLUGIN_TYPE:
errors.extend(PluginHealthChecker._validate_rocksdb_manifest(manifest))

elif plugin_type == 'log_analytics':
additional_fields = manifest.get('additionalFields', {})
if 'workspaceId' not in additional_fields:
Expand Down Expand Up @@ -441,7 +455,60 @@ def validate_plugin_manifest(manifest: Dict[str, Any], plugin_type: str) -> Tupl
errors.append("Azure Maps plugin requires auth.key with an Azure Maps subscription key")

return len(errors) == 0, errors


@staticmethod
def _validate_rocksdb_manifest(manifest: Dict[str, Any]) -> List[str]:
"""Validate a RocksDB action manifest against the RocksDB HTTP service contract."""
errors: List[str] = []
additional_fields = manifest.get('additionalFields', {})
if not isinstance(additional_fields, dict):
additional_fields = {}

auth = manifest.get('auth', {}) if isinstance(manifest.get('auth'), dict) else {}

key_encoding = str(additional_fields.get('key_encoding') or 'utf8').strip().lower()
if key_encoding not in SUPPORTED_KEY_ENCODINGS:
errors.append("RocksDB plugin key_encoding must be either 'utf8' or 'base64'")

value_encoding = str(additional_fields.get('value_encoding') or 'utf8').strip().lower()
if value_encoding not in SUPPORTED_VALUE_ENCODINGS:
errors.append("RocksDB plugin value_encoding must be one of 'utf8', 'base64', or 'json'")

for field_name, minimum, maximum in (
('max_results', 1, MAX_RESULTS_CEILING),
('max_value_bytes', 1, MAX_VALUE_BYTES_CEILING),
('timeout', 1, MAX_TIMEOUT),
):
raw_value = additional_fields.get(field_name)
if raw_value in (None, ''):
continue
try:
parsed_value = int(raw_value)
except (TypeError, ValueError):
errors.append(f"RocksDB plugin additionalFields.{field_name} must be an integer")
continue
if parsed_value < minimum or parsed_value > maximum:
errors.append(
f"RocksDB plugin additionalFields.{field_name} must be between {minimum} and {maximum}"
)

base_url = str(additional_fields.get('base_url') or manifest.get('endpoint') or '').strip()
if not base_url:
errors.append("RocksDB plugin requires 'base_url' in additionalFields")
else:
try:
normalize_rocksdb_base_url(base_url)
except ValueError as validation_error:
errors.append(f"RocksDB plugin base_url is invalid: {validation_error}")

auth_scheme = str(additional_fields.get('auth_scheme') or AUTH_SCHEME_NONE).strip().lower()
if auth_scheme not in SUPPORTED_AUTH_SCHEMES:
errors.append("RocksDB plugin auth_scheme must be one of 'none', 'bearer', or 'api_key'")
elif auth_scheme != AUTH_SCHEME_NONE and not auth.get('key'):
errors.append("RocksDB plugin requires auth.key when auth_scheme is 'bearer' or 'api_key'")

return errors

@staticmethod
def check_plugin_health(plugin_instance: BasePlugin, plugin_name: str) -> Dict[str, Any]:
"""
Expand Down
Loading
Loading