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.211"
VERSION = "0.250.212"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
2 changes: 2 additions & 0 deletions application/single_app/functions_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"databricks": "databricks",
"snowflake": "snowflake",
"tableau": "tableau",
"yamcs": "yamcs",
"chart": "chart",
"azure_maps": "azure_maps",
"blob_storage": "blob_storage",
Expand All @@ -98,6 +99,7 @@
"databricks": "Databricks",
"snowflake": "Snowflake",
"tableau": "Tableau",
"yamcs": "Yamcs",
"chart": "Chart",
"azure_maps": "Azure Maps",
"blob_storage": "Blob Storage",
Expand Down
8 changes: 8 additions & 0 deletions application/single_app/functions_keyvault.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import app_settings_cache
from functions_mcp_operations import MCP_CUSTOM_HEADERS_FIELD, MCP_PLUGIN_TYPE
from functions_snowflake_operations import SNOWFLAKE_PLUGIN_TYPE, SNOWFLAKE_SENSITIVE_ADDITIONAL_FIELDS
from functions_yamcs_operations import YAMCS_PLUGIN_TYPE, YAMCS_SENSITIVE_ADDITIONAL_FIELDS

Check warning on line 15 in application/single_app/functions_keyvault.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.

try:
from azure.identity import DefaultAzureCredential
Expand Down Expand Up @@ -407,6 +408,12 @@
return isinstance(plugin_type, str) and plugin_type.lower() == SNOWFLAKE_PLUGIN_TYPE


def _is_yamcs_plugin(plugin_dict):

Check warning on line 411 in application/single_app/functions_keyvault.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.
"""Return True when the plugin manifest is a Yamcs action."""

Check warning on line 412 in application/single_app/functions_keyvault.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.
plugin_type = (plugin_dict or {}).get("type", "")

Check warning on line 413 in application/single_app/functions_keyvault.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.
return isinstance(plugin_type, str) and plugin_type.lower() == YAMCS_PLUGIN_TYPE

Check warning on line 414 in application/single_app/functions_keyvault.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.


def _is_mcp_plugin(plugin_dict):
"""Return True when the plugin manifest is an MCP action."""
plugin_type = (plugin_dict or {}).get("type", "")
Expand All @@ -418,6 +425,7 @@
return (
_is_sql_sensitive_additional_field(plugin_dict, field_name)
or (_is_snowflake_plugin(plugin_dict) and field_name in SNOWFLAKE_SENSITIVE_ADDITIONAL_FIELDS)
or (_is_yamcs_plugin(plugin_dict) and field_name in YAMCS_SENSITIVE_ADDITIONAL_FIELDS)

Check warning on line 428 in application/single_app/functions_keyvault.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.
)


Expand Down
27 changes: 27 additions & 0 deletions application/single_app/functions_workspace_identities.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
ACTION_IDENTITY_SNOWFLAKE_AUTH_TYPES = {"api_key", "bearer_token", "username_password"}
ACTION_IDENTITY_TABLEAU_TYPES = {"tableau"}
ACTION_IDENTITY_TABLEAU_AUTH_TYPES = {"api_key", "username_password"}
ACTION_IDENTITY_YAMCS_TYPES = {"yamcs"}
ACTION_IDENTITY_YAMCS_AUTH_TYPES = {"api_key", "bearer_token", "username_password"}

Check warning on line 75 in application/single_app/functions_workspace_identities.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.

Check warning on line 75 in application/single_app/functions_workspace_identities.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.


def _now_iso() -> str:
Expand Down Expand Up @@ -470,6 +472,8 @@
return ACTION_IDENTITY_SNOWFLAKE_AUTH_TYPES
if plugin_type in ACTION_IDENTITY_TABLEAU_TYPES:
return ACTION_IDENTITY_TABLEAU_AUTH_TYPES
if plugin_type in ACTION_IDENTITY_YAMCS_TYPES:

Check warning on line 475 in application/single_app/functions_workspace_identities.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.
return ACTION_IDENTITY_YAMCS_AUTH_TYPES
return ACTION_IDENTITY_AUTH_TYPES


Expand Down Expand Up @@ -527,6 +531,8 @@
_apply_snowflake_action_identity_auth(action_auth, additional_fields, identity_auth)
elif plugin_type in ACTION_IDENTITY_TABLEAU_TYPES:
_apply_tableau_action_identity_auth(action_auth, additional_fields, identity_auth)
elif plugin_type in ACTION_IDENTITY_YAMCS_TYPES:

