Critical security and robustness fixes from a code audit:
- WS terminal/VNC now require a JWT (?token=), validate the session,
vms.console permission and VM ownership before bridging to Proxmox
(previously the LXC terminal gave unauthenticated root shell access).
Frontend ConsolePage passes the token in both WS URLs.
- Fix SSH fallback for `pvecm delnode`: SSHClient now supports password
auth (it previously crashed with TypeError); ProxmoxClient stores the
password for SSH fallbacks and the host port suffix is stripped.
- Add production startup warnings for default ADMIN_PASSWORD and wildcard
CORS_ORIGINS.
- dashboard: use tz-aware utcnow() instead of naive datetime.utcnow().
- crypto: narrow decrypt_value except to InvalidToken and log failures.
- ssh_client: guard the shared connection cache with a lock; make
unknown-host-key rejection opt-in via SSH_REJECT_UNKNOWN_HOSTS.
- logging: send loguru console output to stderr instead of stdout.
- Sync version fallback to 1.5.1; remove one-off refactor scripts.
- Add CODE_REVIEW.md audit report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Encrypt Proxmox server passwords and VM cloud-init passwords in the database:
- Add backend/app/crypto.py: Fernet-based encrypt/decrypt helpers + EncryptedString
SQLAlchemy TypeDecorator (transparent to all existing code, no API changes)
- Add FERNET_KEY config field (env var); without it values stay plaintext for
backward compatibility, with a logged warning
- Update models.py: ProxmoxServer.password and VMInstance.cloud_init_password
now use EncryptedString(255) instead of String(255)
- Add migration 14 in migrations/migrations.py: idempotent re-encryption of
existing plaintext rows (skipped when FERNET_KEY is not set)
CSRF analysis:
- /api/auth/change-password is protected by JWT Bearer token (Authorization header)
- Browsers cannot auto-include Authorization headers cross-site → CSRF is not
applicable here; added explanatory comment
Improve migration loading:
- Add migrations/__init__.py to make it a proper Python package
- Replace fragile importlib dynamic loading in main.py with a standard import
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R
Critical fixes:
- init_admin.py: replace hardcoded admin123 password with ADMIN_PASSWORD env var
- proxmox_client.py, models.py: change verify_ssl default from False to True
- api/proxmox.py: use server.verify_ssl instead of hardcoded verify=False in websocket handler
- ssh_client.py: replace AutoAddPolicy (MITM-vulnerable) with WarningPolicy + load_system_host_keys
Security hardening:
- main.py: add security headers middleware (CSP, X-Frame-Options, HSTS, X-XSS-Protection, Referrer-Policy)
- main.py: fix CORS — allow_credentials=True is incompatible with allow_origins=["*"]; read origins from CORS_ORIGINS env var
- auth.py, logging_middleware.py: validate X-Forwarded-For/X-Real-IP to prevent IP spoofing
Performance:
- monitoring_worker.py: wire up cleanup_expired_connections() as hourly scheduled job (was defined but never called)
Code quality:
- logging_service.py: move `import json` from inside function to module level
- ssh_client.py, proxmox_client.py, api/proxmox.py, rbac/migration.py, task_queue_service.py: replace all bare `except:` with `except Exception:`
- .env.example, config.py: document ADMIN_PASSWORD and CORS_ORIGINS settings
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R