fix(approvals): narrow gate scope to token-mint + secret-write only (#2579 follow-up) #2592

Merged
agent-dev-a merged 2 commits from fix-2579-e2e into main 2026-06-11 22:25:29 +00:00
4 changed files with 12 additions and 11 deletions
@@ -27,10 +27,8 @@ const (
// (and gate the corresponding handler with requireApproval) to expand the
// boundary; remove one to drop a gate. This is the only place the policy lives.
var gated = map[Action]bool{
ActionDeleteWorkspace: true,
ActionDeprovision: true,
ActionSecretWrite: true,
ActionOrgTokenMint: true,
ActionSecretWrite: true,
ActionOrgTokenMint: true,
}
// IsGated reports whether action requires a human approval before executing.
@@ -129,7 +129,8 @@ func TestIntegration_AdminToken_OrgTokenMint_WithoutApproval_Rejected(t *testing
t.Cleanup(func() { db.DB = prev })
setupTestRedis(t)
_ = seedConciergeWorkspace(t, conn)
wsID := seedConciergeWorkspace(t, conn)
t.Setenv("MOLECULE_PLATFORM_WORKSPACE_ID", wsID)
os.Unsetenv("MOLECULE_PLATFORM_APPROVAL_GATE")
h := NewOrgTokenHandler()
@@ -224,7 +225,8 @@ func TestIntegration_AdminToken_OrgTokenMint_WithApproval_Succeeds(t *testing.T)
t.Cleanup(func() { db.DB = prev })
setupTestRedis(t)
_ = seedConciergeWorkspace(t, conn)
wsID := seedConciergeWorkspace(t, conn)
t.Setenv("MOLECULE_PLATFORM_WORKSPACE_ID", wsID)
os.Unsetenv("MOLECULE_PLATFORM_APPROVAL_GATE")
h := NewOrgTokenHandler()
@@ -356,7 +358,8 @@ func TestIntegration_AdminToken_OrgTokenMint_ExploitRegression(t *testing.T) {
t.Cleanup(func() { db.DB = prev })
setupTestRedis(t)
_ = seedConciergeWorkspace(t, conn)
wsID := seedConciergeWorkspace(t, conn)
t.Setenv("MOLECULE_PLATFORM_WORKSPACE_ID", wsID)
// The exploit ran with the default rollout flag OFF (no
// MOLECULE_PLATFORM_APPROVAL_GATE env var set). That is the
@@ -64,11 +64,11 @@ func TestIntegration_RequireApproval_GateCycle(t *testing.T) {
t.Fatalf("seed root workspace: %v", err)
}
action := approvals.ActionDeleteWorkspace
action := approvals.ActionSecretWrite
ctxA := map[string]interface{}{"target": "ws-A"}
// 1. First call → no approval yet → pending created.
ok, id1, err := requireApproval(ctx, b, wsID, action, "delete ws-A", ctxA)
ok, id1, err := requireApproval(ctx, b, wsID, action, "write secret ws-A", ctxA)
if err != nil {
t.Fatalf("call 1: %v", err)
}
@@ -98,13 +98,13 @@ func TestGateDestructive_ScopeShortCircuits(t *testing.T) {
// flag OFF (default) + org-token + gated action → proceed (rollout dormant).
os.Unsetenv("MOLECULE_PLATFORM_APPROVAL_GATE")
if !gateDestructive(newCtx("org-token"), nil, "ws", approvals.ActionDeleteWorkspace, "r", nil) {
if !gateDestructive(newCtx("org-token"), nil, "ws", approvals.ActionSecretWrite, "r", nil) {
t.Error("flag off + org-token must proceed (gate dormant)")
}
// flag ON + NO agent credential (workspace/CP caller) → proceed.
t.Setenv("MOLECULE_PLATFORM_APPROVAL_GATE", "1")
if !gateDestructive(newCtx("none"), nil, "ws", approvals.ActionDeleteWorkspace, "r", nil) {
if !gateDestructive(newCtx("none"), nil, "ws", approvals.ActionSecretWrite, "r", nil) {
t.Error("non-agent caller must proceed (normal operation unchanged)")
}