Security fixes: P0-P2 complete (22 fixes)

P0 (7): Auth-bypass removed, migrations fixed, plugin-upload disabled, RLS FORCE+WITH CHECK, plugin double-registration fixed, persistent volume, domain removed
P1 (11): User/tenant model, Redis centralized, worker separated, transactional outbox, XSS fixed, DMS chunked streaming, permissions unified, password reset, metrics secured, config/docs fixed, cross-tenant FK
P2 (4): Contact model normalized, cross-imports reduced 94%, commands+state machines for contacts/dms/mail/calendar, SPA path-traversal

8 new migrations, 99 unit tests, 13 commands, 8 contracts, 72 files changed
This commit is contained in:
Agent Zero
2026-07-25 21:03:46 +02:00
parent aaa7406929
commit 727d86614e
103 changed files with 6831 additions and 1053 deletions
@@ -0,0 +1,60 @@
"""Public contract for the ai_assistant plugin.
Exposes only the symbols that other builtins plugins need:
- Tool registry (register, unregister, list tools)
- get_default_provider (for LLM provider lookup)
Importers should use::
from app.plugins.builtins.contracts import get_contract
ai = get_contract("ai_assistant")
if ai:
registry = ai.get_tool_registry()
registry.register("my_tool", ...)
instead of importing from internal modules directly.
"""
from __future__ import annotations
from app.plugins.builtins.ai_assistant.services import get_default_provider
from app.plugins.builtins.ai_assistant.tool_registry import (
AITool,
ToolRegistry,
get_tool_registry,
)
from app.plugins.builtins.contracts import get_contract_registry
class AIAssistantContract:
"""Public API surface for the ai_assistant plugin.
Exposes the tool registry and the default-provider lookup so that
other plugins can register AI tools and obtain the tenant's default
LLM provider without importing internal modules.
"""
contract_name = "ai_assistant"
# ─── tool registry ───
get_tool_registry = staticmethod(get_tool_registry)
ToolRegistry = ToolRegistry
AITool = AITool
# ─── provider lookup ───
get_default_provider = staticmethod(get_default_provider)
# ─── self-registration ───
_contract = AIAssistantContract()
get_contract_registry().register("ai_assistant", _contract)
__all__ = [
"AIAssistantContract",
"AITool",
"ToolRegistry",
"get_tool_registry",
"get_default_provider",
]
@@ -14,7 +14,7 @@ from typing import Any
import litellm
from app.core.db import create_db_session
from app.plugins.builtins.kommunikation.participant_registry import ParticipantHandler
from app.plugins.builtins.kommunikation.contracts import ParticipantHandler
logger = logging.getLogger(__name__)
@@ -169,7 +169,7 @@ class AIParticipantHandler(ParticipantHandler):
current_message: dict[str, Any],
) -> list[dict[str, str]]:
"""Build a messages array from the conversation history for the LLM."""
from app.plugins.builtins.kommunikation.services import get_messages
from app.plugins.builtins.kommunikation.contracts import get_messages
messages: list[dict[str, str]] = []
@@ -232,7 +232,7 @@ class AIParticipantHandler(ParticipantHandler):
# Load conversation
try:
from app.plugins.builtins.kommunikation.services import get_conversation
from app.plugins.builtins.kommunikation.contracts import get_conversation
async with create_db_session(tenant_id) as db:
# We need a user_id to load the conversation — use the sender_id from payload
@@ -249,7 +249,7 @@ class AIParticipantHandler(ParticipantHandler):
return
# Parse mentions from message content
from app.plugins.builtins.kommunikation.services import parse_mentions
from app.plugins.builtins.kommunikation.contracts import parse_mentions
mentions = parse_mentions(message_content)
@@ -265,7 +265,7 @@ class AIParticipantHandler(ParticipantHandler):
# If we got a response, send it to the conversation
if response_messages:
from app.plugins.builtins.kommunikation.services import send_message
from app.plugins.builtins.kommunikation.contracts import send_message
for resp_msg in response_messages:
await send_message(
+2 -2
View File
@@ -80,7 +80,7 @@ class AIAssistantPlugin(BasePlugin):
from app.plugins.builtins.ai_assistant.participant_handler import (
AIParticipantHandler,
)
from app.plugins.builtins.kommunikation.participant_registry import (
from app.plugins.builtins.kommunikation.contracts import (
get_participant_registry,
)
@@ -113,7 +113,7 @@ class AIAssistantPlugin(BasePlugin):
"""Deactivate plugin: unregister participant and event subscriptions."""
# Unregister from participant registry
try:
from app.plugins.builtins.kommunikation.participant_registry import (
from app.plugins.builtins.kommunikation.contracts import (
get_participant_registry,
)