allowCustomObjectId accepts an operation as objectId and creates an unreachable user
New Issue Checklist
- Report security issues confidentially.
- Any contribution is under this license.
- Before posting search existing issues.
Issue Description
With allowCustomObjectId: true, a create whose objectId is an operation envelope such as
{"__op":"Delete"} is accepted, and the row that results is unusable in three separate ways.
getType returns nothing for an operation envelope, so validateObject hits
if (!expected) { continue; } and never compares the value against the String type of the
objectId column. The value then reaches the database layer, where the driver generates its own
_id.
The outcome is a _User that:
- is stored under a Mongo-generated
_idsuch as6a862fa06e17011e3f5a9c96, which is not a Parse objectId and is not what the client was told, - has
_rpermand_wpermof["[object Object]"], because the owner ACL is keyed by stringifying the same operation object, so the row is readable and writable by nobody, - and is echoed back to the client with
"objectId": {"__op":"Delete"}, along with a working session token for a user that session can never read.
A non-operation value behaves correctly and is refused with 111 INCORRECT_TYPE, so this is
specific to values whose type cannot be inferred.
Steps to reproduce
Start a server with allowCustomObjectId: true, then:
curl -s -X POST http://127.0.0.1:1337/parse/users \
-H 'X-Parse-Application-Id: myAppId' -H 'Content-Type: application/json' \
-d '{"objectId":{"__op":"Delete"},"username":"probe2","password":"pw"}'Then look at the stored row, with the master key:
curl -s -G http://127.0.0.1:1337/parse/classes/_User \
-H 'X-Parse-Application-Id: myAppId' -H 'X-Parse-Master-Key: myMasterKey' \
--data-urlencode 'where={"username":"probe2"}'For contrast, "objectId": 123 in the same body is refused as expected.
Actual Outcome
201 {"objectId":{"__op":"Delete"},"createdAt":"...","sessionToken":"r:..."}and in MongoDB:
_id: "6a862fa06e17011e3f5a9c96"
_rperm: ["[object Object]"]
_wperm: ["[object Object]"]An anonymous GET /classes/_User does not return the row, and neither does a query by another
authenticated user, so the account exists and is unreachable by everyone except the master key.
With "objectId": 123 instead: 400 {"code":111,...} and no row, which is the expected behaviour.
Expected Outcome
An objectId whose type cannot be inferred should be refused rather than skipped. Either the same
111 INCORRECT_TYPE that every other non-string value produces, or an explicit rejection of
operation envelopes in that position.
At minimum the response should not report an objectId that differs from the stored _id, since a
client cannot address the object it just created.
Environment
Server
- Parse Server version:
9.10.1-alpha.6(commitca75b1fe) - Operating system:
macOS 26.5.2 - Local or remote host:
local
Database
- System (MongoDB or Postgres):
MongoDB - Database version:
7.0.25 - Local or remote host:
local
Client
- SDK (iOS, Android, JavaScript, PHP, Unity, etc):
none, raw REST over HTTP - SDK version:
n/a
Logs
No error is logged; the request succeeds.
Found while building a reimplementation and comparing behaviour against a server built at
ca75b1fe. The stored _id and permission columns above were read directly out of MongoDB rather
than inferred.
Source: parse-community/parse-server