This document explains the current security model of PPV Stream, what has already been hardened in the codebase, and what operators should do when deploying the application to production.
It is written for:
- developers maintaining the repository
- DevOps or platform engineers deploying the app
- reviewers performing security or compliance checks
The application protects:
- user accounts
- admin accounts
- creator content access
- wallet balances and wallet transactions
- fiat invoice state
- affiliate commission integrity
- payment webhooks
- streaming session authorization
The main goals are:
- prevent unauthorized admin access
- prevent forged purchases and payment state changes
- prevent unauthorized video playback
- prevent browser-based cross-site request abuse
- reduce sensitive data exposure
- keep webhook and replay handling idempotent
- Users authenticate through
/auth/login. - Passwords are stored as Argon2 hashes.
- Successful login creates a signed session cookie named
ppv_session.
- Admins authenticate through
/admin/login. - The session record stores whether the actor is admin.
- Sensitive
/admin/*routes now require an active admin session in server-side checks.
The session cookie is:
HttpOnlySameSite=LaxSecureautomatically whenBASE_URLuseshttps://Securecan also be forced withSESSION_COOKIE_SECURE=true
Relevant code:
Sensitive admin endpoints must never rely only on frontend gating.
Server-side enforcement now exists for:
- admin data views
- payment monitoring
- payment settings
- SMTP settings
- wallet admin actions
- affiliate admin commission listing
Relevant files:
Operational requirement:
- Do not expose any additional admin routes without adding the same admin-session validation pattern.
State-changing browser requests that carry the ppv_session cookie are checked for same-origin behavior using Origin and Referer.
This helps block:
- classic CSRF attempts
- cross-site POST abuse from external pages
The middleware currently applies to cookie-authenticated mutating requests and excludes /api/pay/* webhook-related paths from the browser-origin rule.
Relevant file:
Basic in-memory rate limiting is applied to sensitive auth-style endpoints such as:
/auth/login/admin/login/auth/register/auth/forgot/setup_admin/api/change_password/admin/change_password
This is a first-line defense against brute-force and abuse.
Important limitation:
- It is process-local and in-memory, so it is not a distributed rate limiter.
For multi-instance deployments, use an external layer such as:
- Cloudflare
- Nginx rate limit
- load balancer rate limiting
- Redis-backed application rate limiting
Rate limiting and request fingerprinting can use:
X-Forwarded-ForX-Real-IP
But these headers are only trusted when:
TRUST_PROXY_HEADERS=trueDefault behavior:
TRUST_PROXY_HEADERS=false
This is intentional, because trusting forwarded IP headers when the app is directly exposed can allow spoofing.
Enable TRUST_PROXY_HEADERS=true only when:
- the app sits behind a trusted reverse proxy
- the proxy strips or rewrites client forwarding headers correctly
Relevant files:
The backend no longer trusts:
- client-supplied
user_id - client-supplied
amount_cents
Instead:
- buyer identity is derived from the active session
- price is loaded from the server-side video record
- the owner cannot buy their own video
Relevant file:
Webhook replay is expected behavior from some providers.
The backend now short-circuits repeated successful webhook callbacks when an invoice is already marked paid, reducing duplicate side effects such as:
- repeated access grant attempts
- repeated disbursement attempts
- repeated affiliate processing
x402 confirm now recognizes already-paid invoices and returns a replay-safe response instead of re-running the normal happy path.
Relevant files:
The /setup_admin endpoint is intentionally restricted.
Current hardening:
- if
ADMIN_BOOTSTRAP_TOKENis missing, bootstrap is disabled - if an admin account already exists, bootstrap is refused
- the route requires the correct bootstrap token in the query
Production guidance:
- set a strong
ADMIN_BOOTSTRAP_TOKEN - use bootstrap once
- create the intended admin account
- remove or rotate bootstrap secrets afterward
Relevant file:
Public API responses should not expose unnecessary sensitive information.
Recent reductions include removing creator payout and contact fields from the public video listing response:
- wallet address
- bank account
User lookup is also restricted:
- login is required
- email is no longer returned in lookup responses
Relevant file:
Playback is protected through:
- authenticated request to create a playback session
- access checks using ownership, purchase, allowlist, or admin role
- per-user playback session records
- HLS session ownership validation before serving segments
- path safety checks for HLS file access
Relevant file:
Operational recommendation:
- keep
HLS_ROOTon private server storage - do not expose the raw session directory directly through a public web server bypassing the Rust app
The app now adds several response headers globally:
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()Cross-Origin-Opener-Policy: same-originCross-Origin-Resource-Policy: same-origin
Default Cache-Control: no-store is also added to dynamic responses unless another cache policy is already present.
Static asset paths are excluded from the blanket no-store default to avoid unnecessary frontend cache degradation.
Relevant file:
Use at minimum:
BASE_URL=https://stream.example.com
SESSION_COOKIE_SECURE=true
TRUST_PROXY_HEADERS=true
HMAC_SECRET=replace-with-a-long-random-secret
ADMIN_BOOTSTRAP_TOKEN=replace-with-a-long-random-token
RUST_LOG=infoIf the app is directly exposed to the internet without a reverse proxy, use:
TRUST_PROXY_HEADERS=falseUse Nginx, Caddy, Cloudflare, or another trusted edge layer to add:
- TLS termination
- request size limits
- additional rate limiting
- IP reputation / WAF rules
- bot filtering
- access logging
Suggested edge protections:
- rate limit
/auth/loginand/admin/login - rate limit
/api/upload - block obvious scanners on
/setup_admin - enforce HTTPS redirect
- log 4xx and 5xx responses
The codebase is safer than before, but still has room for improvement.
Recommended next steps:
- move from in-memory rate limiting to a shared store or edge-based limiter
- expand audit logging further to additional security-sensitive business flows if needed
- review legacy frontend routes and remove unused endpoints
- add integration tests for:
- admin auth enforcement
- forged fiat purchase attempts
- CSRF rejection
- webhook replay handling
- review upload and FFmpeg resource abuse limits under load
- review outbound webhook and payment provider timeout/retry strategies
When changing security-sensitive code, review at least these files:
src/sessions.rssrc/middleware.rssrc/handlers/admin.rssrc/handlers/payment_plugins.rssrc/handlers/pay.rssrc/handlers/setup.rssrc/handlers/video.rssrc/handlers/stream.rs
If a feature touches payments, auth, storage, upload, or admin controls, it should be reviewed as a security-sensitive change even if the UI change looks small.