Skip to content
Open
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: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Yuxi 只交付完整知识能力路径。API 始终注册 `external_kb`、`knowl
- `composables` 封装请求排队、Run SSE、流式消息、审批、线程状态、提及和其他可组合逻辑。
- `utils` 放轻量转换和展示辅助;全局样式集中在 `assets/css`,颜色和基础规范优先复用 `base.css`。

`/` 是公开首页;登录后的核心工作区是 `/agent`。`/extensions` 对所有登录用户开放,其中 Skills 对普通用户可见,知识库、工具和 MCP 管理能力仅管理员可见;Dashboard 仅超级管理员可访问。后端权限检查始终是最终边界,前端守卫只负责页面体验。
`/` 是公开首页;登录后的核心工作区是 `/agent`。`/extensions` 对所有登录用户开放,其中 Skills 和已授权知识库对普通用户可见;知识库文档维护按单库授权开放,整库配置、工具和 MCP 管理要求管理员;Dashboard 仅超级管理员可访问。后端权限检查始终是最终边界,前端守卫只负责页面体验。

## 智能体运行链路

Expand Down
14 changes: 10 additions & 4 deletions backend/package/yuxi/knowledge/graphs/milvus_graph_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
normalize_entity_name,
)
from yuxi.knowledge.graphs.milvus_graph_vector_store import MilvusGraphVectorStore
from yuxi.knowledge.utils.security import redact_sensitive_params
from yuxi.repositories.knowledge_base_repository import KnowledgeBaseRepository
from yuxi.repositories.knowledge_chunk_repository import KnowledgeChunkRepository
from yuxi.repositories.knowledge_graph_repository import KnowledgeGraphRepository
Expand Down Expand Up @@ -129,7 +130,8 @@ async def get_graph_vector_store(self) -> MilvusGraphVectorStore:
def driver(self):
return self.connection.driver

async def get_status(self, kb_id: str, *, tasker: Any = None) -> dict[str, Any]:
async def get_status(self, kb_id: str, *, tasker: Any = None, redact_secrets: bool = True) -> dict[str, Any]:
"""返回图谱构建状态,默认隐藏配置中的凭据。"""
kb = await self._get_milvus_kb(kb_id)
params = dict(kb.additional_params or {})
config = params.get(GRAPH_CONFIG_KEY) or {}
Expand Down Expand Up @@ -175,7 +177,7 @@ async def get_status(self, kb_id: str, *, tasker: Any = None) -> dict[str, Any]:
"kb_type": kb.kb_type,
"configured": bool(config),
"locked": bool(config.get("locked")),
"config": self._public_config(config),
"config": self._public_config(config, redact_secrets=redact_secrets),
"total_chunks": total_chunks,
"pending_chunks": pending_chunks,
"indexed_chunks": indexed_chunks,
Expand Down Expand Up @@ -1043,13 +1045,17 @@ def _get_locked_config(self, additional_params: dict[str, Any]) -> dict[str, Any
raise ValueError("图谱抽取配置缺少 extractor_type")
return config

def _public_config(self, config: dict[str, Any]) -> dict[str, Any] | None:
def _public_config(self, config: dict[str, Any], *, redact_secrets: bool = True) -> dict[str, Any] | None:
"""组装状态响应配置,保留运行时使用的原始参数。"""
if not config:
return None
extractor_options = self._runtime_extractor_options(config)
if redact_secrets:
extractor_options = redact_sensitive_params(extractor_options)
return {
"locked": bool(config.get("locked")),
"extractor_type": config.get("extractor_type"),
"extractor_options": self._runtime_extractor_options(config),
"extractor_options": extractor_options,
"created_at": config.get("created_at"),
"created_by": config.get("created_by"),
"updated_at": config.get("updated_at"),
Expand Down
4 changes: 3 additions & 1 deletion backend/package/yuxi/knowledge/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,15 @@ async def get_databases_by_user(self, user: User | dict) -> list[KnowledgeBaseSu
if permission == ResourcePermission.NONE:
continue
additional_params = database.additional_params
if permission == ResourcePermission.READ:
can_manage = user_role in {"admin", "superadmin"} and permission == ResourcePermission.MANAGE
if not can_manage:
additional_params = redact_sensitive_params(additional_params)
filtered_databases.append(
replace(
database,
additional_params=additional_params,
effective_permission=permission,
can_manage=can_manage,
)
)

Expand Down
5 changes: 3 additions & 2 deletions backend/package/yuxi/knowledge/read_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ class KnowledgeBaseSummary:
pending_index_count: int = 0
processing_count: int = 0
effective_permission: ResourcePermission | None = None
can_manage: bool = False

@property
def can_manage(self) -> bool:
"""返回当前调用者是否拥有管理权限。"""
def can_edit_documents(self) -> bool:
"""返回当前调用者是否拥有知识库内容编辑权限。"""
return self.effective_permission == ResourcePermission.MANAGE


Expand Down
25 changes: 20 additions & 5 deletions backend/package/yuxi/knowledge/utils/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,30 @@

from typing import Any

_SENSITIVE_PARAMETER_MARKERS = ("token", "secret", "password", "api_key")
_SENSITIVE_PARAMETER_MARKERS = ("token", "secret", "password", "api_key", "authorization", "authentication", "cookie")
_NON_SECRET_TOKEN_PARAMETERS = {"max_tokens", "max_completion_tokens", "max_output_tokens", "chunk_token_num"}
_AUTHENTICATION_HEADER_PARAMETERS = {"default_headers", "extra_headers"}


def redact_sensitive_params(params: dict[str, Any]) -> dict[str, Any]:
"""移除知识库类型参数中的敏感凭据。"""
"""复制知识库配置并移除各层凭据和可携带任意网关认证的请求头。"""
redacted = {}
for key, value in params.items():
normalized_key = key.lower()
if any(marker in normalized_key for marker in _SENSITIVE_PARAMETER_MARKERS):
normalized_key = key.lower().replace("-", "_")
if normalized_key in _AUTHENTICATION_HEADER_PARAMETERS:
continue
redacted[key] = value
if normalized_key not in _NON_SECRET_TOKEN_PARAMETERS and any(
marker in normalized_key for marker in _SENSITIVE_PARAMETER_MARKERS
):
continue
redacted[key] = _redact_parameter_value(value)
return redacted


def _redact_parameter_value(value: Any) -> Any:
"""复制配置中的对象与数组,保留非凭据参数值。"""
if isinstance(value, dict):
return redact_sensitive_params(value)
if isinstance(value, list):
return [_redact_parameter_value(item) for item in value]
return value
4 changes: 2 additions & 2 deletions backend/package/yuxi/permissions/resource_permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ResourcePermissionPolicy:
DEFAULT_SCOPE = {"access_level": "global", "department_ids": [], "user_uids": []}
KNOWLEDGE_BASE_PERMISSION_POLICY = ResourcePermissionPolicy(
role_ceiling={
"user": ResourcePermission.READ,
"user": ResourcePermission.MANAGE,
"admin": ResourcePermission.MANAGE,
"superadmin": ResourcePermission.MANAGE,
}
Expand Down Expand Up @@ -207,7 +207,7 @@ def require_resource_permission(


def resolve_knowledge_base_permission(user: Any, resource: ShareableResource) -> ResourcePermission:
"""解析知识库权限,普通用户最多只能获得只读权限。"""
"""解析知识库共享权限;整库管理另由管理员身份约束。"""

return resolve_resource_permission(
user,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ async def query_kb_file_stats(self, kb_id: str, *, session: AsyncSession) -> dic
}

async def upsert(self, file_id: str, data: dict[str, Any]) -> KnowledgeFile:
"""新增或更新同库文件,拒绝跨库文件 ID 碰撞。"""
sanitized_data = self._sanitize_data(data)
async with pg_manager.get_async_session_context() as session:
result = await session.execute(select(KnowledgeFile).where(KnowledgeFile.file_id == file_id))
Expand All @@ -725,6 +726,8 @@ async def upsert(self, file_id: str, data: dict[str, Any]) -> KnowledgeFile:
record = KnowledgeFile(file_id=file_id, **sanitized_data)
session.add(record)
return record
if existing.kb_id != sanitized_data.get("kb_id"):
raise ValueError("File ID already belongs to another knowledge base")
for key, value in sanitized_data.items():
setattr(existing, key, value)
return existing
Expand Down
6 changes: 3 additions & 3 deletions backend/server/routers/knowledge_eval_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from fastapi.responses import Response
from pydantic import BaseModel, Field
from server.utils.auth_middleware import get_admin_user
from server.utils.auth_middleware import get_admin_user, get_required_user
from server.utils.knowledge_permissions import (
ensure_knowledge_base_permission,
require_knowledge_base_manage,
Expand Down Expand Up @@ -56,9 +56,9 @@ async def _get_evaluation_dataset_or_raise(dataset_id: str) -> Any:

async def require_evaluation_dataset_read(
dataset_id: str,
current_user: User = Depends(get_admin_user),
current_user: User = Depends(get_required_user),
) -> User:
"""校验管理员对评估数据集所属知识库的读取权限。"""
"""校验登录用户对评估数据集所属知识库的读取权限。"""

dataset = await _get_evaluation_dataset_or_raise(dataset_id)
await ensure_knowledge_base_permission(str(dataset.kb_id), current_user, ResourcePermission.READ)
Expand Down
Loading