Security Fix: Authorization check missing in mergeSpecifiedPersons and canMergeSpecifiedPersons
Summary
mergeSpecifiedPersons and canMergeSpecifiedPersons endpoints only validate that the caller's token is well-formed, but do not verify that the caller is one of the persons being merged. Any authenticated user can merge any two persons by knowing their UUIDs.
Affected file
server/account/src/operations.ts
Vulnerable code
canMergeSpecifiedPersons
export async function canMergeSpecifiedPersons (
...
): Promise<boolean> {
decodeTokenVerbose(ctx, token) // ← only checks token is valid, no ownership check
const { primaryPerson, secondaryPerson } = params
...
}
mergeSpecifiedPersons
export async function mergeSpecifiedPersons (
...
): Promise<void> {
decodeTokenVerbose(ctx, token) // ← only checks token is valid, no ownership check
const { primaryPerson, secondaryPerson } = params
...
await doMergePersons(db, primaryPerson, secondaryPerson)
}
Fix
The caller's account (extracted from the token) must be either primaryPerson or secondaryPerson. Otherwise throw Forbidden.
canMergeSpecifiedPersons
export async function canMergeSpecifiedPersons (
ctx: MeasureContext,
db: AccountDB,
branding: Branding | null,
token: string,
params: {
primaryPerson: PersonUuid
secondaryPerson: PersonUuid
}
): Promise<boolean> {
const { account } = decodeTokenVerbose(ctx, token)
const { primaryPerson, secondaryPerson } = params
if (primaryPerson == null || primaryPerson === '' || secondaryPerson == null || secondaryPerson === '') {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
// Caller must be one of the persons being merged
if (account !== primaryPerson && account !== secondaryPerson) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
if (primaryPerson === secondaryPerson) {
return false
}
const primaryPersonObj = await db.person.findOne({ uuid: primaryPerson })
if (primaryPersonObj == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.PersonNotFound, { person: primaryPerson }))
}
const secondaryPersonObj = await db.person.findOne({ uuid: secondaryPerson })
if (secondaryPersonObj == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.PersonNotFound, { person: secondaryPerson }))
}
const verifiedSecondaryIds = await db.socialId.find({ personUuid: secondaryPerson, verifiedOn: { $ne: null } })
return verifiedSecondaryIds.length === 0
}
mergeSpecifiedPersons
export async function mergeSpecifiedPersons (
ctx: MeasureContext,
db: AccountDB,
branding: Branding | null,
token: string,
params: {
primaryPerson: PersonUuid
secondaryPerson: PersonUuid
}
): Promise<void> {
const { account } = decodeTokenVerbose(ctx, token)
const { primaryPerson, secondaryPerson } = params
if (primaryPerson == null || primaryPerson === '' || secondaryPerson == null || secondaryPerson === '') {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
// Caller must be one of the persons being merged
if (account !== primaryPerson && account !== secondaryPerson) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
await doMergePersons(db, primaryPerson, secondaryPerson)
}
Impact
Without this fix, any authenticated user can:
- Obtain a valid token (e.g. by signing up)
- Know or enumerate two person UUIDs
- Call
mergeSpecifiedPersons to merge those persons — effectively hijacking or destroying another user's account identity
Steps to reproduce
# Attacker is logged in as person A, but merges persons B and C (unrelated to A)
curl -X POST http://<host>:3000/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <attacker_token>" \
-d '{
"id": "1",
"method": "mergeSpecifiedPersons",
"params": {
"primaryPerson": "<victim_uuid_1>",
"secondaryPerson": "<victim_uuid_2>"
}
}'
This succeeds with {"id":"1","result":null} — no error, persons merged.
Security Fix: Authorization check missing in
mergeSpecifiedPersonsandcanMergeSpecifiedPersonsSummary
mergeSpecifiedPersonsandcanMergeSpecifiedPersonsendpoints only validate that the caller's token is well-formed, but do not verify that the caller is one of the persons being merged. Any authenticated user can merge any two persons by knowing their UUIDs.Affected file
server/account/src/operations.tsVulnerable code
canMergeSpecifiedPersonsmergeSpecifiedPersonsFix
The caller's
account(extracted from the token) must be eitherprimaryPersonorsecondaryPerson. Otherwise throwForbidden.canMergeSpecifiedPersonsmergeSpecifiedPersonsImpact
Without this fix, any authenticated user can:
mergeSpecifiedPersonsto merge those persons — effectively hijacking or destroying another user's account identitySteps to reproduce
This succeeds with
{"id":"1","result":null}— no error, persons merged.