Check warning on line 534 in application/single_app/functions_workspace_identities.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.
_apply_yamcs_action_identity_auth(action_auth, additional_fields, identity_auth)
else:
_apply_generic_action_identity_auth(action_auth, identity_auth)

Expand Down Expand Up @@ -637,6 +643,27 @@
additional_fields["auth_method"] = "oauth"


def _apply_yamcs_action_identity_auth(
action_auth: Dict[str, Any],
additional_fields: Dict[str, Any],
identity_auth: Dict[str, Any],
) -> None:
auth_type = _normalize_text(identity_auth.get("auth_type"), 50).lower()
if auth_type == "username_password":
action_auth["type"] = "username_password"
action_auth["identity"] = identity_auth.get("username", "")
action_auth["key"] = identity_auth.get("password", "")
additional_fields["auth_method"] = "username_password"
elif auth_type == "api_key":
action_auth["type"] = "key"
action_auth["key"] = _identity_secret(identity_auth)
additional_fields["auth_method"] = "api_key"
elif auth_type == "bearer_token":
action_auth["type"] = "key"
action_auth["key"] = _identity_secret(identity_auth)
additional_fields["auth_method"] = "bearer_token"


def _apply_generic_action_identity_auth(action_auth: Dict[str, Any], identity_auth: Dict[str, Any]) -> None:
auth_type = _normalize_text(identity_auth.get("auth_type"), 50).lower()
if auth_type == "api_key":
Expand Down
162 changes: 162 additions & 0 deletions application/single_app/functions_yamcs_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# functions_yamcs_operations.py
"""Shared defaults and normalization helpers for Yamcs mission control action plugins."""

import re
from typing import Any, Dict, Optional


YAMCS_PLUGIN_TYPE = "yamcs"
YAMCS_DEFAULT_PROCESSOR = "realtime"
YAMCS_DEFAULT_PORT = 8090
YAMCS_SCHEME_PATTERN = re.compile(r"^https?://", re.IGNORECASE)

YAMCS_AUTH_METHOD_USERNAME_PASSWORD = "username_password"
YAMCS_AUTH_METHOD_API_KEY = "api_key"
YAMCS_AUTH_METHOD_BEARER_TOKEN = "bearer_token"
YAMCS_AUTH_METHOD_NONE = "none"
YAMCS_SUPPORTED_AUTH_METHODS = {
YAMCS_AUTH_METHOD_USERNAME_PASSWORD,
YAMCS_AUTH_METHOD_API_KEY,
YAMCS_AUTH_METHOD_BEARER_TOKEN,
YAMCS_AUTH_METHOD_NONE,
}
YAMCS_SUPPORTED_AUTH_TYPES = {"NoAuth", "key", "identity", "username_password"}

# Yamcs archive SQL is a full engine that also supports DDL/DML. Only these leading
# keywords are accepted, and only when archive SQL is explicitly enabled.
YAMCS_ALLOWED_READ_STATEMENTS = {
"DESC",
"DESCRIBE",
"SELECT",
"SHOW",
}

# Secrets always live in auth.key, but the constant keeps redaction plumbing symmetric
# with the other connector action types.
YAMCS_SENSITIVE_ADDITIONAL_FIELDS = {
"api_key",
"access_token",
"password",
"token",
}

YAMCS_DEFAULT_MAX_ROWS = 500
YAMCS_MIN_MAX_ROWS = 1
YAMCS_MAX_MAX_ROWS = 5000
YAMCS_DEFAULT_TIMEOUT = 30
YAMCS_MIN_TIMEOUT = 1
YAMCS_MAX_TIMEOUT = 300
YAMCS_DEFAULT_BYTE_LIMIT = 250000
YAMCS_MIN_BYTE_LIMIT = 1000
YAMCS_MAX_BYTE_LIMIT = 2000000


def _as_bool(value: Any, default: bool = False) -> bool:
if isinstance(value, bool):
return value
if value in [None, ""]:
return default
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "on"}
return bool(value)


def _as_int(value: Any, default: int, minimum: int, maximum: int) -> int:
try:
parsed_value = int(value)
except (TypeError, ValueError):
parsed_value = default
return max(minimum, min(maximum, parsed_value))


def normalize_yamcs_server_url(endpoint: Any) -> str:
"""Normalize a Yamcs server base URL so the Yamcs client can derive TLS from the scheme.

Yamcs addresses are commonly written as ``host:port``. ``urlparse`` misreads that form as
a scheme, so the scheme is detected explicitly rather than inferred from ``urlparse``.
"""
value = str(endpoint or "").strip().rstrip("/")
if not value:
return ""

