Skip to content

Security and Secrets

SyncTV has three high-impact secrets. They have different purposes and should not be reused.

SettingPurposeMust remain stableImpact if lost or leaked
jwt.secretSigns access, refresh, and guest tokensCan be rotated, but affects sessionsA leaked value may allow forged tokens
security.opaque_server_setup_secretServer setup secret for OPAQUE password authYesChanging it can make OPAQUE password records unverifiable
security.credential_encryption_keyEncrypts provider credentialsYesLosing it can make encrypted provider credentials unreadable

Use environment variables or secret files in production. Do not commit these values.

Purpose: signs login tokens.

Requirements:

  • At least 32 characters.
  • High entropy.
  • Not a placeholder, project name, domain, date, or common word.

Generate:

Terminal window
openssl rand -base64 32

Recommended YAML:

jwt:
secret_file: "/run/secrets/jwt_secret"

Environment variables:

Terminal window
SYNCTV_JWT_SECRET=...
SYNCTV_JWT_SECRET_FILE=/run/secrets/jwt_secret

Rotating it can invalidate existing access and refresh tokens. Rotate immediately if it may have leaked.

FieldDefaultPurpose
jwt.access_token_duration_hours1Access token lifetime
jwt.refresh_token_duration_days30Refresh token lifetime
jwt.guest_token_duration_hours4Guest token lifetime
jwt.clock_skew_leeway_secs60Clock skew tolerance

For internet-facing deployments, keep access tokens short and rely on refresh rotation. Use NTP instead of increasing clock skew to hide server time drift.

Purpose: encrypts sensitive provider credentials such as tokens, API keys, and provider secrets.

Format:

  • 64 hexadecimal characters.
  • Equivalent to a 32-byte AES-256-GCM key.

Generate:

Terminal window
openssl rand -hex 32

Recommended YAML:

security:
credential_encryption_key_file: "/run/secrets/credential_encryption_key"

Environment variables:

Terminal window
SYNCTV_SECURITY_CREDENTIAL_ENCRYPTION_KEY=...
SYNCTV_SECURITY_CREDENTIAL_ENCRYPTION_KEY_FILE=/run/secrets/credential_encryption_key

Back it up securely before storing encrypted provider credentials. Do not rotate it casually without a migration plan.

Purpose: stable server secret used by OPAQUE password authentication.

Generate:

Terminal window
openssl rand -base64 48

Recommended YAML:

security:
opaque_server_setup_secret_file: "/run/secrets/opaque_server_setup_secret"

Environment variables:

Terminal window
SYNCTV_SECURITY_OPAQUE_SERVER_SETUP_SECRET=...
SYNCTV_SECURITY_OPAQUE_SERVER_SETUP_SECRET_FILE=/run/secrets/opaque_server_setup_secret

Important constraints:

  • Do not reuse jwt.secret.
  • Do not generate a new value on every container start.
  • Keep the value stable across Helm upgrades and redeployments.
  • Changing it incorrectly can break existing password login records.

Default:

password_complexity:
min_length: 8
require_uppercase: true
require_lowercase: true
require_digit: true
require_special: false
max_repeated_chars: 3
zxcvbn_enabled: false
zxcvbn_min_score: 3

This applies to user account passwords, not room passwords.

Production policy usually benefits more from longer passwords and rate limits than from forcing many special characters on clients with poor text input.

max_repeated_chars=0 disables the repeated-character check. zxcvbn_enabled=false is the default. When enabled, SyncTV uses the zxcvbn entropy estimator and rejects passwords with a score below zxcvbn_min_score (0-4; 3 is the recommended minimum).

CORS controls which browser origins may call the API.

server:
cors_allowed_origins:
- "https://app.example.com"

Origins must not include paths, query strings, or fragments.

Trusted proxies control whether SyncTV trusts forwarding headers such as X-Forwarded-For.

server:
trusted_proxies:
- "10.0.0.0/8"

Only add proxy IPs or CIDRs that you control. If this list is empty, SyncTV uses the socket peer address and does not trust forwarded client IP headers.

SyncTV uses the global security.ssrf policy for server-side outbound HTTP, proxy, provider, OAuth2, remote-provider, and livestream pull requests. SSRF protection is disabled by default so self-hosted deployments can bind private media sources without extra setup. Public deployments should enable SSRF protection and configure the narrowest allowlist that covers trusted internal media endpoints.

security:
ssrf:
enabled: true
allow_private_network_targets: false
allowed_hosts:
- "alist.internal"
allowed_ip_ranges:
- "192.168.1.10/32"
- "10.0.8.0/24"

Field behavior:

  • enabled=false: default; disable SSRF protection.
  • enabled=true: enable SSRF protection and block local, private, and metadata targets.
  • allow_private_network_targets=false: keep local, private, and metadata targets blocked when SSRF protection is enabled.
  • allowed_hosts: allow known internal services by hostname.
  • allowed_ip_ranges: allow known internal services or subnets by IP/CIDR.
  • allow_private_network_targets=true: allow all private/non-global targets; use only in trusted private deployments.

Environment variables:

Terminal window
SYNCTV_SECURITY_SSRF_ENABLED=true
SYNCTV_SECURITY_SSRF_ALLOW_PRIVATE_NETWORK_TARGETS=false
SYNCTV_SECURITY_SSRF_ALLOWED_HOSTS=nas.example.internal,alist.internal
SYNCTV_SECURITY_SSRF_ALLOWED_IP_RANGES=192.168.1.10/32,10.0.8.0/24

For public deployments, enable SSRF protection before exposing provider endpoints to untrusted users. Prefer minimal allowlists.

WebAuthn/passkey configuration is documented in WebAuthn and Passkeys.

User-level two-factor authentication is a preference, not a global YAML switch. Before enabling 2FA, a user must have at least two usable local verification methods:

  • Password.
  • WebAuthn/passkey.
  • Verified email.

OAuth2 does not participate in local 2FA, but a user with 2FA enabled may still log in through OAuth2. OAuth2 sessions are treated as an accepted authentication context for token refresh.

When 2FA is enabled, local one-factor tokens should not be accepted as enough for refresh or security-sensitive preference changes.