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
@@ -352,6 +352,32 @@ impl AdminSettingsService {
})
}
// ========================================================================
// System Initialization
// ========================================================================
/// Check if the system has been initialized (first admin created).
/// Returns `true` if the `system_initialized` flag is set to `"true"` in the DB.
pub async fn is_system_initialized(&self) -> bool {
match self.settings_repo.get("system_initialized").await {
Ok(Some(val)) => val == "true",
_ => false, // fail-closed: if DB error or key absent, system is NOT initialized
}
}
/// Mark the system as initialized after the first admin is created.
pub async fn mark_system_initialized(&self, admin_user_id: &str) -> Result<(), DomainError> {
self.settings_repo
.set(
"system_initialized",
"true",
"system",
false,
Some(admin_user_id),
)
.await
}
// ========================================================================
// Registration Control
// ========================================================================