Security Architecture
Deployment posture
Riviamigo is not approved for direct Internet exposure. Production Compose binds the application behind a host firewall and authenticated gateway, and every shared deployment must place an authenticated tunnel or identity-aware reverse proxy in front of it. The outer gateway owns public TLS, certificate renewal, identity enforcement, and Internet-facing rate limits; Riviamigo login remains required behind it.
See the secure deployment runbook for the required gateway contract and verification steps.
The current internal audit evidence and release requirements are tracked in
docs/security-audit.md.
Sensitive vulnerability reports must use the repository's private GitHub Security Advisory flow. Do not publish credentials, exploit details, production data, live telemetry, or precise vehicle locations in public issues.
Authentication
- JWT (RS256) with 15-minute access tokens
- 30-day HttpOnly refresh tokens, rotated on use
- API keys are SHA256-hashed, read-only, and bound to exactly one vehicle; keys never authorize dashboard, account, administrative, or vehicle-setting writes
- Argon2 password hashing
- Vehicle membership roles are capability boundaries:
vieweris telemetry and history read-only,managermay run operational changes such as schedules and backfills, andowneralone manages credentials and membership. - Protected-route bootstrap uses
POST /v1/auth/bootstrap, which returns fresh tokens when a valid refresh cookie exists and204 No Contentwhen no resumable session exists, so first-load logged-out state does not depend on a visible refresh 401. - The web app attempts one refresh on protected 401s, then emits a single auth-expired flow: toast, session clear, redirect to
/login, and resume to the original in-app route after successful sign-in.
Transport Security
- Production nginx is an HTTP origin on port 8080, loopback-bound by default,
not a public TLS endpoint. Non-loopback binding requires the explicit
ALLOW_PUBLIC_ORIGIN_BIND=trueopt-in. - Public HTTPS and HSTS are enforced by the authenticated outer gateway
Securecookie flag enforced;COOKIE_INSECUREis local-development-only. The narrowALLOW_INSECURE_LAN_HTTP_AUTH=trueproduction exception accepts only documented private literal-IP HTTP origins and emits a startup warning.
Rate Limiting
- Riviamigo applies class-specific auth, read, write, and heavy-read limits
- The authenticated outer gateway must apply its own client-facing limits
- The internal origin does not trust arbitrary forwarded client-IP headers
Headers
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: no-referrerContent-Security-Policy: default-src 'self'; ...
Database
- PostgreSQL accessible only on internal Docker network (not exposed to host)
- Parameterized queries via sqlx (compile-time checked)
- Telemetry column names validated against allowlist before interpolation
- Rivian vehicle credentials are encrypted with age before durable storage
Secret Storage
- Durable Rivian credential bundles are encrypted before storage in
riviamigo.vehicle_credentials - Short-lived connect / OTP staging data should stay encrypted at rest in Redis and Redis should remain internal-only
- Production may generate
AGE_ENCRYPTION_KEY,JWT_SECRET, andJWT_PUBLIC_KEYon first start and persist them in PostgreSQL; externally managed overrides must supply all three together. The database-backed option is an explicitly accepted P2 shared-fate risk, mitigated by tested database recovery; a secret manager is the optional separate-custody recovery path.
Audit Logging
- Security events (first-owner claim, login success/failure, password changes,
account-invitation operations, API-key create/revoke/rotate, and user
administration) are recorded in
riviamigo.security_events. - Each current event has an event type, actor when known, stable target, UUID request correlation when supplied, success/failure outcome, and redacted enum-like metadata. The service must not store credentials, tokens, email addresses, locations, or raw telemetry in this audit metadata.
- There is currently no automatic retention/deletion job for security events. Retention is therefore bounded by the operator's database retention and backup policy, not by an application purge interval.
- Structured
[riviamigo][LEVEL]key-value logs are written to stdout/stderr; Docker supplies the outer timestamp. The production wrapper normalizes Nginx error lines into the same shape.
Security regression controls
pnpm security:routesverifies that every API route module is mounted through the intended protected router composition. The authentication module must have separate public, metadata, and protected mounts.- Restore capability tokens and agent keys are compared without early-exit string equality and reject oversized or malformed authentication headers.
- Dynamic telemetry and metric selectors reject control characters and bounded oversized input before their allowlists are used to build SQL identifiers.
Dependencies
- Weekly automated dependency audits via Dependabot
cargo audit --deny warningsin CIpnpm audit --prod --audit-level=highin CI- Semgrep SAST is blocking on trusted branches and same-repository pull requests; fork pull requests use a separate secret-free blocking scan.
- Fixable critical and high Trivy findings are blocking after the unified production image builds; unfixed base-image findings remain visible for review and base-digest refreshes.
- Workflow actions are pinned to reviewed commit SHAs.
Release Images
- Standard Compose pulls one public unified image from GitHub Container Registry; source builds use the explicit build overlay only.
- Stable images use immutable Calendar Version tags and provenance attestations;
latestis a moving convenience tag, not a reproducible deployment identifier. - Versioned container images are published only by intentional release workflows from validated
maintags or the currentdevpre-release candidate. Pushes tomainanddevmay publish unversioned, commit-addressed build candidates; these are cache/release inputs, not releases. Stable and pre-release image tags and digests must be treated as release artifacts. - See the release images runbook for package visibility, tag protection, and recovery requirements.
Production Checklist
-
COOKIE_INSECUREis NOT set (except local development) -
ALLOW_INSECURE_LAN_HTTP_AUTHremainsfalse, or the documented trusted-LAN exception and host firewall controls are in place -
POSTGRES_PASSWORDchanged from default -
REDIS_PASSWORDis strong and Redis is not host-published - Generated application keys are protected by database backups, or all three explicit key overrides are stored safely
-
ALLOWED_ORIGINSset to exact frontend domain(s) - An authenticated tunnel or identity-aware reverse proxy terminates public HTTPS
- Host firewall rules restrict direct access to port 8080
- Redis is reachable only on a private/internal network
- Firewall blocks API, PostgreSQL, Redis, and origin ports from external access
-
RIVIAMIGO_IMAGEuses the digest-qualifiedimages.lockreference when exact repeatability matters, orIMAGE_TAGis pinned to a Calendar Version for version-level stability