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
+10 -1
View File
@@ -171,7 +171,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
if config.features.enable_auth {
use interfaces::api::handlers::auth_handler::{
auth_routes, login_route, refresh_route, register_route,
auth_routes, login_route, refresh_route, register_route, setup_route,
};
use oxicloud::interfaces::api::handlers::app_password_handler;
use oxicloud::interfaces::api::handlers::device_auth_handler;
@@ -229,6 +229,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with_state(app_state.clone());
// Remaining auth routes (status, OIDC, protected /me, /logout, etc.)
let auth_router = auth_routes().with_state(app_state.clone());
// One-time setup route — public, rate-limited like register
let setup_router = setup_route()
.layer(axum::middleware::from_fn_with_state(
register_limiter.clone(),
rate_limit_register,
))
.with_state(app_state.clone());
// Device Authorization Grant (RFC 8628)
// Public endpoints: /api/auth/device/authorize + /api/auth/device/token
@@ -281,6 +288,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.nest("/api/auth", auth_refresh)
// Other auth endpoints (status, OIDC, protected /me, /logout)
.nest("/api/auth", auth_router)
// One-time setup endpoint — public, rate-limited
.nest("/api", setup_router)
// Device Auth Grant public endpoints (authorize + token polling)
.nest("/api/auth/device", device_public)
// Device Auth Grant protected endpoints (verify + device management)