Skip to content

Web UI

dotvault includes an optional web-based dashboard built as a Preact single-page application. It provides browser-based authentication, status monitoring, and secret inspection.

Enabling the web UI

web:
  enabled: true
  listen: "127.0.0.1:9000"

Loopback only

The listen address must be a loopback address (127.0.0.1, [::1], or localhost). dotvault will refuse to start if a non-loopback address is configured. This is a hard security constraint that cannot be overridden.

Features

Authentication

The web UI supports all three auth methods:

  • OIDC — "Login with OIDC" button redirects to the identity provider
  • LDAP — username/password form with inline MFA handling (Duo push and TOTP)
  • Token — paste a Vault token to authenticate

Status dashboard

Shows at a glance:

  • Authentication state and Vault token TTL
  • Vault server address and KV mount configuration
  • Per-rule sync status (last synced, secret version)
  • Username and user prefix

Secret inspection

Browse and inspect secrets synced by dotvault. Secrets are hidden by default and require explicit reveal (?reveal=true). Nested Vault paths — such as a grouped enrolment written under databricks/prod — render in the sidebar as expandable folders that lazy-load their contents on first open, mirroring the grouped layout on the enrolment screen.

Manual sync

Trigger an immediate sync cycle from the dashboard without waiting for the next poll interval.

Copy Vault token

A clipboard icon in the header bar allows you to copy the current active Vault token to the clipboard. This lets you authenticate directly to the Vault web UI using your existing token, avoiding a repeated multi-factor authentication flow.

Customisable content

You can display markdown text on the login page and secret view page:

web:
  enabled: true
  listen: "127.0.0.1:9000"
  login_text: |
    Welcome to **dotvault**. Click Login to authenticate via your
    organisation's single sign-on.
  secret_view_text: |
    These secrets are synchronised from Vault. Contact IT support
    if you need additional credentials provisioned.

Security

  • CSRF protection — all mutating API endpoints require a CSRF token (obtained from GET /api/v1/csrf), with three deliberate exceptions: the peer-action endpoints POST /api/v1/remote/browse, POST /api/v1/remote/notify, and POST /api/v1/remote/clipboard (see below)
  • Content Security Policydefault-src 'self' prevents XSS via injected scripts
  • X-Content-Type-Optionsnosniff header on all responses
  • Loopback binding — enforced at startup; non-loopback addresses are rejected

API endpoints

The web UI communicates with the dotvault daemon via a REST API:

Method Path Description
GET /api/v1/status Server status, auth state, token TTL, sync state
GET /api/v1/rules Configured sync rules
GET /api/v1/token Current Vault token (authenticated sessions only)
GET /api/v1/secrets/{path} List or reveal a secret
POST /api/v1/sync Trigger immediate sync (CSRF-protected)
POST /api/v1/remote/browse Open a form-posted url in this host's default browser (not CSRF-protected)
POST /api/v1/remote/notify Raise a form-posted desktop notification on this host (not CSRF-protected)
POST /api/v1/remote/clipboard Put form-posted text on this host's clipboard (not CSRF-protected)
GET /api/v1/csrf Obtain a one-time CSRF token

POST /api/v1/remote/browse is the outbound counterpart of GET /api/v1/token: over the same SSH-forwarded Unix socket that lets a headless peer borrow the workstation's token, it lets the peer hand a URL back so browser-driven flows open where a browser actually exists — see dotvault browse. It accepts a form POST (url=https://..., body only — the query string is ignored) and only http/https URLs with a host and no embedded user:pass@ credentials; file:// and custom protocol schemes are rejected before anything reaches the OS URL opener, and only one browser open runs at a time (concurrent requests get a 503). It is deliberately exempt from the CSRF handshake: its consumer is a bare curl/dotvault browse POST with no practical way to run the issue-then-spend token dance, and it reads no state and returns nothing sensitive. Cross-site browser traffic is rejected by an Origin check instead — browsers always attach an Origin header to cross-origin POSTs, and only the daemon's own origin (a loopback hostname on the daemon's own listener port — a page served by any other loopback server does not qualify) is accepted; curl and the CLI send no Origin and pass.

POST /api/v1/remote/notify is the same idea for desktop notifications — see dotvault notify. It accepts a form POST with level (one of info, warning, error, attention), title, an optional body, and an optional action_url, and raises a native notification (Windows toast / macOS Notification Center / Linux D-Bus). action_url (http/https, the same allowlist as browse) makes the notification open that URL when clicked on Windows, and is appended to the body on macOS/Linux where a one-shot notification cannot be made clickable. It shares the browse endpoint's security posture exactly: no CSRF, the same Origin check, body-only fields, and a single-flight bounded delivery. Its input-validation control restricts level to the known set, validates action_url against the http/https allowlist, and sanitizes title/body — stripping control characters and neutralizing the metacharacters that would otherwise break out of the Windows toast backends (an XML CDATA section and a PowerShell here-string), so a crafted notification cannot inject toast XML or execute a PowerShell subexpression on the delivering host. Log lines record the level and field lengths only, never the title/body text or the action_url (arbitrary, potentially capability-bearing content that may name secret systems).

POST /api/v1/remote/clipboard is the third peer action — see dotvault clipboard. It accepts a form POST with a single text field (body only — a URL is the last place a secret should travel) and places the value on this host's clipboard, so a headless peer can stage a one-time token or device code right where the user's paste is after remote/browse opened the page that asks for it. It shares the browse/notify security posture exactly: no CSRF, the same Origin check, and a single-flight bounded write. Validation rejects input no clipboard can carry faithfully (interior NULs, invalid UTF-8) or that signals a caller bug (empty, over 64 KiB); the text is otherwise written verbatim — clipboard content is data, never interpolated into an evaluated context, and the typical payload is a credential that must arrive byte-for-byte intact. This endpoint carries the most secret-bearing payload of the three, so log lines record the text's length only — never the content — and writer errors are additionally scrubbed of the exact text before logging or being returned (best-effort defense in depth; the writers never embed their input in errors).

Auth endpoints:

Method Path Description
GET /auth/oidc/start Redirect to Vault OIDC auth URL
GET /auth/oidc/callback Handle OIDC callback
POST /auth/ldap/login Start async LDAP login (CSRF-protected)
GET /auth/ldap/status Poll login status
POST /auth/ldap/totp Submit TOTP passcode (CSRF-protected)
POST /auth/token/login Validate and set token (CSRF-protected)