Skip to content

Deleting a record that has uploaded files, or force-deleting its subject, is refused #1572

Closed

Description

@joshunrau

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 single instrumentRecordModel.delete.
  • SubjectsService.deleteById with force (apps/api/src/subjects/subjects.service.ts:106) runs a $transaction of deleteMany over 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

  1. Give StorageService a method that deletes the objects for a set of FileSearchParams (DeleteObjectsCommand).
  2. 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 Restrict refuses.
  3. 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

  1. added
    BugType: existing behavior is wrong
    Difficulty: MediumStraightforward change, about a day or less
    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)
    on Sep 27, 2026
  2. gdevenyi commented on Sep 28, 2026

    @gdevenyi
    Contributor

    We need a redact-subject interface for sure. Removes/wipes all data but prevents re-use of the subject ID.

  3. MiroslavGit commented on Sep 29, 2026

    @MiroslavGit

    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?

  4. joshunrau commented on Sep 30, 2026

    @joshunrau
    CollaboratorAuthor

    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

  5. MiroslavGit commented on Oct 3, 2026

    @MiroslavGit

    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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)BugType: existing behavior is wrongDifficulty: MediumStraightforward change, about a day or lessPriority: MediumReal value; schedule normally

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions