fix(audit-2026-06-11/F-FS-002): belt-and-suspenders collection validation on files read/delete
The admin files endpoints hit FsFilesRepo directly (not via
FilesServiceImpl, which validates), so only create/update guarded the
collection — head/get/list/delete built FS paths from an unvalidated
value. Today nothing can store a traversal-shaped collection, but one
future bad migration / restore tool inserting collection='../../etc'
would give the read/delete paths arbitrary host-file reach.
- FsFilesRepo::{head,get,list,delete} now call guard_collection (create
and update already did).
- The three admin endpoints (list/get/delete) call
validate_files_collection up front for a clean 422 instead of the
repo guard surfacing as an opaque 500.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -350,6 +350,7 @@ impl FilesRepo for FsFilesRepo {
|
||||
collection: &str,
|
||||
id: Uuid,
|
||||
) -> Result<Option<FileMeta>, FilesRepoError> {
|
||||
Self::guard_collection(collection)?;
|
||||
let row: Option<FileRow> = sqlx::query_as(
|
||||
"SELECT id, collection, name, content_type, size_bytes, \
|
||||
checksum_sha256, created_at, updated_at \
|
||||
@@ -369,6 +370,7 @@ impl FilesRepo for FsFilesRepo {
|
||||
collection: &str,
|
||||
id: Uuid,
|
||||
) -> Result<Option<Vec<u8>>, FilesRepoError> {
|
||||
Self::guard_collection(collection)?;
|
||||
let row: Option<(String,)> = sqlx::query_as(
|
||||
"SELECT checksum_sha256 FROM files \
|
||||
WHERE app_id = $1 AND collection = $2 AND id = $3",
|
||||
@@ -434,6 +436,7 @@ impl FilesRepo for FsFilesRepo {
|
||||
collection: &str,
|
||||
id: Uuid,
|
||||
) -> Result<Option<FileMeta>, FilesRepoError> {
|
||||
Self::guard_collection(collection)?;
|
||||
// SELECT + DELETE in one tx; unlink afterwards (outside the tx).
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let row: Option<FileRow> = sqlx::query_as(
|
||||
@@ -479,6 +482,7 @@ impl FilesRepo for FsFilesRepo {
|
||||
cursor: Option<&str>,
|
||||
limit: u32,
|
||||
) -> Result<FilesListPage, FilesRepoError> {
|
||||
Self::guard_collection(collection)?;
|
||||
let limit = if limit == 0 {
|
||||
FILES_LIST_DEFAULT_LIMIT
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user