Describe the bug
In apps/public-api/src/controllers/data.controller.js, the updateSingleData handler reads project.databaseUsed from the request-time project snapshot and checks the quota before writing:
const existingDoc = await Model.findOne(queryFilter).lean();
const oldSize = mongoose.mongo.BSON.calculateObjectSize(existingDoc);
const newSize = mongoose.mongo.BSON.calculateObjectSize(simulatedNewDoc);
const sizeDelta = newSize - oldSize;
if (sizeDelta > 0) {
if ((project.databaseUsed || 0) + sizeDelta > project.databaseLimit) {
return next(new AppError(403, 'Storage quota exceeded.'));
}
}
result = await Model.findOneAndUpdate(queryFilter, ...);
await Project.findByIdAndUpdate(project._id, { $inc: { databaseUsed: sizeDelta } });
The project.databaseUsed value is fetched once at request start. Multiple concurrent update requests can all read the same stale value, all pass the quota check, and all proceed to write and increment the counter, collectively pushing the database usage significantly over the declared limit.
To Reproduce
- Set a project database limit close to its current usage (e.g., 1 MB used, 1.1 MB limit).
- Send 10 concurrent PATCH requests, each adding ~50 KB of data to a document.
- Each request reads the same
databaseUsed = 1 MB and passes the check (1 MB + 50 KB < 1.1 MB).
- All 10 updates succeed, pushing actual usage to ~1.5 MB — 400 KB over the limit.
Expected behavior
The size check and the increment should be atomic. Use a conditional MongoDB update that enforces the limit at the database level:
const result = await Project.findOneAndUpdate(
{ _id: project._id, $expr: { $lte: [{ $add: ['$databaseUsed', sizeDelta] }, project.databaseLimit] } },
{ $inc: { databaseUsed: sizeDelta } }
);
if (!result) return next(new AppError(403, 'Storage quota exceeded.'));
Then proceed with the document update only after the quota increment succeeds.
Additional context
File: apps/public-api/src/controllers/data.controller.js, function: updateSingleData
This pattern is already used correctly in insertData (single-document insert). updateSingleData should apply the same approach.
Labels: bug, nsoc26, level3
Describe the bug
In
apps/public-api/src/controllers/data.controller.js, theupdateSingleDatahandler readsproject.databaseUsedfrom the request-time project snapshot and checks the quota before writing:The
project.databaseUsedvalue is fetched once at request start. Multiple concurrent update requests can all read the same stale value, all pass the quota check, and all proceed to write and increment the counter, collectively pushing the database usage significantly over the declared limit.To Reproduce
databaseUsed = 1 MBand passes the check (1 MB + 50 KB < 1.1 MB).Expected behavior
The size check and the increment should be atomic. Use a conditional MongoDB update that enforces the limit at the database level:
Then proceed with the document update only after the quota increment succeeds.
Additional context
File:
apps/public-api/src/controllers/data.controller.js, function:updateSingleDataThis pattern is already used correctly in
insertData(single-document insert).updateSingleDatashould apply the same approach.Labels:
bug,nsoc26,level3