fix(security): VULN-01 admin escalation + VULN-02 path traversal hardening

VULN-01 - Admin privilege escalation:
- Harden register() to reject is_admin=true
- Add /api/setup endpoint with setup_token for initial admin creation
- Add SetupAdminDto and setup_token to AppState
- Remove dead code from auth handler

VULN-02 - Path traversal (CVSS ~8.6):
- Solution A+E: Harden StoragePath constructors (from_string, new, join)
  to strip '..' and '.' segments and reject slash injection
- Solution B: resolve_path() now returns Result<PathBuf>, calls
  validate_path() internally, and verifies resolved path stays under root
- Update StoragePort trait signature to return Result<PathBuf, DomainError>
- Remove dead code: FilePathResolutionPort, StorageVerificationPort,
  DirectoryManagementPort (declared but never implemented)
- Add 17 security tests covering traversal attack vectors
This commit is contained in:
Dionisio
2026-03-04 14:14:40 +01:00
parent 3e2b6f11c1
commit 98fb3e6408
10 changed files with 463 additions and 217 deletions
+4 -2
View File
@@ -10,8 +10,10 @@ use super::storage_ports::{FileReadPort, FileWritePort};
/// Secondary port for storage operations
pub trait StoragePort: Send + Sync + 'static {
/// Resolves a domain path to a physical path
fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf;
/// Resolves a domain path to a physical path.
///
/// Returns an error if the path contains unsafe segments (defense-in-depth).
fn resolve_path(&self, storage_path: &StoragePath) -> Result<PathBuf, DomainError>;
/// Creates directories if they don't exist
async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>;
-24
View File
@@ -303,30 +303,6 @@ pub trait FileWritePort: Send + Sync + 'static {
// Auxiliary ports (unchanged)
// ─────────────────────────────────────────────────────
/// Secondary port for file path resolution
pub trait FilePathResolutionPort: Send + Sync + 'static {
/// Gets the storage path of a file
async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>;
/// Resolves a domain path to a physical path
fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf;
}
/// Secondary port for file/directory existence verification
pub trait StorageVerificationPort: Send + Sync + 'static {
/// Checks whether a file exists at the given path
async fn file_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
/// Checks whether a directory exists at the given path
async fn directory_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
}
/// Secondary port for directory management
pub trait DirectoryManagementPort: Send + Sync + 'static {
/// Creates directories if they do not exist
async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>;
}
/// Secondary port for storage usage management
pub trait StorageUsagePort: Send + Sync + 'static {
/// Updates storage usage statistics for a user