diff --git a/meshai/config.py b/meshai/config.py index e732efc..1e933b3 100644 --- a/meshai/config.py +++ b/meshai/config.py @@ -64,6 +64,7 @@ class SafetyConfig: emergency_keywords: list[str] = field( default_factory=lambda: ["emergency", "help", "sos"] ) # Always respond to these + prompt_injection_guard: bool = True # Basic prompt injection detection @dataclass diff --git a/meshai/router.py b/meshai/router.py index 8e33193..ce6239f 100644 --- a/meshai/router.py +++ b/meshai/router.py @@ -33,6 +33,17 @@ class RouteResult: query: Optional[str] = None # For LLM, the cleaned query +# Patterns that suggest prompt injection attempts +_INJECTION_PATTERNS = [ + re.compile(r"ignore\s+(all\s+)?previous", re.IGNORECASE), + re.compile(r"ignore\s+your\s+instructions", re.IGNORECASE), + re.compile(r"disregard\s+(all\s+)?previous", re.IGNORECASE), + re.compile(r"you\s+are\s+now\b", re.IGNORECASE), + re.compile(r"new\s+instructions?\s*:", re.IGNORECASE), + re.compile(r"system\s*prompt\s*:", re.IGNORECASE), +] + + class MessageRouter: """Routes incoming messages to appropriate handlers.""" @@ -186,12 +197,28 @@ class MessageRouter: logger.debug(f"Persisted summary for {user_id}") def _clean_query(self, text: str) -> str: - """Remove @mention from query text.""" + """Remove @mention and check for prompt injection.""" # Remove @botname mention cleaned = self._mention_pattern.sub("", text) # Clean up extra whitespace cleaned = " ".join(cleaned.split()) - return cleaned.strip() + cleaned = cleaned.strip() + + # Check for prompt injection if guard is enabled + if self.config.safety.prompt_injection_guard: + for pattern in _INJECTION_PATTERNS: + if pattern.search(cleaned): + logger.warning( + f"Possible prompt injection detected: {cleaned[:80]}..." + ) + # Truncate to just the part before the injection pattern + match = pattern.search(cleaned) + cleaned = cleaned[:match.start()].strip() + if not cleaned: + cleaned = "Hello" + break + + return cleaned def _make_command_context(self, message: MeshMessage) -> CommandContext: """Create command context from message.""" diff --git a/meshai/web_status.py b/meshai/web_status.py index 7824431..63e09a3 100644 --- a/meshai/web_status.py +++ b/meshai/web_status.py @@ -1,6 +1,7 @@ """Simple web status page for MeshAI.""" import asyncio +import html as html_module import json import logging import threading @@ -159,6 +160,31 @@ class StatusRequestHandler(BaseHTTPRequestHandler): include_activity=self.config.show_recent_activity if self.config else False ) + esc = html_module.escape + + # Build optional stat rows + rows = "" + if self.config and self.config.show_uptime: + rows += ( + '
Uptime' + f'{esc(str(status["uptime"]))}
' + ) + if self.config and self.config.show_message_count: + rows += ( + '
Messages' + f'{esc(str(status["messages_received"]))}
' + '
Responses' + f'{esc(str(status["responses_sent"]))}
' + ) + if self.config and self.config.show_connected_nodes: + rows += ( + '
Connected Nodes' + f'{esc(str(status["connected_nodes"]))}
' + ) + + status_class = "status-fallback" if status.get("using_fallback") else "status-online" + status_text = "ONLINE (Fallback)" if status.get("using_fallback") else "ONLINE" + html = f""" @@ -192,17 +218,12 @@ class StatusRequestHandler(BaseHTTPRequestHandler):

MeshAI Status

Status - - {'ONLINE (Fallback)' if status.get('using_fallback') else 'ONLINE'} - + {esc(status_text)}
- {'
Uptime' + status["uptime"] + '
' if self.config and self.config.show_uptime else ''} - {'
Messages' + str(status["messages_received"]) + '
' if self.config and self.config.show_message_count else ''} - {'
Responses' + str(status["responses_sent"]) + '
' if self.config and self.config.show_message_count else ''} - {'
Connected Nodes' + str(status["connected_nodes"]) + '
' if self.config and self.config.show_connected_nodes else ''} + {rows}
Errors - {status["errors"]} + {esc(str(status["errors"]))}