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: 2 additions & 0 deletions application/single_app/functions_global_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
validate_scoped_mcp_action,
)
from functions_settings import get_settings
from functions_mcp_tool_pinning import validate_mcp_tool_pinning_for_save

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

Check warning on line 34 in application/single_app/functions_global_actions.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 _clean_action(action, return_type, action_id=None):
Expand Down Expand Up @@ -165,6 +166,7 @@
existing_action = None

validate_legacy_action_update(submitted_action, existing_action)
validate_mcp_tool_pinning_for_save(action_data, existing_action)

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

Check warning on line 169 in application/single_app/functions_global_actions.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.
legacy_type = is_legacy_msgraph_type(action_data.get('type'))
if legacy_type:
action_data['type'] = 'msgraph'
Expand Down
2 changes: 2 additions & 0 deletions application/single_app/functions_group_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
)
from functions_settings import get_settings
from functions_agent_delegation import validate_agent_action_for_scope
from functions_mcp_tool_pinning import validate_mcp_tool_pinning_for_save

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

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


_NAME_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$")
Expand Down Expand Up @@ -154,6 +155,7 @@
except exceptions.CosmosResourceNotFoundError:
pass
validate_legacy_action_update(submitted_action, existing_action, 'group_id', group_id)
validate_mcp_tool_pinning_for_save(payload, existing_action)

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

Check warning on line 158 in application/single_app/functions_group_actions.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.
legacy_type = is_legacy_msgraph_type(payload.get('type'))
if legacy_type:
payload['type'] = 'msgraph'
Expand Down
61 changes: 61 additions & 0 deletions application/single_app/functions_mcp_fingerprint_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# functions_mcp_fingerprint_metadata.py
"""Shared MCP prompt and tool fingerprint metadata normalization."""

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

from typing import Any, Dict, List


MCP_TOOL_FINGERPRINTS_FIELD = "mcp_tool_fingerprints"

Check warning on line 7 in application/single_app/functions_mcp_fingerprint_metadata.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.
MCP_TOOL_DRIFT_FIELD = "mcp_tool_drift"
MCP_PROMPTS_FIELD = "mcp_prompts"


def normalize_mcp_prompt_metadata(value: Any) -> List[Dict[str, Any]]:
"""Return normalized MCP prompt metadata entries."""
if not isinstance(value, list):
return []

prompts = []
seen_names = set()
for prompt in value:
if not isinstance(prompt, dict):
continue
name = str(prompt.get("name") or "").strip()
if not name or name in seen_names:
continue
seen_names.add(name)
arguments = prompt.get("arguments")
if not isinstance(arguments, list):
arguments = []
prompts.append({
"name": name,
"description": str(prompt.get("description") or "").strip(),
"arguments": [
argument for argument in arguments
if isinstance(argument, dict) and str(argument.get("name") or "").strip()
],
})
return prompts


def normalize_mcp_tool_fingerprints(value: Any) -> Dict[str, Any]:
"""Return a normalized approved fingerprint object, or an empty dict."""
if not isinstance(value, dict):
return {}
manifest_hash = str(value.get("manifest_hash") or "").strip()
tools = value.get("tools") if isinstance(value.get("tools"), dict) else {}
prompts = value.get("prompts") if isinstance(value.get("prompts"), dict) else {}
normalized = {
"manifest_hash": manifest_hash,
"tools": {
str(name): str(fingerprint)
for name, fingerprint in tools.items()
if str(name).strip() and str(fingerprint).strip()
},
"prompts": {
str(name): str(fingerprint)
for name, fingerprint in prompts.items()
if str(name).strip() and str(fingerprint).strip()
},
"discovered_at": str(value.get("discovered_at") or "").strip(),
}
return normalized if normalized["manifest_hash"] and normalized["discovered_at"] else {}
17 changes: 17 additions & 0 deletions application/single_app/functions_mcp_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
from jsonschema.exceptions import SchemaError

from functions_action_manifest import McpConfigurationError, McpStdioRemovedError
from functions_mcp_fingerprint_metadata import (
MCP_PROMPTS_FIELD,
MCP_TOOL_DRIFT_FIELD,
MCP_TOOL_FINGERPRINTS_FIELD,
normalize_mcp_prompt_metadata,
normalize_mcp_tool_fingerprints,
)


MCP_PLUGIN_TYPE = "mcp"
Expand Down Expand Up @@ -601,6 +608,16 @@ def normalize_mcp_additional_fields(additional_fields):
normalized_fields.get("allowed_tool_names")
)
normalized_fields["mcp_tools"] = normalize_mcp_tool_metadata(normalized_fields.get("mcp_tools"))
normalized_fields[MCP_PROMPTS_FIELD] = normalize_mcp_prompt_metadata(
normalized_fields.get(MCP_PROMPTS_FIELD)
)
fingerprints = normalize_mcp_tool_fingerprints(normalized_fields.get(MCP_TOOL_FINGERPRINTS_FIELD))
if fingerprints:
normalized_fields[MCP_TOOL_FINGERPRINTS_FIELD] = fingerprints
elif MCP_TOOL_FINGERPRINTS_FIELD in normalized_fields:
normalized_fields.pop(MCP_TOOL_FINGERPRINTS_FIELD, None)
if normalized_fields.get(MCP_TOOL_DRIFT_FIELD) is None:
normalized_fields.pop(MCP_TOOL_DRIFT_FIELD, None)

for retired_field in ("command", "args", "env"):
normalized_fields.pop(retired_field, None)
Expand Down
Loading
Loading