fix(approvals): narrow gate scope to token-mint + secret-write only (#2579 follow-up) #2592
@@ -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)")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user