Security
- Enforce guest ownership and server assignment across Proxmox endpoints
(VM/LXC actions, firewall, backups, tasks, bulk operations, deploy,
LXC/ISO creation, golden template) and WebSocket channels
- Keep the panel's PVE root ticket server-side for consoles; single-use
WebSocket tickets instead of the JWT in URLs; re-check access on open
WebSocket bridges
- Validate identifiers that reach Proxmox API paths and node shell
commands; reject backup archives / foreign volumes in create requests
- Close privilege escalation in user management, TOTP replay, reset-link
poisoning, X-Forwarded-For spoofing, stored XSS (group icons, SVG logos)
- SSH host key pinning (TOFU), encrypted 2FA seeds and notification
secrets, password changes end other sessions, redact secrets in logs
- Quotas count in-flight creations; 2FA failures count toward lockout
Fixes
- Consoles on non-default API ports, large App Store/script files,
interrupted tasks after restart, impersonation lost on token refresh,
HTTPExceptions swallowed into 500s, WebSocket client reconnects
Performance and process
- Fewer DB writes and queries on hot paths, lazy-loaded locales and
heavy pages, off-loop blocking work, indexes (migrations 44-46)
- Deploy waits for backend tests and frontend build; frontend checks
workflow; API bound to loopback without the SSL profile
App Store install/update pipeline reliability fixes:
- explicit nameserver (APPSTORE_NAMESERVER) on App Store LXCs and the golden
template build CT, instead of inheriting a node resolver that may be
unreachable from the container network (e.g. Tailscale-only DNS)
- golden template storage/resolution moved from one global setting to
per-server_id (migration 37: app_operations.server_id) — building it on one
Proxmox server no longer overwrites the template configured for another
- golden template automated build fixed: vzdump export step no longer fails
on "unable to parse directory volume name 'vztmpl/probe'"
- bind-mount host directories are pre-created with permissive ownership
before `docker compose up`, fixing Permission denied for non-root
containers (e.g. MariaDB) writing to Docker-auto-created root-owned dirs
- delete/teardown now waits for Proxmox to actually confirm the destroy task
finished (was silently proceeding after a short timeout, letting a new
install reuse the VMID while the node was still wiping the old disk)
- pct create retries a few times on transient "got lock" config errors
- failed pipeline steps now report the tail of command output (4000 chars)
instead of the head, so the real error isn't buried under pull/progress noise
- install error box in the UI wraps long text and scrolls instead of
stretching the dialog
Also carries forward in-progress script catalog / instances / RBAC changes
that were already uncommitted in the working tree before this session.
Bump to v1.9.1, update WIKI.md/docs/CHANGELOG accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A1: иконки Umbrel тянутся пофайлово с GitHub Pages CDN в пуле из 16 потоков
(<app-id>/icon.svg) вместо тяжёлого tarball галереи со скриншотами. Синк
Umbrel: ~7.5 мин → ~13-17с, 379/379 иконок. Настройка UMBREL_APPSTORE_GALLERY_CDN
заменяет UMBREL_APPSTORE_GALLERY_REPO. 404/сетевой сбой отдельного запроса
просто пропускает иконку, не роняя синк.
A2: на свежем деплое каталог пуст, а IntervalTrigger впервые сработает лишь
через сутки. Теперь при пустом каталоге планировщик ставит разовый синк
немедленно (trigger=date) — каталог наполняется на старте без ручной кнопки.
Проверено на стенде: стартовый синк наполнил каталог 644 (runtipi 265 +
umbrel 379) за 16.5с; эндпоинт логотипа отдаёт 200 image/svg+xml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App Store:
- поле кастомного порта в мастере установки (переопределяет порт каталога)
- валидация диапазона порта 1..65535 (Pydantic Field + EngineError)
- APP_PORT в окружении синхронизируется с выбранным портом
- при удалении приложения инстанс сразу помечается deleted в кэше
vm_instances (soft_delete_vm_instance), не дожидаясь фонового воркера
Также включены правки IPAM (привязка сетей к рабочей области, UI
WorkspaceSelect) и синхронизации каталога App Store.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an email-based "forgot password" flow:
- PasswordResetToken model (stores only a SHA-256 hash, single-use, TTL)
and migration 27 for the password_reset_tokens table.
- POST /api/auth/forgot-password — generic response (no account
enumeration), 60s throttle, emails a one-time reset link via the
existing EmailChannel as a background task.
- GET /api/auth/reset-password/validate — token validity check for the UI.
- POST /api/auth/reset-password — validates the token, enforces the
password policy, sets the new password, clears lock state, invalidates
outstanding tokens and terminates all sessions.
- Configurable link lifetime via PASSWORD_RESET_TOKEN_EXPIRE_MINUTES (30m).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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