if not YAMCS_SCHEME_PATTERN.match(value):
value = f"https://{value}"
return value.rstrip("/")


def normalize_yamcs_auth_method(
additional_fields: Optional[Dict[str, Any]] = None,
auth_type: str = "username_password",
) -> str:
"""Return the Yamcs auth method represented by a manifest."""
fields = additional_fields if isinstance(additional_fields, dict) else {}
aliases = {
"apikey": YAMCS_AUTH_METHOD_API_KEY,
"api_key": YAMCS_AUTH_METHOD_API_KEY,
"bearer": YAMCS_AUTH_METHOD_BEARER_TOKEN,
"bearer_token": YAMCS_AUTH_METHOD_BEARER_TOKEN,
"access_token": YAMCS_AUTH_METHOD_BEARER_TOKEN,
"token": YAMCS_AUTH_METHOD_BEARER_TOKEN,
"noauth": YAMCS_AUTH_METHOD_NONE,
"none": YAMCS_AUTH_METHOD_NONE,
"anonymous": YAMCS_AUTH_METHOD_NONE,
"password": YAMCS_AUTH_METHOD_USERNAME_PASSWORD,
"username_password": YAMCS_AUTH_METHOD_USERNAME_PASSWORD,
}

explicit_method = str(fields.get("auth_method") or "").strip().lower().replace("-", "_")
explicit_method = aliases.get(explicit_method, explicit_method)
if explicit_method in YAMCS_SUPPORTED_AUTH_METHODS:
return explicit_method

identity_auth_type = str(fields.get("identity_auth_type") or "").strip().lower().replace("-", "_")
identity_auth_type = aliases.get(identity_auth_type, identity_auth_type)
if identity_auth_type in YAMCS_SUPPORTED_AUTH_METHODS:
return identity_auth_type

normalized_auth_type = str(auth_type or "").strip()
if normalized_auth_type == "NoAuth":
return YAMCS_AUTH_METHOD_NONE
if normalized_auth_type == "key":
return YAMCS_AUTH_METHOD_API_KEY
return YAMCS_AUTH_METHOD_USERNAME_PASSWORD


def normalize_yamcs_additional_fields(
additional_fields: Optional[Dict[str, Any]] = None,
auth_type: str = "username_password",
) -> Dict[str, Any]:
"""Normalize Yamcs additionalFields with bounded, read-only-safe defaults."""
fields = dict(additional_fields or {}) if isinstance(additional_fields, dict) else {}
fields["server_url"] = normalize_yamcs_server_url(
fields.get("server_url") or fields.get("serverUrl") or ""
)
fields["instance"] = str(fields.get("instance") or fields.get("yamcs_instance") or "").strip()
fields["processor"] = str(
fields.get("processor") or fields.get("yamcs_processor") or ""
).strip() or YAMCS_DEFAULT_PROCESSOR
fields["auth_method"] = normalize_yamcs_auth_method(fields, auth_type=auth_type)
fields["tls_verify"] = _as_bool(fields.get("tls_verify"), default=True)
# Yamcs actions never issue commands or write parameters; the flag is stored for parity
# with the other connector action types and is always forced on.
fields["read_only"] = True
fields["enable_archive_sql"] = _as_bool(fields.get("enable_archive_sql"), default=False)
fields["max_rows"] = _as_int(
fields.get("max_rows"),
YAMCS_DEFAULT_MAX_ROWS,
YAMCS_MIN_MAX_ROWS,
YAMCS_MAX_MAX_ROWS,
)
fields["timeout"] = _as_int(
fields.get("timeout"),
YAMCS_DEFAULT_TIMEOUT,
YAMCS_MIN_TIMEOUT,
YAMCS_MAX_TIMEOUT,
)
fields["byte_limit"] = _as_int(
fields.get("byte_limit"),
YAMCS_DEFAULT_BYTE_LIMIT,
YAMCS_MIN_BYTE_LIMIT,
YAMCS_MAX_BYTE_LIMIT,
)
return fields
1 change: 1 addition & 0 deletions application/single_app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ ffmpeg-python==0.2.0
semantic-kernel==1.39.4
snowflake-connector-python[pandas]==3.18.0
tableauserverclient==0.40
yamcs-client==2.1.0
protobuf==6.33.5
redis==5.3.1
smbprotocol==1.15.0
Expand Down
Loading
Loading