file_storage: StoredFile is not tenant scoped #361
Description
Summary
file_storage.StoredFile doesn't use MultiTenantMixin, so on a multi-tenant host any file id is readable and reusable across every tenant. A module that stores a StoredFile id in a MultiTenantMixin-protected row (e.g. a media reference on a tenant-scoped record) inherits this hole: tenant A can reference — and, depending on the endpoint, read — a file id that belongs to tenant B, because the file table itself never filters.
Observed
file_storage/models.py:18:
class StoredFile(Base, AuditMixin, SoftDeleteMixin, table=True):
...MultiTenantMixin is absent — compare to users/models/user.py, which does have a nullable tenant_id column even without the mixin. StoredFile has neither.
Minimal reproduction (SQLite), showing the table participates in none of the framework's tenant filtering because it isn't a MultiTenantMixin subclass at all:
from simple_module_db.mixins import MultiTenantMixin
from file_storage.models import StoredFile
assert not issubclass(StoredFile, MultiTenantMixin)
print("StoredFile carries no tenant_id column; any file id is valid under any current_tenant_id")This mirrors the read-path gap already measured for mapper-scoped tables (see the sibling read/flush issues from this batch): even once those are fixed, file_storage cannot benefit, because there is no tenant_id column for a loader-criteria filter to attach to in the first place.
Expected
StoredFile isolates files per tenant the same way a MultiTenantMixin table isolates any other row: upload under tenant A is invisible to (and cannot be referenced successfully by) tenant B.
Why a module cannot work around it
records (and any module storing media references) does not own file_storage's table or upload/serve endpoints; it can validate that a referenced file id exists, but has no way to also assert it belongs to the caller's tenant, because the file row carries no tenant information to check against.
Proposed API
MultiTenantMixin on file_storage.StoredFile, with the storage key/path also tenant-scoped so two tenants' otherwise-identical filenames don't collide on the same backend key.
Context
Found while making the records module (antosubash/smpy_modules#37) multi-tenant; design doc docs/plans/2026-09-23-records-multitenancy.md there.
Activity
antosubash commentedon Oct 1, 2026
Closing: filed before #370 (fail-closed tenancy + tenants module) landed, so the premise is stale. Module tenancy adoption is being re-tracked with fresh per-module issues in the post-#370 format (see #371–#377); any remaining gap here gets a new issue and lands in the tenancy adoption PR stack.