Bind the requested object to the authenticated owner at the mutation.
A valid session is only half of the authorization decision.
A personal environment is a user-owned object. To change it, a backend needs two facts together: who is making the request, and whether that person owns the selected environment. Hoppscotch’s GraphQL authentication guard established the first fact, but the affected update path did not carry that identity into the operation on the object.
The public advisory covers the self-hosted Hoppscotch backend. It describes unauthorized access, modification and deletion when an authenticated user knows another user’s environment identifier. The mechanism is an insecure direct object reference: the supplied identifier selected a record without the required owner constraint. The backend accepted an object address where it needed an authorization decision.
Identity stopped short of the database operation.
Before the fix, updateUserEnvironment accepted an environment ID, a name and serialized variables. Its database update matched the ID alone. The resolver applied GqlAuthGuard but did not extract the authenticated user for this method. There was no owner value available to constrain the update. The service then constructed a response from the updated object, including its owner, name and variables.
Delete illustrates a different way to lose the same boundary. Its service method already received the caller’s UID and used it to look up that caller’s global environment. That special-case check prevented one kind of deletion; it did not authorize the requested personal environment. The eventual delete still selected by ID alone. Seeing identity somewhere in a method is therefore weaker evidence than seeing it constrain the consequential query.
- Authenticated user A
The request passes the login guard.
- Environment ID for B
The caller supplies another object’s ID.
- Match the ID alone
The mutation does not require owner A.
- Authenticated user A
The resolver obtains the trusted user.
- ID + owner A
The service scopes the mutation to both.
- No owned match
Another user’s environment is not changed.
Conceptual two-user example. A and B represent separate accounts on one instance; this is not a replay of a private test.
Put ownership in the mutation’s selection predicate.
The patch adds the authenticated user parameter to the update resolver and passes it to the service. The service requires both the requested environment ID and that user’s UID in the update predicate. Delete receives the corresponding UID constraint. The same commit also adds an owner constraint to the global-environment clearing query.
This makes the security rule visible at the point that changes state. The ID answers which object the caller is asking for; the owner field answers whether it is within that caller’s personal scope. Keeping them in the same database operation avoids relying on an earlier, unrelated lookup as evidence of authorization.
The resolver obtains the owner identity from authenticated request context. An owner field supplied by the caller would defeat that property. The difference is small in the query, but the complete fix spans both layers: carry trusted identity through the resolver, then use it where data changes.
An unpredictable ID reduces discovery, not authority.
The advisory assigns High severity and CVSS 3.1 8.3. It identifies self-hosted backend versions through 2026.1.1 as affected and 2026.2.0 as patched. Environments can hold request credentials and tokens, making unauthorized access or changes consequential. The attacker must already possess a target environment ID; the CUID format makes broad guessing less practical.
- A shared instance
- A logged-in attacker targets an environment belonging to a different user on that instance.
- A known object ID
- The advisory requires knowledge of the target identifier; it does not establish unrestricted enumeration.
- Sensitive environment data
- Practical impact depends on the data stored and how that environment is used.
Prove isolation with two owners and one real data store.
The upstream commit updates service tests to supply the newly required user parameter. Those edits are useful evidence that the calling contract changed. They should not be described as proof of a complete cross-account integration suite: a mock that returns an object for any query can hide a missing ownership constraint.
A focused regression check creates one environment for each of two ordinary accounts in an isolated instance. Keep the object IDs fixed and change the authenticated account. Verify the response and persisted state separately. For a rejected operation, also check that no data or change notification crosses the ownership boundary. The following matrix is a suggested review design, not a claim of tests executed by TraceMint.
| Scenario | Expected boundary |
|---|---|
| Owner updates their environment | The intended change succeeds and the owner receives the result. |
| Different account uses the same ID | Update fails; the original state remains and no environment data is returned. |
| Different account attempts deletion | Deletion fails; the owner’s environment still exists. |
| Related clear and bulk operations | Each operation enforces its documented owner scope independently. |
Follow the principal all the way to the write.
Authorization review becomes more reliable when it follows data flow instead of looking only for guard annotations. Start at the authenticated principal, follow each parameter through the resolver and service, and finish at the exact query that reads or changes the object. If the identity disappears before the query, explain where the owner decision is enforced. A route being authenticated is not that explanation.
Review each operation independently. A list endpoint scoped to the current user does not protect a mutation that accepts a separate ID. A check for a global environment does not prove ownership of a personal one. Useful negative tests preserve a valid session and valid input while changing only the relationship between the principal and the target.
The public record credits TraceMint as a Reporter alongside another reporter; the original advisory preserves the complete credit record. Hoppscotch’s maintainers supplied the remediation. This study does not claim sole discovery, identify a private research workflow, or demonstrate access to any live deployment. Its diagrams and review matrix explain the published mechanism.
Follow the evidence.
Public upstream records and immutable code revisions, reviewed 2026-09-19.
- Hoppscotch security advisory
Scope, prerequisites, impact, fixed release and complete reporter credits.
- Service before the fix
Immutable parent revision · 55c1cb82 · update and delete select by ID.
- Upstream corrective commit
a1be60da · resolver, service and test changes. The commit also addresses a separate onboarding issue.
- Resolver after the fix
Authenticated user extraction and propagation into the update service.
- Service after the fix
Owner constraints in update, delete and clear operations.
- Upstream service tests
Updated calls include the authenticated-user parameter.
- Hoppscotch 2026.2.0 release
Release record links the security fix in pull request #5897.
Brief code excerpts from hoppscotch/hoppscotch. Upstream license: MIT License. All remediation described here is upstream work.
Read the original record
This case study analyzes an already-public advisory and upstream source. For the complete impact, remediation and credit record, see the Hoppscotch security advisory.
Editorial publication is separate from disclosure. No private discovery timeline or method has been inferred.