fix: code audit — security, correctness, and robustness fixes
Security:
- Path traversal: validate user_id, case_id, skill_name against safe regex
- Input sanitization in memory.py and skills.py file path construction
Correctness:
- ISR timezone: replace hardcoded UTC+3 with ZoneInfo("Asia/Jerusalem") for DST
- save_rule: use EspoCrmApiError.status_code instead of fragile string matching
- Background tasks: store references to prevent GC before completion
- Lock race: use dict.setdefault() for atomic lock creation
- Version: health.py now matches main.py (0.2.0)
- Skill names: normalized to lowercase for dedup consistency
Build:
- Dockerfile: install from pyproject.toml instead of hardcoded pip list
- Remove unused mcp>=1.0.0 dependency from pyproject.toml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,14 @@ import logging
|
||||
logger = logging.getLogger("shira.espocrm")
|
||||
|
||||
|
||||
class EspoCrmApiError(RuntimeError):
|
||||
"""Raised when the EspoCRM API returns a non-success status code."""
|
||||
|
||||
def __init__(self, status_code: int, text: str):
|
||||
self.status_code = status_code
|
||||
super().__init__(f"EspoCRM API error {status_code}: {text}")
|
||||
|
||||
|
||||
class EspoCrmClient:
|
||||
"""Thin async wrapper around the EspoCRM REST API."""
|
||||
|
||||
@@ -41,7 +49,7 @@ class EspoCrmClient:
|
||||
if not resp.is_success:
|
||||
text = resp.text
|
||||
logger.error("[crm-api] Error %s: %s", resp.status_code, text)
|
||||
raise RuntimeError(f"EspoCRM API error {resp.status_code}: {text}")
|
||||
raise EspoCrmApiError(resp.status_code, text)
|
||||
return resp.json()
|
||||
|
||||
async def get(self, endpoint: str) -> dict:
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from mcp_server.espocrm_client import EspoCrmClient
|
||||
|
||||
from mcp_server.espocrm_client import EspoCrmApiError
|
||||
from mcp_server.tools._helpers import normalize_date, ok, fail
|
||||
|
||||
VALID_STATUSES = {
|
||||
@@ -200,13 +201,15 @@ def register_crm_tools(
|
||||
"isActive": True,
|
||||
})
|
||||
return ok(f'כלל חדש נשמר: "{name}" (ID: {result["id"]})')
|
||||
except Exception as e:
|
||||
if "404" in str(e) and user_id:
|
||||
except EspoCrmApiError as e:
|
||||
if e.status_code == 404 and user_id:
|
||||
# AssistantRule entity not available — save to user profile instead
|
||||
from api.services.memory import update_user_profile
|
||||
entry = f"כלל [{scope}] {name}: {rule}"
|
||||
return await update_user_profile(user_id, "add", entry)
|
||||
return fail(f"שגיאה בשמירת כלל: {e}")
|
||||
except Exception as e:
|
||||
return fail(f"שגיאה בשמירת כלל: {e}")
|
||||
|
||||
# Register all tools
|
||||
tools["create_task"] = {
|
||||
|
||||
Reference in New Issue
Block a user