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:
Blizzard
2026-07-02 09:31:31 +08:00
parent 16c67dcb4f
commit 9e43d07428
7 changed files with 104 additions and 8 deletions
+28 -3
View File
@@ -15,13 +15,38 @@ func (p *Postgres) ListAudit(ctx context.Context, limit, offset int) ([]AuditLog
if p.db == nil {
return nil, errStoreDisabled
}
limit, offset = clampPage(limit, offset)
var out []AuditLog
err := p.db.WithContext(ctx).Order("created_at desc").Limit(limit).Offset(offset).Find(&out).Error
return out, err
}
// AppendGuardrailEvent 追加一条护栏命中(best-effort)。
func (p *Postgres) AppendGuardrailEvent(ctx context.Context, e *GuardrailEvent) error {
if p.db == nil || e == nil {
return errStoreDisabled
}
return p.db.WithContext(ctx).Create(e).Error
}
// ListGuardrailEvents 倒序列出护栏事件(管理端安全事件流)。
func (p *Postgres) ListGuardrailEvents(ctx context.Context, limit, offset int) ([]GuardrailEvent, error) {
if p.db == nil {
return nil, errStoreDisabled
}
limit, offset = clampPage(limit, offset)
var out []GuardrailEvent
err := p.db.WithContext(ctx).Order("created_at desc").Limit(limit).Offset(offset).Find(&out).Error
return out, err
}
// clampPage 归一分页参数:limit ∈ [1,200](默认 50),offset ≥ 0。
func clampPage(limit, offset int) (int, int) {
if limit <= 0 || limit > 200 {
limit = 50
}
if offset < 0 {
offset = 0
}
var out []AuditLog
err := p.db.WithContext(ctx).Order("created_at desc").Limit(limit).Offset(offset).Find(&out).Error
return out, err
return limit, offset
}
+15
View File
@@ -57,3 +57,18 @@ type AuditLog struct {
}
func (AuditLog) TableName() string { return "sundynix_audit_log" }
// GuardrailEvent 是一次输入护栏命中(硬拦截 blocked / 灰区 suspect)。
// 由 Guardrail 中间件命中时 best-effort 写入;供安全溯源"谁触发多少次护栏"。
type GuardrailEvent struct {
BaseModel
Actor string `gorm:"size:64;index"` // 操作者 uid(未登录留空)
Kind string `gorm:"size:16;index"` // blocked(硬拦)/ suspect(灰区放行)
Reason string `gorm:"size:256"` // 拦截原因(blocked
Signals string `gorm:"type:text"` // 命中软信号 JSON 数组(suspect
Method string `gorm:"size:8"`
Path string `gorm:"size:256"`
IP string `gorm:"size:64"`
}
func (GuardrailEvent) TableName() string { return "sundynix_guardrail_event" }
+1 -1
View File
@@ -66,7 +66,7 @@ func OpenPostgres(dsn string) *Postgres {
migrateLegacyIntIDs(db)
migrateDocLinkToID(db)
if err := db.AutoMigrate(&User{}, &Task{}, &Eval{}, &LLMModel{}, &KB{}, &Doc{}, &Agent{}, &DocLink{}, &Pricing{}, &Prompt{}, &AuditLog{}); err != nil {
if err := db.AutoMigrate(&User{}, &Task{}, &Eval{}, &LLMModel{}, &KB{}, &Doc{}, &Agent{}, &DocLink{}, &Pricing{}, &Prompt{}, &AuditLog{}, &GuardrailEvent{}); err != nil {
log.Printf("[store] postgres AutoMigrate 失败,降级运行: %v", err)
return &Postgres{}
}