October 24, 2026
When One Integer Crosses the Authorization Boundary
A cross-account authorization flaw hidden inside a preference endpoint

By Insid_e
2 min read
Some authorization bugs are obvious.
You change an identifier and suddenly get another user's private data.
Others are quieter.
While reviewing an authenticated booking flow, I came across an endpoint responsible for returning marketing communication preferences.
The response itself was simple.
What caught my attention was that the client was supplying the member identifier directly.
The request
The application called:
POST /api/cusco/communicationPreference/getMarketingDisplayRulesPOST /api/cusco/communicationPreference/getMarketingDisplayRulesThe request contained a structure similar to:
{
"subscriptionQuery": {
"memberId": 12345678
},
"userId": "..."
}{
"subscriptionQuery": {
"memberId": 12345678
},
"userId": "..."
}The response returned the preference state associated with that member:
{
"isSuccess": true,
"result": {
"isCheckBoxDisplay": true,
"isDefaultOptIn": false,
"userSubscriptionStatus": "OptOut"
},
"error": null
}{
"isSuccess": true,
"result": {
"isCheckBoxDisplay": true,
"isDefaultOptIn": false,
"userSubscriptionStatus": "OptOut"
},
"error": null
}For the authenticated user's own account, this was expected.
The real question was whether the backend actually tied that memberId to the authenticated session.
Testing the authorization boundary
I used two separate accounts, each with its own member ID.
I authenticated as Account B and kept the session unchanged throughout the test.
Same cookies.
Same authentication context.
Same browser session.
Only the memberId changed.
With Account B's own identifier:
Session B + memberId B
โ OptOutSession B + memberId B
โ OptOutI then supplied Account A's identifier while still authenticated as Account B:
Session B + memberId A
โ OptOutSession B + memberId A
โ OptOut
The request was accepted and the preference state associated with the supplied member was returned.
I continued testing other identifiers without changing the session:
memberId = 0
โ Unknown
memberId = 2147483647
โ Unknown
adjacent memberId
โ OptIn
another adjacent memberId
โ OptInmemberId = 0
โ Unknown
memberId = 2147483647
โ Unknown
adjacent memberId
โ OptIn
another adjacent memberId
โ OptInAcross all of these requests, the authenticated session remained the same.
The returned state followed the client-controlled memberId.
That was the important part.
The authorization failure
The backend was effectively answering:
Which memberId did the client request?Which memberId did the client request?without first enforcing:
Is this authenticated account allowed to access that member?Is this authenticated account allowed to access that member?Those are two different questions.
An identifier can be used to locate an object.
It should not be treated as proof that the caller owns that object.
In this case, Account B could supply Account A's identifier and receive data associated with Account A.
That means the ownership boundary between authenticated accounts was not properly enforced.
Why the response alone doesn't tell the whole story
The endpoint did not expose passwords, authentication tokens or payment information.
But severity is not determined only by the sensitivity of one returned field.
The more important security issue was that one authenticated user could access an object associated with another account simply by changing a client-controlled identifier.
That changes the question from:
How sensitive is this preference value?
to:
Why can one account resolve another account's object at all?
That is the more useful authorization question.
Root cause
The underlying problem was simple:
object selection and authorization were treated as the same thing.
The client supplied the member identifier.
The backend located the corresponding preference state.
But the server did not sufficiently bind that object to the authenticated identity before returning it.
For an endpoint that only needs to operate on the current user's data, a safer design would be to derive the member context directly from the authenticated session.
If a client-supplied identifier is required, ownership should be validated server-side before any data is returned.
Remediation
The main fix is to enforce object-level authorization on every request.
Conceptually:
authenticatedMember = session.memberId
requestedMember = request.memberId
if requestedMember != authenticatedMember: reject requestauthenticatedMember = session.memberId
requestedMember = request.memberId
if requestedMember != authenticatedMember: reject requestEven better, when -possible:
memberId = session.memberIdmemberId = session.memberIdThis removes the client-controlled ownership decision entirely.
Rate limiting can help reduce enumeration, but it does not solve the underlying authorization issue.
Final thought
One authenticated session.
One changed identifier.
One missing ownership check.
The returned data was simple.
The authorization failure behind it was not.