Improper authorization control for web services In github.com/nuclio/nuclio

Description

Nuclio: Missing authorization on project write paths allows any authenticated user to modify or delete any project This vulnerability exists in Nuclio Dashboard's project management API, allowing any authenticated user (without membership in the target project) to bypass OPA authorization checks on write paths (PUT /api/projects/{id}, DELETE /api/projects) and modify or delete any project along with all its associated resources (functions, API gateways, etc.). CWE classification: CWE-862 (Missing Authorization). --- ## Summary Nuclio Dashboard correctly enforces OPA-based authorization on the project read path (GET /api/projects), populating MemberIds in PermissionOptions so OPA can filter results by user membership. However, the write paths (PUT /api/projects/{id} and DELETE /api/projects) construct PermissionOptions without setting MemberIds. The platform-layer FilterProjectsByPermissions function (pkg/platform/abstract/platform.go:652) short-circuits when MemberIds is empty, bypassing OPA entirely. Any authenticated user who knows a project name can modify or delete that project, triggering cascading deletion of all associated Functions, APIGateways, and FunctionEvents. Affected: Nuclio v1.15.26 (latest Helm release) and HEAD commit e185454 (latest source). --- ## Attacker Model Attacker type: Authenticated low-privilege tenant (no membership in the target project) Initial access: - Holds any valid Nuclio Dashboard credentials - The account has no membership in the target project (OPA correctly denies GET /api/projects, returning an empty list) - No Kubernetes RBAC permissions required — the vulnerability is triggered at the Dashboard application layer, not via K8s API Server Attacker location: Authenticated user; network position depends on deployment: - Typical Iguazio/MLRun enterprise deployment: Dashboard is exposed via load balancer to internal or public networks - Self-hosted deployment: Dashboard is usually limited to cluster-internal or internal network access - Conservative baseline: authenticated-internal; for public-facing deployments, reachability should be rated higher User interaction required: None - The attacker directly sends HTTP requests to PUT /api/projects/{id} or DELETE /api/projects to trigger the vulnerability; no action from the target user is required Privilege gap: Before: Authenticated account with zero permissions on target project (OPA correctly denies GET, returns empty list) ↓ After: Can modify or delete any project on the platform Can trigger cascading deletion of all associated Functions, APIGateways, and FunctionEvents Can modify project configuration, affecting NuclioProject CRD in Kubernetes deployments Exploitation difficulty: Very low — four-step attack chain, each step requiring no special technical skill. --- ## Details ### Root Cause The read and write paths diverge in how they populate PermissionOptions.MemberIds: Read path (correct implementation)pkg/dashboard/resource/project.go:87-90 go PermissionOptions: opaclient.PermissionOptions{ MemberIds: opa.GetUserAndGroupIdsFromAuthSession(pr.getCtxSession(ctx)), OverrideHeaderValue: request.Header.Get(headers.ProjectsRole), }, Write path Update (vulnerable)pkg/dashboard/resource/project.go:194-196 go PermissionOptions: opaclient.PermissionOptions{ OverrideHeaderValue: request.Header.Get(headers.ProjectsRole), // MemberIds is not set — OPA check will be skipped }, Write path deleteProject (vulnerable)pkg/dashboard/resource/project.go:686-688 go PermissionOptions: opaclient.PermissionOptions{ OverrideHeaderValue: request.Header.Get(headers.ProjectsRole), // MemberIds is not set — OPA check will be skipped }, Short-circuit bypasspkg/platform/abstract/platform.go:652 go func (ap *Platform) FilterProjectsByPermissions(...) ([]platform.Project, error) { if len(permissionOptions.MemberIds) == 0 || len(projects) == 0 { return projects, nil // skips OPA entirely } allowedList, err := ap.QueryOPAMultipleResources(...) } Kubernetes platform write paths (no OPA at all)pkg/platform/kube/platform.go:779-793 go func (p *Platform) UpdateProject(ctx context.Context, updateProjectOptions *platform.UpdateProjectOptions) error { if err := p.ValidateProjectConfig(ctx, &updateProjectOptions.Project); err != nil { ... } if _, err := p.projectsClient.Update(ctx, updateProjectOptions); err != nil { ... } return nil // no OPA call } func (p *Platform) DeleteProject(ctx context.Context, deleteProjectOptions *platform.DeleteProjectOptions) error { if err := p.ValidateDeleteProjectOptions(ctx, deleteProjectOptions); err != nil { ... } if err := p.projectsClient.Delete(ctx, deleteProjectOptions); err != nil { ... } return nil // no OPA call } ### Sanitizer Coverage Analysis All validation functions on the write path are format-only and contain no identity or ownership-based authorization checks: ValidateProjectConfigpkg/platform/abstract/platform.go:854-879 go func (ap *Platform) ValidateProjectConfig(projectConfig *platform.ProjectConfig) error { if projectConfig.Meta.Name == "" { return ... } // name not empty if err := utils.ValidateLabels(...); err != nil { ... } // labels format if err := utils.ValidateLabels(...); err != nil { ... } // node selector format errorMessages := validation.IsDNS1123Label(...) // DNS naming convention return nil // No identity check, no ownership check, no OPA call } Pure format validation — no identity or resource-ownership checks of any kind. ValidateDeleteProjectOptions (false security check)pkg/platform/abstract/platform.go:536-576 go func (ap *Platform) ValidateDeleteProjectOptions(ctx context.Context, deleteProjectOptions *platform.DeleteProjectOptions) error { // ... projects, err := ap.platform.GetProjects(ctx, &platform.GetProjectsOptions{ Meta: deleteProjectOptions.Meta, PermissionOptions: deleteProjectOptions.PermissionOptions, // MemberIds still empty! // ... }) // ... if len(projects) == 0 { return nil } // project doesn't exist, exit early // Check for associated functions/API gateways } This function calls GetProjects passing along the PermissionOptions inherited from the caller — with MemberIds still empty. The inner OPA query is therefore also bypassed by the same short-circuit. It appears to validate project existence and associated resources, but the identity check is silently absent. This is a false security check. Conclusion: Two validation layers exist on the write path; both are format-only. No identity-based authorization check exists anywhere in the write path, leaving the entire defense chain ineffective at the application layer. ### Call Chain Comparison Read path (correctly blocks unauthorized access): GET /api/projects → GetAll() [project.go:71] → MemberIds = GetUserAndGroupIdsFromAuthSession() [project.go:88] → platform.GetProjects(MemberIds=[uid, gid1, ...]) → FilterProjectsByPermissions(MemberIds=[uid, gid1, ...]) → OPA filter_allowed called → unauthorized user gets empty list ✓ Write path (authorization bypassed): PUT /api/projects/{id} → Update() [project.go:165] → MemberIds = (not set) [project.go:194] → platform.UpdateProject(MemberIds=[]) → ValidateProjectConfig() ← format-only, no auth check → projectsClient.Update() → project modified, no OPA check DELETE /api/projects → deleteProject() [project.go:663] → MemberIds = (not set) [project.go:686] → platform.DeleteProject(MemberIds=[]) → ValidateDeleteProjectOptions() → inner GetProjects(MemberIds=[]) → OPA bypassed again (false security check) → projectsClient.Delete() → project deleted, no OPA check For reference, the function write path is correctly implemented (pkg/dashboard/resource/function.go:564-566), confirming this is an omission specific to project write paths: go PermissionOptions: opaclient.PermissionOptions{ MemberIds: opa.GetUserAndGroupIdsFromAuthSession(fr.getCtxSession(ctx)), OverrideHeaderValue: request.Header.Get(headers.ProjectsRole), }, --- ## PoC (Proof of Concept) ### Environment Setup This verification uses the Nuclio Dashboard binary compiled from source, paired with a mock Iguazio auth server and a mock OPA server. No Kubernetes cluster is required. Step 1: Prepare working directory ```bash mkdir -p

Mitigation

Update Impact

Minimal update. May introduce new vulnerabilities or breaking changes.

Ecosystem
Package
Affected version
Patched versions