feat(admin): 「安全护栏」页做实 —— 接真护栏事件,去 mock (P1)

审计 P1「admin 三页纯 mock」之二。此前 GuardrailsPage 是写死的正则/敏感词
编辑框(改了不生效的假配置)+编造拦截日志。后端 /admin/guardrail-events 早已
现成(T4.B),纯前端做实。

- 命中事件流接真数据(guardrail_event,middleware.Guardrail 命中即落库):
  blocked 硬拦/suspect 灰区放行,带原因/信号/路径/来源;计数卡片+原因 Top 分布
  (客户端从近100条聚合)+按 kind 筛。
- 诚实处理假配置:护栏规则(Tier1 正则/词库 + Tier2 LLM 分类器)是中间件代码
  常量、非运行时可配,故删掉"能改却不生效"的编辑框,换成只读规则说明 + 指出
  运行时可配需另建配置存储(参考提示词控制面)。不摆假控件。

live:触发注入越狱输入→422 硬拦+落库(reason「疑似提示词注入」)→页面渲染
真事件流+原因分布。tsc+41 vitest 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-18 12:07:32 +08:00
parent d04d830c37
commit c02ebc7bce
2 changed files with 169 additions and 319 deletions
+20
View File
@@ -345,6 +345,26 @@ export async function adminEvals(days = 14): Promise<{ from: string; to: string;
};
}
// ---- 输入护栏安全事件(真数据,来自 guardrail_event----
export interface GuardrailEvent {
id: string;
actor: string;
kind: string; // blocked(硬拦)/ suspect(灰区放行)
reason: string;
signals: string; // 命中软信号 JSON 数组字符串
method: string;
path: string;
ip: string;
at: string;
}
export async function guardrailEvents(limit = 100): Promise<GuardrailEvent[]> {
const res = guard(await fetch(`${ADMIN}/guardrail-events?limit=${limit}`, { headers: authHeaders() }));
const d = (await res.json().catch(() => ({}))) as { events?: GuardrailEvent[]; error?: string };
if (!res.ok) throw new Error(d.error ?? `guardrail failed: ${res.status}`);
return d.events ?? [];
}
// gatewayOnline 用公开的 /healthz 探活(不受鉴权影响)。
export async function gatewayOnline(): Promise<boolean> {
try {