feat(secret): 敏感信息库前端接入——AgentIO钥匙图标+选择窗数据源(2026-09-17)

- agent_config.py: GENERAL_TOOLS += 6 个 ToolDefinition(category=secret,
  required 显式声明防 native FC 把可选参数标必填逼 LLM 编值)。
- wwwroot/api/secret_options.dspy: AgentIO 选择窗数据源。身份取自 get_user()/
  get_userorgid(),**不接受前端传 user_id/org_id**(否则改参数即可枚举他人凭据名称);
  双保险剔除 encrypted_value/fingerprint;未登录返 success:false(区分没登录与没数据)。
  RBAC 已被 rp.json 的 /pipeline_core/**(logined) 覆盖,无需额外注册。
- 3 个 .ui 的 AgentIO options 注入 secret_dataurl/secret_format/secret_title/
  secret_tip(共4处,含 agent/index.ui 动态建 tab 的 script 字符串)。
  未传 secret_dataurl 时钥匙图标不渲染 → 其他宿主应用零影响。

bricks 侧配套(UiText.insertAtCursor / AgentInput 弹窗 / AgentIO 透传)在 bricks 仓库。
This commit is contained in:
yumoqing 2026-09-17 15:45:17 +08:00
parent 17b5a50a04
commit a243a16941
5 changed files with 111 additions and 5 deletions

View File

@ -449,6 +449,57 @@ GENERAL_TOOLS = [
required=["table"],
category="data",
),
# ── 用户敏感信息库2026-09-17凭据密文入库 + 占位符引用 + 入站自动侦测 ──
# 实现在 pipeline_service.secret_vaulthandler 签名 (sor, params, ctx))。
# 安全不变量agent 全程只见元数据与占位符,真值只在执行边界以子进程环境变量注入。
ToolDefinition(
name="list_secrets",
description="列出当前用户/机构可用的敏感信息apikey/token/密码/私钥)。只返回元数据(名称/类型/前缀/长度/使用次数),绝不返回明文值。需要用凭据执行命令前先调这个查有哪些",
parameters={"all": "可选:传 1 时含已停用条目"},
required=[],
category="secret",
),
ToolDefinition(
name="save_secret",
description="把一份敏感信息密文入库。用户说「把 X 存为 Y」「保存这个 apikey/token/密码」时调用;用户直接在消息里贴明文时平台入站门禁会自动侦测入库,无需你再调。入库后返回占位符 @@sec:NAME@@ 与环境变量名 $PIPELINE_SEC_NAME",
parameters={
"value": "明文值(仅本次调用使用,落库即 RC4 加密)",
"name": "可选:变量名(大写字母开头,只含大写字母/数字/下划线;缺省按类型自动派生)",
"label": "可选:显示名称",
"secret_type": "可选:类型(缺省自动识别,如 github_token/api_key/db_password",
"remark": "可选:备注(用途说明)",
},
required=["value"],
category="secret",
),
ToolDefinition(
name="use_secret",
description="从敏感信息库选一个插入当前会话(返回占位符 @@sec:NAME@@ 与环境变量名,不返回明文)。用户说「用我的 github token」「插入那个 apikey」时调用",
parameters={"name": "变量名(先用 list_secrets 查可用名称)"},
required=["name"],
category="secret",
),
ToolDefinition(
name="delete_secret",
description="删除一条敏感信息(仅本人/本机构条目)",
parameters={"name": "变量名", "id": "可选记录ID与 name 二选一)"},
required=[],
category="secret",
),
ToolDefinition(
name="set_secret_status",
description="启用/停用一条敏感信息。停用后不再注入执行环境但保留记录(凭据泄露疑似时先停用)",
parameters={"name": "变量名", "status": "active启用或 disabled停用"},
required=["name", "status"],
category="secret",
),
ToolDefinition(
name="detect_secret",
description="只侦测文本中的疑似敏感信息但不入库返回前4字符+类型+置信度+熵)。用户问「这段有没有泄露风险」时调用",
parameters={"text": "待检测文本"},
required=["text"],
category="secret",
),
]

View File

@ -18,7 +18,7 @@
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var tp=bricks.getWidgetById('session_tabs',bricks.app);if(!tp)return;var sid='s'+Date.now()+'_'+Math.floor(Math.random()*100000);var n=tp.opts.items.length+1;tp.open_tab({name:'session_'+sid,label:'会话 '+n,removable:true,content:{widgettype:'VBox',options:{css:'filler',width:'100%',height:'100%'},subwidgets:[{widgettype:'urlwidget',options:{url:'/pipeline_core/api/agent_menus.dspy?session_id='+sid+'&pipeline_id=sdlc_general',method:'GET'}},{widgettype:'AgentIO',options:{css:'filler',margin:'0 24px 24px 24px',url:'/pipeline_core/api/agent_chat.dspy?session_id='+sid+'&pipeline_id=sdlc_general',model_dataurl:'/pipeline_core/api/agent_model_options.dspy?session_id='+sid+'&pipeline_id=sdlc_general',model_cwidth:14,placeholder:'输入你的需求...'}}]}});"
"script": "var tp=bricks.getWidgetById('session_tabs',bricks.app);if(!tp)return;var sid='s'+Date.now()+'_'+Math.floor(Math.random()*100000);var n=tp.opts.items.length+1;tp.open_tab({name:'session_'+sid,label:'会话 '+n,removable:true,content:{widgettype:'VBox',options:{css:'filler',width:'100%',height:'100%'},subwidgets:[{widgettype:'urlwidget',options:{url:'/pipeline_core/api/agent_menus.dspy?session_id='+sid+'&pipeline_id=sdlc_general',method:'GET'}},{widgettype:'AgentIO',options:{css:'filler',margin:'0 24px 24px 24px',url:'/pipeline_core/api/agent_chat.dspy?session_id='+sid+'&pipeline_id=sdlc_general',model_dataurl:'/pipeline_core/api/agent_model_options.dspy?session_id='+sid+'&pipeline_id=sdlc_general',model_cwidth:14,placeholder:'输入你的需求...',secret_dataurl:'/pipeline_core/api/secret_options.dspy',secret_format:'@@sec:{name}@@',secret_title:'选择敏感信息',secret_tip:'插入敏感信息'}}]}});"
}]
}
]
@ -52,7 +52,11 @@
"url": "/pipeline_core/api/agent_chat.dspy?session_id=default_sdlc_general&pipeline_id=sdlc_general",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy?session_id=default_sdlc_general&pipeline_id=sdlc_general",
"model_cwidth": 14,
"placeholder": "输入你的需求..."
"placeholder": "输入你的需求...",
"secret_dataurl": "/pipeline_core/api/secret_options.dspy",
"secret_format": "@@sec:{name}@@",
"secret_title": "选择敏感信息",
"secret_tip": "插入敏感信息"
}
}
]

View File

@ -31,7 +31,11 @@
"url": "/pipeline_core/api/agent_chat_generic.dspy",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy",
"model_cwidth": 14,
"placeholder": "输入你的需求...(纯通用模式)"
"placeholder": "输入你的需求...(纯通用模式)",
"secret_dataurl": "/pipeline_core/api/secret_options.dspy",
"secret_format": "@@sec:{name}@@",
"secret_title": "选择敏感信息",
"secret_tip": "插入敏感信息"
}
}
]

View File

@ -0,0 +1,39 @@
# secret_options.dspy — AgentIO「插入敏感信息」选择窗的数据源2026-09-17
#
# 前端AgentInput 的 opts.secret_dataurl 指向此处bricks 通用框架不硬编码宿主路径,
# 由 pipeline-core 的 agent/index.ui 注入)。
# 返回:{"success":true, "secrets":[{name,label,secret_type,prefix_hint,length_hint,
# use_count,remark,status}, ...]}
#
# 安全铁律(与 secret_vault.list_secrets 同源):
# - **绝不返回 encrypted_value 或明文**。只给元数据:名称/类型/前缀4字符/长度/使用次数。
# 前缀4字符够人类辨认是哪个凭据拼不出可用值GitHub PAT 前缀是公开固定值,零熵)。
# - 身份取自登录态get_user/get_userorgid**不接受前端传 user_id/org_id**——
# 否则任意用户改参数即可枚举他人凭据名称(元数据本身也是信息)。
# - 未登录返回 success:false不返回空列表区分「没登录」与「没数据」便于前端提示
import json
uid = await get_user()
if not uid:
return json.dumps({"success": False, "error": "未登录"}, ensure_ascii=False)
org_id = (await get_userorgid()) or ''
show_all = str((params_kw or {}).get('all', '') or '').strip() in ('1', 'true', 'True')
from sqlor.dbpools import DBPools
from pipeline_service import secret_vault
async with DBPools().sqlorContext('pipeline') as sor:
rows = await secret_vault.list_secrets(sor, org_id=org_id, user_id=uid,
only_active=not show_all)
await sor.sqlExe("COMMIT", {})
# 双保险:即使上游误带出密文字段,这里再剔一次(返回体是安全边界)
_safe = []
for r in rows:
item = {k: v for k, v in r.items()
if k not in ('encrypted_value', 'fingerprint')}
_safe.append(item)
return json.dumps({"success": True, "secrets": _safe}, ensure_ascii=False, default=str)

View File

@ -17,7 +17,11 @@
"url": "/pipeline_core/api/agent_chat.dspy?pipeline_id={{pl_id}}&session_id=m_{{pl_id}}",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy?capabilities=chat&pipeline_id={{pl_id}}&session_id=m_{{pl_id}}",
"model_cwidth": 14,
"placeholder": "输入你的需求..."
"placeholder": "输入你的需求...",
"secret_dataurl": "/pipeline_core/api/secret_options.dspy",
"secret_format": "@@sec:{name}@@",
"secret_title": "选择敏感信息",
"secret_tip": "插入敏感信息"
}
}
]
@ -77,7 +81,11 @@
"url": "/pipeline_core/api/agent_chat.dspy",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy?capabilities=chat",
"model_cwidth": 14,
"placeholder": "输入你的需求..."
"placeholder": "输入你的需求...",
"secret_dataurl": "/pipeline_core/api/secret_options.dspy",
"secret_format": "@@sec:{name}@@",
"secret_title": "选择敏感信息",
"secret_tip": "插入敏感信息"
}
},
{