# CVE-2026-58165 Patch Analysis

## Target and Fix Commits

- **Vulnerable commit:** `c2e6c8ee3da42321ef22442c8d25d0413caf4a4f`
- **Original fix:** `3027fdffd3e57884487b7c46e5e669cfbc8becdf` — "Prevent enrollment-based privilege escalation to admin identities. Fixes #4010"
- **Strengthening follow-up:** `5fb197935dcd3f0a5633a7be20b2dde418ca260a` — "Deny enrollment access on identity read error. For #4010"
- **Backport of the combined fix:** `eba2afbe0683adbcaf5c52d46e620ef2dc403035` — backport to release-v2.0.x of issue #4013
- **Latest main tested:** `45b5046f5259e685d7d99e54564c7e38eec9f1f7` (includes `5fb197935`)

## What the Fix Changes

The fix is contained in `controller/internal/routes/enrollment_router.go` and adds two authorization guards to the enrollment management REST API (`/edge/management/v1/enrollments`):

1. **`allowNonAdminEnrollmentForIdentity`** (later renamed `checkNonAdminEnrollmentForIdentity`) is invoked by `Create`. It checks whether the caller has `AdminPermission`. If not, it reads the target identity from the request body and denies the request when that identity has `IsAdmin = true`.

2. **`allowNonAdminAccessToEnrollment`** (later renamed `checkNonAdminAccessToEnrollment`) is invoked by `Detail`, `Delete`, and `Refresh`. It reads the enrollment entity by URL id, extracts its `IdentityId`, and delegates to the same admin-target check. This prevents a non-admin from reading, refreshing, or deleting an enrollment that belongs to an admin identity.

3. **`List`** is changed from `ListWithHandler` to `ListWithQueryF`. When the caller is not an admin, the handler appends the predicate `not (identity.isAdmin = true)` to the query, so admin-identity enrollments are filtered out of list results.

The fix also adds a regression test (`tests/permissions_enrollment_test.go::Test_Permissions_Enrollment_AdminIdentityEscalation`) that verifies each of the above paths is blocked for a non-admin with the `enrollment` permission.

## Fix Assumptions

The fix relies on the following invariants:

- The only runtime path that creates an identity-targeted enrollment is the management API `/enrollments` `Create` handler. Other create paths (e.g., `IdentityRouter.Create` with bundled enrollments) are guarded by the identity router, which already blocks non-admins from creating `IsAdmin` identities.
- An enrollment token is safe once created because the unauthenticated `/enroll` redeem endpoint is reached only by someone who already possesses the token. The fix therefore does not add an admin check to the redeem endpoint itself.
- A non-admin identity cannot possess `AdminPermission` through role attributes or policy assignments; only the explicit admin flag grants that permission.
- The `Identity.Read` and `Enrollment.Read` manager operations are reliable and either return a valid entity or a hard failure. The original fix silently ignored read errors and treated them as "not admin / not found," which was later recognized as a gap.

## What the Fix Does NOT Cover (and Why)

- **Redemption of pre-existing admin enrollments:** The unauthenticated `/enroll` endpoint continues to redeem any valid enrollment token, including one created for an admin identity. This is not a bypass of the create-time guard; it is the intended enrollment lifecycle. An attacker can only exploit this if they already obtained an admin enrollment token through some other means.
- **Non-admin identities gaining `AdminPermission`:** The fix assumes `rc.HasPermission(permissions.AdminPermission)` correctly reflects the caller's admin status. If a non-admin could somehow be granted `AdminPermission` (e.g., through a bug in policy evaluation), the guard would be bypassed. No such mechanism was found.
- **Race-condition / transient read errors:** The original fix (3027fdffd) ignored errors from `Identity.Read` and `Enrollment.Read`. If a non-admin could cause a read error for an existing admin identity while the later create path still succeeded, the guard could be bypassed. The follow-up commit `5fb197935` closes this gap by denying on error rather than allowing. We were unable to identify a practical way to trigger such a transient error in normal operation.
- **Alternative enrollment types:** The fix is placed before the enrollment method is evaluated, so it applies to OTT, OTT-CA, and UPDB. No alternative enrollment method was found that would bypass the identity check.
- **Identity creation with bundled enrollments:** The identity router's `Create` handler already rejects non-admin attempts to create an `IsAdmin` identity. Because `CreateWithEnrollments` always ties the new enrollment to the newly created identity, this path cannot target an existing admin identity.

## Comparison of Behavior Before and After the Fix

| Path | Vulnerable (`c2e6c8ee3`) | Original Fix (`3027fdffd`) | Latest Main (`45b5046f5`) |
|---|---|---|---|
| Create OTT enrollment for admin identity | 201 Created | 401 Unauthorized | 401 Unauthorized |
| Create OTT-CA enrollment for admin identity | 201 Created | 401 Unauthorized | 401 Unauthorized |
| Create UPDB enrollment for admin identity | 201 Created | 401 Unauthorized | 401 Unauthorized |
| List enrollments as non-admin | leaks admin enrollment | filters admin enrollments | filters admin enrollments |
| Detail admin enrollment as non-admin | 200 OK | 401 Unauthorized | 401 Unauthorized |
| Refresh admin enrollment as non-admin | 200 OK | 401 Unauthorized | 401 Unauthorized |
| Delete admin enrollment as non-admin | 200 OK | 401 Unauthorized | 401 Unauthorized |
| Redeem existing admin enrollment token | 404/400 (token not usable in harness) | 400 | 400 |

The variant harness confirms that all tested alternative methods and query paths are blocked by the original fix and remain blocked in the latest main branch.

## Target Security Policy Context

OpenZiti's published vulnerability disclosure policy states that "This policy is applicable to all the code within the OpenZiti project, and anyone is eligible to research and/or submit vulnerability information without fear of legal reprisal." The policy excludes penetration testing of deployed network instances not owned by the tester. The variant testing in this report is performed against the project's own integration test harness and falls within the in-scope code path.

## Completeness Assessment

The original patch (`3027fdffd`) is functionally complete for the practical attack surface exercised in this report: it blocks all tested alternative enrollment types and all CRUD/list paths for admin-identity enrollments. The follow-up commit (`5fb197935`) is a defense-in-depth hardening that removes the possibility of an error-in-read being misinterpreted as a non-admin identity. No concrete bypass of the fixed code was demonstrated.

The only residual exposure is the redemption of a pre-existing admin enrollment token at the unauthenticated `/enroll` endpoint, which is consistent with OpenZiti's enrollment-by-token design and is not a vulnerability in the management API authorization logic.
