0.11.7: 代码审查查缺补漏(安全/并发/前端三轮审查修复)

安全:
- 渠道 webhook 入站强制令牌鉴权(恒定时间比较+统一文案),企微签名官方算法;
- 报表/概览/systemInfo 端点按 usage:read/audit:read/system:manage 授权;
- sso_error 固定错误码;个人渠道令牌仅请求头;工具出站 Dialer.Control 消除
  DNS rebinding TOCTOU;新增 channel:read/manage 权限;限流倍数上限 10。

并发/一致性:
- 任务上报单条条件 UPDATE 防重放双提交;认领回收过期 claimed 任务;
- 审批改先开通后落记录(幂等,无嵌套事务);聊天消息单事务落库;
- 会话列表校验 AuthVersion;吊销先 Del 后 SRem;删工具保护调用历史;
- rejected 冷却 24h;限流被拒补偿;maintenance 清理限流窗口。

前端/菜单:
- 修复 gatewayChildren late-append 导致 reports/tenants/channels 菜单不可见;
- 聊天改名 PUT 对齐;渠道编辑清空凭据防串写+启用开关;
- 聊天响应防串扰;报表本地时区日期。
This commit is contained in:
LLMGuardX Dev
2026-08-13 15:22:19 +08:00
parent 8000bccde3
commit 58535fda7b
21 changed files with 287 additions and 124 deletions
@@ -75,7 +75,10 @@
<ElInput v-model="form.api_key" type="password" show-password :placeholder="editingId ? '留空不更换' : '必填'" />
</ElFormItem>
<ElFormItem label="入站令牌">
<ElInput v-model="form.inbound_token" placeholder="webhook/企微回调鉴权令牌" />
<ElInput v-model="form.inbound_token" type="password" show-password placeholder="webhook/企微回调鉴权令牌" />
</ElFormItem>
<ElFormItem label="启用">
<ElSwitch v-model="form.enabled" />
</ElFormItem>
<template v-if="form.kind === 'wecom'">
<ElFormItem label="CorpID"><ElInput v-model="form.corp_id" /></ElFormItem>
@@ -155,7 +158,8 @@
const form = reactive({
code: '', name: '', kind: 'webhook', binding_provider: '', binding_model: '', api_key: '',
inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '',
department_ids: [] as string[]
department_ids: [] as string[],
enabled: true
})
async function load() {
@@ -176,7 +180,7 @@
function openCreate() {
editingId.value = ''
Object.assign(form, { code: '', name: '', kind: 'webhook', binding_provider: '', binding_model: '', api_key: '', inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '', department_ids: [] })
Object.assign(form, { code: '', name: '', kind: 'webhook', binding_provider: '', binding_model: '', api_key: '', inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '', department_ids: [], enabled: true })
dialogVisible.value = true
}
@@ -184,8 +188,12 @@
editingId.value = row.id
Object.assign(form, {
code: row.code, name: row.name, kind: row.kind, api_key: '',
// 显式清空全部平台配置字段:否则上一条渠道的 Secret/Token 会残留并
// 在保存时静默覆盖当前渠道的凭据。
inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '',
binding_provider: row.model_binding?.provider || '', binding_model: row.model_binding?.model || '',
department_ids: [...(row.department_ids || [])]
department_ids: [...(row.department_ids || [])],
enabled: row.enabled
})
dialogVisible.value = true
}
@@ -213,7 +221,8 @@
code: form.code, name: form.name, kind: form.kind, config,
model_binding: { provider: form.binding_provider || undefined, model: form.binding_model || undefined },
department_ids: form.department_ids,
api_key: form.api_key
api_key: form.api_key,
enabled: form.enabled
}
if (editingId.value) {
await request.put({ url: `/api/v1/admin/channels/${editingId.value}`, params: payload })
@@ -104,13 +104,17 @@
const range = ref<[string, string]>([daysAgo(6), today()])
const dailyUsage = ref<any[]>([])
function fmtLocal(d: Date) {
const pad = (v: number) => String(v).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
}
function daysAgo(n: number) {
const d = new Date()
d.setDate(d.getDate() - n)
return d.toISOString().slice(0, 10)
return fmtLocal(d)
}
function today() {
return new Date().toISOString().slice(0, 10)
return fmtLocal(new Date())
}
function costText(value: number | null | undefined) {
return value != null && value > 0 ? `USD ${(value / 1e6).toFixed(4)}` : '—'
@@ -209,7 +209,13 @@
history.replaceState(null, '', window.location.pathname + window.location.hash)
await router.replace('/')
} else if (ssoError) {
ElMessage.error(ssoError)
const errorText = ({
unbound: '该企业账号尚未绑定本系统账号,请先用账号密码登录后在「账号安全」中绑定',
disabled: '账号已被停用',
expired: '登录状态已过期,请重新扫码',
login_failed: '企业登录失败,请重试或联系管理员'
} as Record<string, string>)[ssoError] || ssoError
ElMessage.error(errorText)
history.replaceState(null, '', window.location.pathname + window.location.hash)
}
} finally {
@@ -173,7 +173,10 @@ async function send() {
draft.value = ''
scrollToBottom()
try {
// 响应到达时校验会话未切换:发送中切模型/切会话时丢弃迟到响应,防止
// 串入新会话。
const response = await appendChatMessage(id, text)
if (currentId.value !== id) return
const choices = (response.choices as Array<{ message?: { content?: string } }>) || []
const answer = choices[0]?.message?.content || ''
messages.value.push({ sequence: messages.value.length + 1, role: 'assistant', content: answer, created_at: '' })
@@ -53,7 +53,7 @@
<div class="text-sm">个人限流倍数</div>
<p class="text-g-500 mt-1 text-sm">你的个人调用按此倍数放宽工具限流例如工具限流 10 rpm倍数 2 时个人可调用 20 rpm</p>
</div>
<ElInputNumber v-model="policy.rate_limit_multiplier" :min="1" :max="100" class="w-32" :disabled="savingPolicy" @change="savePolicy" />
<ElInputNumber v-model="policy.rate_limit_multiplier" :min="1" :max="10" class="w-32" :disabled="savingPolicy" @change="savePolicy" />
</div>
</div>
</ElCard>