KW Knowledge base

Vistralio Security Review

Date: 2026-04-14 Build reviewed: `0.1.0 (Beta) Build 20260414.3`

Mitch Wigham
Updated 24 June 2026 · 7 views

Vistralio Security Review

Date: 2026-04-14
Build reviewed: 0.1.0 (Beta) Build 20260414.3

Scope

This review was a targeted application hardening pass over the current Vistralio codebase and deployment layout. It covered the main web UI, API, camera configuration paths, activity logging, runtime path handling, and support/admin surfaces.

This document does not claim the system is free from unknown vulnerabilities or "zero-day" issues. It records what was checked, what was fixed, and what residual risks remain after this pass.

Areas reviewed

  • Authentication and JWT handling
  • Permission enforcement on admin and support endpoints
  • Camera configuration and secret exposure
  • Activity/audit logging of WebUI actions
  • Runtime storage path overrides and cleanup tooling
  • Media/download endpoints using browser-compatible token auth
  • Build integrity and backend syntax validation
  • Duplicate route surface and accidental endpoint drift
  • Dependency vulnerability audit and package remediation

Issues found and fixed

1. Camera credentials were exposed back to the frontend

Issue:

  • The camera API returned stored camera and ONVIF passwords in normal API responses.
  • Any user with camera access could receive raw saved credentials in the web UI payload.

Fix:

  • Camera and ONVIF passwords are no longer returned by the API.
  • The API now returns has_password flags instead.
  • The camera settings UI was updated to preserve existing saved passwords unless an operator explicitly enters a replacement.
  • The per-camera full settings endpoint now requires cameras.edit rather than plain cameras.view.

Files:

  • backend/app/api/cameras.py
  • frontend/src/pages/CameraConfig.jsx
  • frontend/src/pages/Admin.jsx

2. WebUI activity logging could store secrets

Issue:

  • The activity log was capturing full before/after payloads for camera and settings updates.
  • That could include passwords, tokens, and similar sensitive values.

Fix:

  • Added recursive redaction for sensitive keys before writing activity log entries.
  • Redaction now covers common secret-bearing keys such as passwords, tokens, authorization headers, JWTs, and similar fields.

Files:

  • backend/app/services/activity.py

3. JWT error messages leaked parsing details

Issue:

  • Invalid token responses included the underlying JWT parser error text.

Fix:

  • Token failures now return a generic Invalid token response instead of reflecting parse/signature details back to the client.

Files:

  • backend/app/core/security.py

4. Runtime storage overrides could point at dangerous system roots

Issue:

  • Runtime path overrides accepted arbitrary strings from settings.
  • A bad path value could cause metrics, cleanup, exports, clips, or recordings features to operate against unsafe system locations.

Fix:

  • Runtime paths are now normalized to absolute paths.
  • Runtime paths are now restricted to approved filesystem roots.
  • The live system was seeded with system.path.allowed_roots so the allowlist is explicit and operator-controlled.
  • Relative paths and paths outside approved roots are rejected and replaced by safe defaults.

Files:

  • backend/app/core/runtime_paths.py
  • backend/app/api/system.py

5. Media URLs used the primary login JWT in query strings

Issue:

  • Browser-facing media URLs used the main login token directly in ?token=... query parameters for images, recordings, and live WebSocket feeds.

Fix:

  • Added a separate short-lived media token flow via /api/auth/media-token.
  • The frontend now refreshes and uses media-only tokens for browser media URLs instead of the main session token.
  • Media endpoints now accept media tokens as well as regular authenticated access where appropriate.

Files:

  • backend/app/core/security.py
  • backend/app/api/auth.py
  • backend/app/api/streams.py
  • backend/app/api/recordings.py
  • frontend/src/api.js
  • frontend/src/App.jsx
  • frontend/src/pages/Cameras.jsx

6. Secrets were not encrypted at rest in the application database

Issue:

  • Sensitive values such as camera credentials, ONVIF credentials, SMTP password, and the applied license key could remain stored in plaintext in the database.

Fix:

  • Added transparent encrypted-at-rest handling for selected secret-bearing fields.
  • Existing plaintext values are migrated on startup into encrypted form.
  • Reads remain backward-compatible for any older values encountered before migration.
  • New installs now write a dedicated security.encryption_secret into the main config file, while existing installs fall back safely to the existing JWT secret if needed.

Files:

  • backend/app/core/secrets.py
  • backend/app/models/all.py
  • backend/app/api/cameras.py
  • backend/app/services/onvif_client.py
  • backend/app/services/smtp.py
  • backend/app/services/licensing.py
  • backend/app/api/settings.py
  • backend/app/main.py
  • scripts/install.sh

7. Duplicate export route definitions

Issue:

  • The recordings API contained duplicate GET /api/recordings/export definitions.
  • This was not an active exploit by itself, but it increased maintenance risk and made route behavior less trustworthy during future changes.

