CVE case study
CVE-2026-59979: Authentication Is Not Tenant Authorization
How authentication-only routes and globally keyed service lookups enabled cross-tenant reads and state changes in Yosemite-Crew.
This is a defensive case study of CVE-2026-59979, based on the coordinated public advisory. It explains the trust boundary, the failure mode, and the engineering fix without reproducing a weaponized exploit.
- Severity
- High · 8.3
- Scoring
- CVSS 3.1
- Weakness
- CWE-639 · CWE-862
- Affected
- Main branch through bc1a1824b1a7eba2bc61176cdb61d9a2a11bce57
- Remediation state
- No complete fixed release is listed by the advisory
- Advisory published
- 21 Jun 2026
Official vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H
Why it matters
Several routes established who the caller was but did not establish whether that caller belonged to the organization owning the requested object or held the permission required for the operation. One disclosed read path lacked authentication entirely.
The service layer then looked up records by global identifiers without an organization constraint. In a multi-tenant application, that combination turns predictable or discovered object references into cross-tenant capabilities.
How I discovered it
I used a route-middleware census rather than testing endpoints at random. Yosemite-Crew had recently added organization and permission middleware to new service routes, so I compared create, read, update, and delete siblings in the same router.
The create routes carried authentication, withOrgPermissions(), and requirePermission(). The update and delete routes carried only authorizeCognito, while the single-service read route had no authentication middleware. I opened authorizeCognito to avoid assuming its name implied authorization; it verified the token and set userId, but performed no organization or role lookup.
I then followed :id through the controller. No tenant identity was passed to the service, and the service used a global findById(). Repeating the same census in the PMS task library and template routes revealed the same authentication-only pattern. The finding was therefore not one forgotten endpoint but an incomplete RBAC retrofit across a route family.
Trust boundary and root cause
Yosemite-Crew had separate middleware for Cognito authentication, organization membership, and role permission. Some create routes used the full stack, while related read, update, delete, library, and template routes used authentication only or no guard.
Authorization was therefore inconsistent at the route layer and absent as defense in depth at the data-access layer. The backend accepted a globally supplied record ID as enough context to read or mutate the object.
Source-to-sink trace
- 01Intent control
service.router.ts · create routesSibling operations use authentication, organization membership, and an explicit permission requirement.
- 02Missing route guard
service.router.ts · GET/PATCH/DELETE /:idRead has no guard; update and delete use authentication only.
- 03Identity-only middleware
middlewares/auth.ts · authorizeCognitoThe middleware establishes user identity but not tenant membership or function permission.
- 04Unscoped data sink
service.service.ts · findById()The controller supplies only a global object ID, and the service lookup does not filter by
organisationId.
- 01Low-privilege identity
A registered caller reaches an affected service, task-library, or template route.
- 02Foreign object key
The request names a record belonging to another organization.
- 03Incomplete middleware
The route authenticates the caller but omits organization and function-level authorization.
- 04Global lookup
The service reads, changes, or deletes the record by ID without tenant scoping.
Safe proof of concept
Prerequisites
- Git and
rginstalled locally. - A clone of the public Yosemite-Crew repository; no account, token, database, or hosted target is needed.
Step-by-step reproduction
- Clone the public repository and check out
bc1a1824b1a7eba2bc61176cdb61d9a2a11bce57, the revision named by the advisory. - Run the assertions below. They verify the full RBAC stack on sibling create routes, authentication-only update/delete routes, and the unguarded read route.
- Open
authorizeCognitoand confirm that it establishesuserIdbut performs no organization or permission lookup. - Trace
updateServicefrom router to controller to service. Confirm that only:idand the DTO reach a globalfindById()query. - Repeat the middleware census for task-library and template routes; this establishes the affected family rather than stopping at one service endpoint.
- If you build an optional runtime lab, seed two synthetic organizations through the project’s supported fixtures, obtain a valid local token, fetch a complete valid
ServiceRequestDTO, change one benign field, and PATCH that full DTO. Do not use a one-field body or a production record.
Clone the audited revision
git clone https://github.com/YosemiteCrew/Yosemite-Crew.git yosemite-cve-lab
cd yosemite-cve-lab
git checkout bc1a1824b1a7eba2bc61176cdb61d9a2a11bce57Offline authorization assertions
SERVICE_ROUTER=apps/backend/src/routers/service.router.ts
AUTH=apps/backend/src/middlewares/auth.ts
SERVICE=apps/backend/src/services/service.service.ts
TASK_ROUTER=apps/backend/src/routers/task.router.ts
# Intent control: sibling create routes have organization + permission guards.
rg -n 'withOrgPermissions|requirePermission\("specialities:edit:any"\)' "$SERVICE_ROUTER"
# Gap: read is unguarded; update/delete are authentication-only.
rg -n 'router\.(get|patch|delete)\("/:id"' "$SERVICE_ROUTER"
# Authentication establishes identity, not tenant or role authorization.
rg -n 'authorizeCognito|authReq.userId = payload.sub' "$AUTH"
# Controller/service path reaches a global lookup with no tenant key.
rg -n 'updateService|findById\(oid\)|organisationId' "$SERVICE"
# Same census for the task library/template family.
rg -n 'pms/library|pms/templates|withTaskOrgPermissions|requirePermission' "$TASK_ROUTER"Expected evidence
- The output shows the complete authorization stack on sibling create/task operations and its absence from the disclosed read/update/delete family.
- The authentication middleware sets caller identity without loading organization membership or permissions.
- The update path reaches
findById()without anorganisationIdpredicate; the proof contacts no application endpoint and changes no data.
The sibling create and canonical PMS task routes in the same files carry organization and explicit permission middleware. That in-source control proves the application has an RBAC design and isolates the missing guards as an inconsistent route-family retrofit.
The vendor advisory still lists no complete fixed release. A candidate fix must add authentication, organization membership, permission enforcement, and organisationId-scoped queries across every named route family. Only then should an owned two-tenant integration test assert 401, 403, or a non-enumerating 404 and an unchanged synthetic record.
Impact in context
The advisory documents cross-tenant disclosure, modification, and deletion of service-catalogue and PMS task-definition data. Integrity and availability impact are high because state-changing operations can affect downstream workflows such as bookings and billing.
Most affected write paths require only a low-privilege account and a target identifier. The disclosed service-by-ID read path was reachable without authentication, further demonstrating why every object operation needs its own policy check.
Remediation and verification
Apply authentication, organization membership, and explicit permission middleware to every affected route. Backend methods should also accept the tenant identity and query by both object ID and organisationId, so a missed route guard fails closed.
As of this review, the vendor advisory lists no fixed release. Public source shows partial hardening of service update and delete paths, but it does not justify claiming the entire disclosed route family is fixed. Deployment owners should compare their version with the vendor advisory and verify every named operation.
Engineering lesson
Authorization matrices should be tested as a complete family: create, list, read, update, delete, archive, and template/library variants. A correct guard on one method says nothing about its siblings, and authentication middleware is never a substitute for tenant and function-level authorization.
Primary public references
- Vendor advisory GHSA-256m-gg99-4274
- Authorization-intent pull request 1530
- Partial public hardening commit
Disclosure boundary: only already-public technical detail is included here. Unpublished validation material, private communications, secrets, and weaponized payloads remain excluded.