Repository navigation
Deleting a record that has uploaded files, or force-deleting its subject, is refused #1572
Description
Summary
On an instance with file storage enabled, an administrator cannot delete an instrument record that has uploaded files, and cannot force-delete a subject who has such a record. Both requests are refused. Nothing in the API ever deletes an object from the bucket either, so fixing the refusal alone would leave the files behind.
Detail
InstrumentRecordFile.record is a required relation with no onDelete (apps/api/prisma/schema.prisma:200). On MongoDB, Prisma emulates the default referential action, Restrict, so deleting the parent record fails while file rows still point at it. Neither deletion path touches those rows:
InstrumentRecordsService.deleteById(apps/api/src/instrument-records/instrument-records.service.ts:165) issues a singleinstrumentRecordModel.delete.SubjectsService.deleteByIdwithforce(apps/api/src/subjects/subjects.service.ts:106) runs a$transactionofdeleteManyover records, sessions and the subject. The record delete is refused and the whole transaction rolls back.
A local reproduction against a real MongoDB during the 2026 security audit showed the delete refused outright and rolled back atomically. The web app has no record-delete or subject-delete mutation, so this is reached through the API directly or through odc-cli instrument-records delete / odc-cli subjects delete (which always sends force: true).
StorageService only constructs CreateBucketCommand, GetObjectCommand, HeadBucketCommand and PutObjectCommand, so no code path can remove the object itself.
Suggested fix
- Give
StorageServicea method that deletes the objects for a set ofFileSearchParams(DeleteObjectsCommand). - In both deletion paths, check authorization first. Then read the record's file rows and delete them before the record, in the same transaction. Deleting the record first is exactly what
Restrictrefuses. - After the transaction commits, delete the objects. Log a failed object delete loudly: the rows are gone by then, so nothing else will ever find the orphan.
Tests: unit tests for both services and for the storage method, and an e2e test that deletes a record carrying a file (the api integration suite may be the better home, since the web app has no delete UI).
Notes
The audit filed this as a low-severity retention finding (objects left in the bucket after deletion). On re-verification that effect does not occur, because the deletion itself is refused. What remains is a functional defect. Re-verified on devel at 0d2237394.
Activity
gdevenyi commentedon Sep 28, 2026
We need a redact-subject interface for sure. Removes/wipes all data but prevents re-use of the subject ID.
MiroslavGit commentedon Sep 29, 2026
Hi, I’ve forked the project and have it running locally. I’d like to work on this issue. Would it be possible to assign it to me?
joshunrau commentedon Sep 30, 2026
Sure, please go ahead! Thank you.
You can just make the PR from your branch when you're ready. Let me know if you have any questions or issues
MiroslavGit commentedon Oct 3, 2026
Hi, I've opened PR #1605 with the fix for this issue.
It includes the record/subject deletion changes along with unit, API integration, and end-to-end tests.
Thanks!