Fix:

  • Removed the duplicate route definitions and kept a single canonical export listing endpoint.

Files:

  • backend/app/api/recordings.py

8. Vulnerable backend dependency pins

Issue:

  • The pinned backend dependency set included multiple packages with known published vulnerabilities.

Fix:

  • Updated backend package pins to patched releases:
    • fastapi==0.135.3
    • starlette==0.49.1
    • cryptography==46.0.7
    • python-jose[cryptography]==3.5.0
    • python-multipart==0.0.26
    • jinja2==3.1.6

Files:

  • backend/requirements.txt

Checks performed

Static and code-level review

Reviewed:

  • backend/app/core/security.py
  • backend/app/api/cameras.py
  • backend/app/api/recordings.py
  • backend/app/api/system.py
  • backend/app/core/runtime_paths.py
  • backend/app/services/activity.py
  • relevant frontend camera/admin configuration flows

Focus areas:

  • credential exposure
  • route protection
  • token handling
  • path safety
  • secret leakage into logs

Verification commands

Backend syntax validation:

env PYTHONPYCACHEPREFIX=/tmp/pycache python3 -m py_compile \
  /opt/vistralio/backend/app/core/security.py \
  /opt/vistralio/backend/app/services/activity.py \
  /opt/vistralio/backend/app/core/runtime_paths.py \
  /opt/vistralio/backend/app/api/cameras.py \
  /opt/vistralio/backend/app/api/recordings.py \
  /opt/vistralio/backend/app/api/system.py \
  /opt/vistralio/backend/app/main.py \
  /opt/vistralio/backend/app/api/activity.py \
  /opt/vistralio/backend/app/api/settings.py \
  /opt/vistralio/backend/app/models/all.py \
  /opt/vistralio/backend/app/worker.py

Frontend production build:

cd /opt/vistralio/frontend && npm run build

Targeted runtime checks:

  • verified path sanitization behavior with the project virtualenv
  • verified camera serialization no longer returns raw password fields
  • verified activity-log redaction removes secret-bearing values
  • verified secret encryption/decryption helpers and short-lived media token decoding

Dependency audit checks:

cd /opt/vistralio/frontend && npm audit --omit=dev --json
/tmp/pip-audit-sentinel/bin/pip-audit -r /opt/vistralio/backend/requirements.txt

Results after remediation:

  • frontend production dependency audit: 0 vulnerabilities
  • backend requirements audit: No known vulnerabilities found

Residual risks and follow-up items

These items were identified but are not fully eliminated by this pass:

Query-string media authentication still exists in a reduced-risk form

The app still uses ?token=... on image/video/WebSocket media URLs where the browser cannot attach an Authorization header directly. This is common for browser media delivery, but it now uses short-lived media-only tokens rather than the main session JWT. Residual risks still include:

  • token exposure in browser history
  • token exposure in copied URLs
  • token exposure via intermediate logging if a proxy is misconfigured

Recommended follow-up:

  • move media delivery to cookie-backed or one-time signed URLs for the highest-security mode

Not every secret-bearing surface is fully redesigned yet

This pass added encrypted-at-rest handling for selected DB-backed application secrets, but not every bearer-style operational secret was redesigned. For example, some enrollment/rotation flows still rely on sensitive tokens that are intentionally shown to privileged administrators when needed.

Recommended follow-up:

  • extend the same pattern to any remaining bearer-style enrollment tokens if you want them unreadable at rest too
  • rotate existing operational secrets after a major security maintenance window

Admin path configuration remains an admin trust boundary

Runtime path overrides are now restricted to approved roots, but an administrator can still expand that allowlist deliberately. This is expected behavior for system administration.

Recommended follow-up:

  • add a dedicated UI for approved storage roots if you want this managed without direct settings-key edits

No formal external penetration test or OS hardening review was run in this pass

This review focused on application logic and configuration exposure. It did not include:

  • external penetration testing
  • SAST/DAST tooling integration
  • OS-level hardening review of nginx/systemd/MariaDB

Recommended follow-up:

  • add automated dependency scanning and scheduled external testing

Outcome summary

The main concrete security fixes from this pass are:

  • camera secrets are no longer returned to the frontend
  • activity logs no longer store secret values
  • token error responses no longer leak parser detail
  • unsafe runtime path overrides are constrained to approved roots
  • browser media URLs use short-lived media-only tokens instead of the main JWT
  • selected DB-backed secrets are now encrypted at rest
  • duplicate route surface was cleaned up
  • vulnerable backend package pins were upgraded

No evidence of an active credential leak was found in the reviewed paths after these changes, but this should be treated as an ongoing hardening process, not as a guarantee that all unknown vulnerabilities have been eliminated.

Still need help?

Log a support ticket and the team will pick it up from this page.