GHSA-72h8-wp98-7hch: Npm/unleash-server vulnerability
Summary
Multiple authorization vulnerabilities in Unleash admin API, including a critical missing await that completely bypasses a permission check.
Vulnerability 1: Missing await on Permission Check (HIGH)
File: src/lib/features/segment/segment-controller.ts (line 345)
POST /api/admin/segments/strategies has permission: NONE at the route level. The handler performs its own check via this.accessService.hasPermission(), but omits the await keyword. Since hasPermission() is async (returns Promise<boolean>), the variable always receives a truthy Promise object. The if (!hasFeatureStrategyPermission) check never triggers.
typescript // BUG: missing await - hasPermission() returns Promise<boolean> const hasFeatureStrategyPermission = this.accessService.hasPermission( req.user, UPDATEFEATURESTRATEGY, projectId, environmentId, ); if (!hasFeatureStrategyPermission) { // Always false - Promise is truthy! res.status(403).send(); return; }
Impact: Any authenticated user can modify segment assignments on ANY strategy across ALL projects.
Fix: Add await: const hasFeatureStrategyPermission = await this.accessService.hasPermission(...)
Vulnerability 2: Cross-Project Variant Read (MEDIUM)
File: src/lib/routes/admin-api/project/variants.ts (line 213-223)
GET /api/admin/projects/:projectId/features/:featureName/environments/:environment/variants completely ignores projectId. getVariantsOnEnv() only uses featureName and environment.
Impact: Any authenticated user can read variant configs (names, weights, payloads) from any project.
Vulnerability 3: Cross-Project Strategy Read (MEDIUM)
File: src/lib/features/feature-toggle/feature-toggle-controller.ts (line 1107-1116)
GET .../strategies/:strategyId ignores all params except strategyId. Any authenticated user can read any strategy's full configuration.
Vulnerability 4: Cross-Project Environment Info Leak (MEDIUM)
File: src/lib/features/feature-toggle/feature-toggle-service.ts (line 1611)
getEnvironmentInfo() doesn't validate feature belongs to project. Compare with getFeature() which calls validateFeatureBelongsToProject().
Vulnerability 5: Cross-Project Tag Modification (LOW)
File: src/lib/features/feature-toggle/feature-toggle-controller.ts (line 576-596)
PUT /:projectId/tags accepts features array in body without validating they belong to projectId.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/unleash-serverto a version that resolves this vulnerability.Fixed in 8.0.3 - Configuration
Await the asynchronous permission check before evaluating it: const hasFeatureStrategyPermission = await this.accessService.hasPermission(...).
feature-toggle-controller.ts hasFeatureStrategyPermission = await this.accessService.hasPermission(...) - Configuration
Validate that the requested strategy, feature, environment information, variant configuration, and tag-targeted features belong to the specified project before allowing reads or modifications.
Unleash admin API project-scoped endpoints project ownership validation = enabled
Event History
Frequently Asked Questions
Who can exploit the missing permission check?
Any authenticated user can exploit it. The vulnerable endpoint allows that user to modify segment assignments on any strategy across all projects.
What authorization is required to reach the affected endpoint?
No route-level permission is required because POST /api/admin/segments/strategies is configured with permission: NONE. Its intended handler-level permission check is bypassed because the asynchronous check is not awaited.
How can teams determine whether this specific issue is present in their code?
Inspect src/lib/features/segment/segment-controller.ts around line 345 for the POST /api/admin/segments/strategies handler. It is affected if hasPermission() is called without await and its returned Promise is used in the authorization conditional.
What is the direct remediation for the described permission bypass?
Await the accessService.hasPermission() call before evaluating its result. This makes a false permission result trigger the intended 403 response.