fix: embedding migration, worker error handling, path traversal security, response mismatches (unread-count, plugins, manifests), frontend type safety

This commit is contained in:
Agent Zero
2026-07-24 14:23:28 +02:00
parent c3c5233e58
commit 7b4a2c0791
10 changed files with 161 additions and 31 deletions
+4
View File
@@ -346,6 +346,10 @@ def create_app() -> FastAPI:
# Don't intercept API routes
if full_path.startswith(("api/", "docs", "openapi", "redoc")):
raise HTTPException(status_code=404, detail="Not Found")
# Block path traversal and system file access
blocked_prefixes = ("var/log/", "error/", "error_log", "var/", "etc/", "proc/", "sys/")
if full_path.startswith(blocked_prefixes) or "/../" in full_path or full_path.endswith("/.."):
raise HTTPException(status_code=404, detail="Not Found")
index_path = os.path.join(frontend_dist, "index.html")
if os.path.isfile(index_path):
return FileResponse(index_path)
@@ -42,6 +42,10 @@ async def index_mails(ctx: dict[str, Any], mail_ids: list[str]) -> None:
await index_entity("mail", eid, tenant_id, db)
except Exception:
logger.exception("Failed to index mail %s", mail_id)
try:
await db.rollback()
except Exception:
pass
async def index_file(ctx: dict[str, Any], file_id: str) -> None:
@@ -98,6 +102,10 @@ async def index_file(ctx: dict[str, Any], file_id: str) -> None:
logger.info("Indexed file %s", file_id)
except Exception:
logger.exception("Failed to index file %s", file_id)
try:
await db.rollback()
except Exception:
pass
async def index_contact(ctx: dict[str, Any], contact_id: str) -> None:
@@ -119,6 +127,10 @@ async def index_contact(ctx: dict[str, Any], contact_id: str) -> None:
await index_entity("contact", eid, row["tenant_id"], db)
except Exception:
logger.exception("Failed to index contact %s", contact_id)
try:
await db.rollback()
except Exception:
pass
async def index_event(ctx: dict[str, Any], event_id: str) -> None:
@@ -140,6 +152,10 @@ async def index_event(ctx: dict[str, Any], event_id: str) -> None:
await index_entity("event", eid, row["tenant_id"], db)
except Exception:
logger.exception("Failed to index event %s", event_id)
try:
await db.rollback()
except Exception:
pass
async def reindex(ctx: dict[str, Any], entity_type: str) -> None:
@@ -182,6 +198,10 @@ async def reindex(ctx: dict[str, Any], entity_type: str) -> None:
)
except Exception:
logger.exception("Reindex failed for %s/%s", entity_type, row["id"])
try:
await db.rollback()
except Exception:
pass
offset += BATCH_SIZE
logger.info("Reindex complete for %s", entity_type)
@@ -217,7 +237,15 @@ async def embedding_batch(ctx: dict[str, Any]) -> None:
await index_entity(etype, row["id"], row["tenant_id"], db)
except Exception:
logger.exception("Batch index failed for %s/%s", etype, row["id"])
try:
await db.rollback()
except Exception:
pass
except Exception:
logger.exception("Batch query failed for %s", etype)
try:
await db.rollback()
except Exception:
pass
logger.info("Embedding batch job complete")
@@ -1,5 +1,8 @@
-- Unified Search: Embedding columns and HNSW indexes
-- Ensure pgvector extension is installed
CREATE EXTENSION IF NOT EXISTS vector;
-- ─── Mails ───
ALTER TABLE mails ADD COLUMN IF NOT EXISTS embedding vector(768);
CREATE INDEX IF NOT EXISTS ix_mails_embedding ON mails USING hnsw(embedding vector_cosine_ops);
+2 -2
View File
@@ -67,7 +67,7 @@ async def mark_notification_read_endpoint(
}
@router.get("/unread-count", response_model=UnreadCountResponse)
@router.get("/unread-count")
async def unread_count_endpoint(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("notifications:read")),
@@ -76,7 +76,7 @@ async def unread_count_endpoint(
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
count = await get_unread_count(db, tenant_id, user_id)
return {"count": count}
return count
@router.get("/types")
+2 -2
View File
@@ -50,7 +50,7 @@ async def list_plugins(
"""List all plugins with their current status (discovered, installed, active, inactive)."""
service = get_plugin_service()
plugins = await service.list_plugins(db)
return {"plugins": plugins, "total": len(plugins)}
return plugins
@router.get("/manifest")
@@ -76,7 +76,7 @@ async def get_active_manifests(
"""
service = get_plugin_service()
manifests = await service.get_active_manifests(db)
return {"plugins": manifests, "total": len(manifests)}
return manifests
@router.get("/{name}/config")