feat(gateway): 护栏拦截事件落库 + 安全事件流(T4.B)
- store.GuardrailEvent 表(sundynix_guardrail_event) + AppendGuardrailEvent/ListGuardrailEvents - middleware.Guardrail(db):命中 blocked/suspect 时 best-effort 落库 (actor/kind/reason/signals/method/path/ip,独立超时 ctx) - GET /api/v1/admin/guardrail-events:安全事件流(倒序,翻页) - store.clampPage 抽出分页归一(audit/guardrail 共用) - live:注入 "ignore all previous instructions" → 422 硬拦 + 事件留痕(kind=blocked) - DEPTH_ROADMAP T4.B 护栏事件打勾 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,34 @@ func (h *Handler) AuditList(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"logs": out})
|
||||
}
|
||||
|
||||
// GuardrailEvents: GET /api/v1/admin/guardrail-events?limit=&offset= —— 护栏命中安全事件流(倒序)。
|
||||
func (h *Handler) GuardrailEvents(c *gin.Context) {
|
||||
limit, offset := 50, 0
|
||||
if v := c.Query("limit"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil {
|
||||
limit = n
|
||||
}
|
||||
}
|
||||
if v := c.Query("offset"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil {
|
||||
offset = n
|
||||
}
|
||||
}
|
||||
rows, err := h.db.ListGuardrailEvents(c.Request.Context(), limit, offset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
out := make([]gin.H, 0, len(rows))
|
||||
for _, e := range rows {
|
||||
out = append(out, gin.H{
|
||||
"id": e.ID, "actor": e.Actor, "kind": e.Kind, "reason": e.Reason,
|
||||
"signals": e.Signals, "method": e.Method, "path": e.Path, "ip": e.IP, "at": e.CreatedAt,
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"events": out})
|
||||
}
|
||||
|
||||
// AdminOverview: GET /api/v1/admin/overview —— 管理端系统级聚合(控制塔口径)。
|
||||
// 区别于 stats/overview(桌面端个人工作台):这里一律系统级——全平台用户/任务/评测/
|
||||
// 模型配置态/提示词控制面态/服务健康。Task/Eval 表无 owner 即全量;用户/KB/Doc 走全局计数。
|
||||
|
||||
Reference in New Issue
Block a user