fix: restrict agnet simulation controls

This commit is contained in:
gongzhiyong
2026-05-26 18:41:26 +08:00
parent 2cc5e667df
commit 8a6fea235e
3 changed files with 53 additions and 11 deletions
@@ -1230,6 +1230,10 @@ func normalizeAgnetSimulationEvents(values []string) []string {
}
func AgnetSimulateUserDeploymentEvents(c *gin.Context) {
if c.GetInt("role") < common.RoleAdminUser && !common.GetEnvOrDefaultBool("AGNET_SIMULATION_ENABLED", false) {
agnetError(c, "SIMULATION_DISABLED", "simulation endpoint is admin-only unless AGNET_SIMULATION_ENABLED=true")
return
}
record, ok := requireAuthenticatedUserAgnetDeployment(c)
if !ok {
return
@@ -504,6 +504,7 @@ func TestAgnetUserDeploymentSimulatedEventsArePersistedAndPrefixed(t *testing.T)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 7)
simCtx.Set("role", common.RoleAdminUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agnet/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{"events":["deployment.started","simulation.agent.done"]}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
@@ -546,6 +547,7 @@ func TestAgnetUserDeploymentSimulationRejectsOtherUsersDeployment(t *testing.T)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 8)
simCtx.Set("role", common.RoleAdminUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agnet/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
@@ -557,6 +559,37 @@ func TestAgnetUserDeploymentSimulationRejectsOtherUsersDeployment(t *testing.T)
require.Equal(t, "DEPLOYMENT_FORBIDDEN", envelope.Error.Code)
}
func TestAgnetUserDeploymentSimulationIsDisabledForRegularUserByDefault(t *testing.T) {
setupAgnetControlPlaneTestDB(t)
resetAgnetControlPlaneState(t)
plan := baseAgnetResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgnetCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, createRecorder.Code)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 7)
simCtx.Set("role", common.RoleCommonUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agnet/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
AgnetSimulateUserDeploymentEvents(simCtx)
var envelope agnetCreateTestEnvelope
require.NoError(t, common.Unmarshal(simRecorder.Body.Bytes(), &envelope))
require.False(t, envelope.Success)
require.Equal(t, "SIMULATION_DISABLED", envelope.Error.Code)
}
func TestAgnetUserSwarmsAdapterCreatesScopedDeployment(t *testing.T) {
setupAgnetControlPlaneTestDB(t)
resetAgnetControlPlaneState(t)
@@ -23,6 +23,7 @@ import {
} from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { useAuthStore } from '@/stores/auth-store'
import { api } from '@/lib/api'
// /audit pulls from mcp-server §5.10 stub now, not the Heicode-local
// controller — the contract doc names that endpoint as the canonical
@@ -349,6 +350,8 @@ function grantStatusToneClass(status: string | undefined): string {
function RunDetailPanel({ dep }: { dep: AgnetDeployment }) {
const { t } = useTranslation()
const queryClient = useQueryClient()
const currentUser = useAuthStore((state) => state.auth.user)
const canSimulate = Boolean(currentUser?.role && currentUser.role >= 10)
const phase = dep.phase || dep.status
const risk = describeRiskLevel(dep)
const grants = collectResourceGrants(dep)
@@ -389,6 +392,7 @@ function RunDetailPanel({ dep }: { dep: AgnetDeployment }) {
</p>
</div>
<div className='flex shrink-0 items-center gap-2'>
{canSimulate && (
<Button
type='button'
variant='outline'
@@ -400,6 +404,7 @@ function RunDetailPanel({ dep }: { dep: AgnetDeployment }) {
<Rocket className='h-3.5 w-3.5' />
{t('Simulate')}
</Button>
)}
<StatusBadge phase={phase} />
</div>
</div>