White Paper self-hostedcomputer-visionwebrtchome-securitysurveillanceonvif

Argus NVR: Architecture, Detection Pipeline, and Attack Surface

A mechanism-level look at how Argus NVR proxies camera streams, splits recording from detection, and constrains what's exposed to the network.

September 29, 2026

Argus is a FastAPI backend, a React SPA, and three external binaries (go2rtc, FFmpeg, and optionally rclone) wired together into a single Windows installer or Linux tarball. The design choice that shapes most of its security properties is where those binaries sit relative to the network.

Stream topology

Every camera connection is RTSP, terminated once by go2rtc, which fans the stream out internally to two independent consumers: the FFmpeg recorder and the motion detector. Neither consumer re-requests the stream from the camera, so adding recording or detection doesn’t multiply the load on camera hardware.

go2rtc’s own ports (1984 for its API, 8554 for RTSP, 8555 for WebRTC/RTP) bind to 127.0.0.1 only. FastAPI reverse-proxies go2rtc’s HTTP and WebSocket traffic through port 8080, the same port serving the REST API and the built React frontend. This means the browser’s WebRTC negotiation (trickle ICE over WebSocket) also goes through 8080; the only exception is the UDP media itself, which needs 8555 opened separately for LAN or remote clients. The Windows installer’s service-install.ps1 opens these firewall rules automatically; on Linux it’s manual.

Recording vs. detection

Recording and detection are deliberately decoupled processes reading the same go2rtc feed:

  • Recorder: FFmpeg in stream-copy mode, no transcoding, writing fragmented MP4 in SEGMENT_SECONDS-length chunks (default 120s). Fragmentation means a crash or power loss loses at most the current segment, not the whole recording session. Record mode is configurable per camera: continuous, motion-only, or motion-events.
  • Detector: frame-diff plus a scipy blob filter, sampled at DETECT_FPS (default 2). Privacy masks (normalized {x,y,w,h} rectangles, JSON-configured per camera) are applied before detection runs, not after, so masked regions never reach the motion or face pipeline. Arm schedules (JSON {days,start,end} blocks) gate when the detector runs at all. Hikvision cameras can bypass frame-diff and subscribe to the camera’s own ISAPI VMD alert stream instead, which is lower-latency than software motion detection.

Face recognition sits downstream of the detector, using InsightFace’s buffalo_sc model (512-d embeddings, ONNX runtime, CPU-only — no GPU or external inference dependency). Embeddings are persisted as raw float32 byte arrays, not pickled Python objects, which rules out pickle deserialization as an attack vector if the embedding store is ever reachable by an attacker.

Identity and secrets

ARGUS_SECRET_KEY does double duty: it signs JWTs and serves as the Fernet key encrypting camera RTSP credentials at rest. RBAC has three tiers (admin/operator/viewer), enforced at the route level; user management, audit log access, and recording purges require admin. Every authentication event and mutating action (user create/update/delete, password reset, camera change) is written to an audit log with actor, IP, and timestamp, with a paginated viewer and purge capability gated to admin.

First login after install uses a fixed default password (Screenshot123!); the frontend blocks all navigation until it’s changed, and any admin-created user is flagged must_change_password=1 on creation, forcing the same flow.

Outbound notifications (Telegram, ntfy, SMTP, webhook, FCM, Web Push) are configured via the Settings page and stored in the database, not .env — so credentials for those channels aren’t sitting in a plaintext config file on disk. Webhook payloads are signed with HMAC-SHA256 in an X-Argus-Signature header for receiver-side verification. The settings API itself whitelists recognized keys, which blocks arbitrary key injection through that endpoint.

What Argus doesn’t do for you