feat(security): 代审口径 A 落地(仅子代理发起的敏感调用做实质判定 + fail-closed + 理由进审计与小字)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 12:12:31 +08:00
1 parent fa850f9599
commit 20160c66d8
13 files changed
+1479 -83

No files matched your search

@@ -0,0 +1,62 @@
package audit
import (
"context"
"strings"
"sync"
)
// DecisionNote 把本次代审的**判定理由**带出冻结的调用契约:代审门(capability/restrict)
// 的 Reviewer 只回 (allowed, error),而前端小字要写清「为什么被拒」,因此理由经这条
// **按调用**(不是跨调用)的通道交给组合根,由它拼进小字。零值可用;全部方法对 nil 安全。
type DecisionNote struct {
mu sync.Mutex
reason string
}
// WithDecisionNote 在 ctx 上挂一个全新的按调用理由盒,供本次代审链路写入:
//
// ctx, note := audit.WithDecisionNote(ctx)
// decision := gate.Admit(ctx, call.Name, call.Args) // 代审在内部写入理由
// if r := note.Reason(); r != "" { …拼进前端小字… }
//
// 每次工具调用挂一个新的盒子,因此并发的两次代审绝不互相覆盖理由。
func WithDecisionNote(ctx context.Context) (context.Context, *DecisionNote) {
note := &DecisionNote{}
return context.WithValue(ctx, decisionNoteKey{}, note), note
}
// decisionNoteKey 是理由盒的 context key。
type decisionNoteKey struct{}
// DecisionNoteFrom 返回 ctx 上挂着的理由盒;未挂载时返回 nil(各方法对 nil 安全)。
func DecisionNoteFrom(ctx context.Context) *DecisionNote {
if ctx == nil {
return nil
}
note, _ := ctx.Value(decisionNoteKey{}).(*DecisionNote)
return note
}
// SetReason 记录本次代审的判定理由。空白理由被忽略,且**绝不覆盖**已有的非空理由:
// 一次调用只有一个判定结论,后到的写入不能把先到的事实改掉。
func (n *DecisionNote) SetReason(reason string) {
if n == nil || strings.TrimSpace(reason) == "" {
return
}
n.mu.Lock()
defer n.mu.Unlock()
if n.reason == "" {
n.reason = reason
}
}
// Reason 返回已记录的理由;未记录时返回 ""。
func (n *DecisionNote) Reason() string {
if n == nil {
return ""
}
n.mu.Lock()
defer n.mu.Unlock()
return n.reason
}