feat(security): 代审口径 A 落地(仅子代理发起的敏感调用做实质判定 + fail-closed + 理由进审计与小字)
This commit is contained in:
1 parent
fa850f9599
commit
20160c66d8
13 files changed
+1479
-83
No files matched your search
@@ -0,0 +1,41 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// subagentTaskKey 是「本 Run 由编排器作为子代理派发、并携带派给它的任务目标」的
|
||||
// context key。
|
||||
type subagentTaskKey struct{}
|
||||
|
||||
// WithSubagentTask 把一次子代理派发的**任务目标**挂到该次 Run 的 context 上。
|
||||
// 它由编排层在驱动子代理 Run 之前挂载(见 orchestration 的 Scheduler.run,与
|
||||
// WithToolWhitelist / WithFlowBranch 同一处、同一族),是「本次工具调用是否由
|
||||
// 子代理发起、为了什么任务」的**结构化信号**。
|
||||
//
|
||||
// 敏感操作代审的越界判定只读这条信号,绝不从提示词、模型文本或会话 id 形态推断
|
||||
// (项目红线:身份与归属靠元数据)。空 goal 是空操作:未派发的运行(主会话自身、
|
||||
// 后台思考)不挂该键,读侧得到 ok=false,语义为「非子代理发起」。
|
||||
func WithSubagentTask(ctx context.Context, goal string) context.Context {
|
||||
if ctx == nil || strings.TrimSpace(goal) == "" {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, subagentTaskKey{}, goal)
|
||||
}
|
||||
|
||||
// SubagentTaskFrom 返回 ctx 上挂着的派发任务目标;未挂载(或目标为空白)时返回
|
||||
// ok=false。它是 WithSubagentTask 的读侧,供拿到 context 的调用方(工具准入钩子、
|
||||
// 代审判定器)识别「这次调用由子代理发起」,并拿到判「有没有越界」所需的那个任务。
|
||||
//
|
||||
// 它的签名与 audit.SubagentOrigin 一致,组合根因此可以直接把它当来源解析器注入。
|
||||
func SubagentTaskFrom(ctx context.Context) (string, bool) {
|
||||
if ctx == nil {
|
||||
return "", false
|
||||
}
|
||||
goal, _ := ctx.Value(subagentTaskKey{}).(string)
|
||||
if strings.TrimSpace(goal) == "" {
|
||||
return "", false
|
||||
}
|
||||
return goal, true
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSubagentTask_RoundTrip:挂载的任务目标能被原样读回(ok=true)。
|
||||
func TestSubagentTask_RoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := WithSubagentTask(context.Background(), "整理导出文件")
|
||||
got, ok := SubagentTaskFrom(ctx)
|
||||
if !ok || got != "整理导出文件" {
|
||||
t.Fatalf("SubagentTaskFrom = (%q, %v), want (整理导出文件, true)", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSubagentTask_EmptyIsNoop:未派发的运行(主会话自身、后台思考)是
|
||||
// ok=false —— 这正是「非子代理发起」的语义,判定侧据此不加判定。
|
||||
func TestSubagentTask_EmptyIsNoop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
}{
|
||||
{name: "bare context", ctx: context.Background()},
|
||||
{name: "blank goal", ctx: WithSubagentTask(context.Background(), " ")},
|
||||
{name: "nil context", ctx: nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got, ok := SubagentTaskFrom(tt.ctx); ok || got != "" {
|
||||
t.Fatalf("SubagentTaskFrom = (%q, %v), want (\"\", false)", got, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSubagentTask_NilContextIsNoop:nil ctx 是空操作,不 panic。
|
||||
func TestSubagentTask_NilContextIsNoop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := WithSubagentTask(nil, "任务"); got != nil {
|
||||
t.Fatalf("WithSubagentTask(nil, …) = %v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSubagentTask_PropagatesThroughChildContexts:派发标记必须能穿过中途派生的
|
||||
// ctx 抵达工具执行点(准入钩子拿到的就是那一层 ctx);被取消的派生 ctx 同样保留它。
|
||||
func TestSubagentTask_PropagatesThroughChildContexts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
parent := WithSubagentTask(context.Background(), "整理导出文件")
|
||||
child, cancel := context.WithCancel(parent)
|
||||
defer cancel()
|
||||
|
||||
got, ok := SubagentTaskFrom(child)
|
||||
if !ok || !strings.Contains(got, "整理") {
|
||||
t.Fatalf("派生 ctx 上的任务 = (%q, %v), want 原任务", got, ok)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user