Tournevia

Security

Report a vulnerability privately and harden an installation: access, sessions, secrets and stored data.

Report a vulnerability

Report suspected vulnerabilities privately through GitHub: open the Tournevia repository's Security tab and choose Report a vulnerability (GitHub private vulnerability reporting). Do not open a public issue, pull request or discussion about a suspected vulnerability.

Include the affected commit or release kit, steps to reproduce using synthetic data, and the impact you observed. Reports are handled privately. There is no guaranteed response time.

Supported versions

Tournevia Community is pre-release software and has no supported release yet. Security fixes are made on the main branch only; release kits built from earlier commits are not patched.

What not to share

  • Never include customer records, credentials, session cookies or details of a deployment you do not operate.
  • Test only synthetic data, or deployments you are explicitly authorized to test.
  • Allow time for a fix before publishing details.

Deployment checklist

  1. Team access uses shared mode over HTTPS, with a certificate the users' browsers trust. Never expose local mode publicly.
  2. Only the HTTPS proxy is published; behind an existing HTTPS proxy, nothing is published and only the edge web server joins that proxy's network. The database, API and router stay private; the routing overlay keeps the router on an internal network. Plain HTTP to a remote router would carry delivery coordinates unencrypted.
  3. The private directory (workspace.env, workspace.json, private-setup.url) and backups are readable by operators only, and never committed.
  4. The first-use link is used once by the operator and never shared with dispatchers.
  5. MONITOR_TOKEN is set only if a monitor scrapes /api/v1/metrics.
  6. TRUST_PROXY_CLIENT_HEADER stays off behind a proxy other than the bundled one, unless that proxy overwrites X-VRP-Client. Behind Coolify or another existing proxy, keep the bundled edge in front of the API: it takes the visitor's address from the proxy's X-Forwarded-For, and other containers on that shared network could set that header.
  7. Backups are encrypted before leaving the host and a restore has been exercised. See Backup and restore.
  8. GEOCODER_URL is empty or points at the installation's own geocoder; the geocoder overlay keeps it on an internal network, with no published port and no request log. An administrator turns on IGN's public service, in Settings → Address search, only with the agreement of whoever owns the delivery data.

Sign-in, sessions and passwords

  • Passwords have 15 to 128 characters and are stored as Argon2id hashes. There is no default password, public sign-up or email service.
  • Users change their own password in Personal preferences after entering the current one; their other sessions end. An administrator sets a new password for another user of the company from Edit user, which ends that user's sessions. An administrator who cannot sign in needs another administrator or the operator command python -m app.manage_users reset-password USERNAME, run from backend or inside an application container; it prompts for the password.
  • Each sign-in issues a new random token; the database stores only its SHA-256 hash. Shared mode uses the __Host-vrp_session cookie: Secure, HttpOnly, SameSite=Strict, Path=/, no Domain. Local mode uses a separate vrp_local_session cookie with the same controls except Secure.
  • Sessions expire after 8 hours, or after 30 minutes without activity. Background status polling does not count as activity. Signing out ends the session; a change to a user's name, role or active state ends all of that user's sessions.
  • Writes require the configured Origin, X-VRP-Request: 1 and the session's X-CSRF-Token, kept in browser memory only.
  • Failed sign-ins are limited to 5 per username from one client address and 50 per username from all addresses, per 15 minutes; a successful sign-in clears that client's count. Failed password changes count toward the same limits. The bundled proxy also limits sign-in requests to 20 per minute per address, with a burst of 10, also behind an existing HTTPS proxy. These limits survive restarts; do not clear them to bypass a suspected attack.

Browser and network protections

  • Shared mode does not serve /api/docs or /api/openapi.json; use the contract published with this documentation.
  • The proxy sends Strict-Transport-Security: max-age=15552000 (180 days, this host only) and accepts TLS 1.2 and 1.3. Behind an existing HTTPS proxy, that proxy negotiates TLS and holds the certificate.
  • The application sends a same-origin script Content-Security-Policy with frame-ancestors 'none', X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and a Permissions-Policy that disables camera, microphone, geolocation, payment, USB and motion sensors.
  • Request bodies are limited to 1 MiB. Validation errors never repeat the values sent.
  • The application, proxy and router containers run with a read-only root filesystem, all Linux capabilities dropped and no-new-privileges.

Secrets and database roles

  • Launchers generate the secrets in workspace.env. Passwords are never command arguments: account commands prompt for them, and the backup helper passes database passwords through the child process environment.
  • Three database roles separate bootstrap administration, migrations and the restricted runtime role used by the API and worker. The runtime role cannot itself delete deliveries, vehicles, depots or calculations. It does so only through three functions owned by the migration role, each limited to one company: delete_orders deletes the chosen deliveries and refuses those a waiting or running calculation uses; delete_depot deletes a depot or place that is not the main depot and that no vehicle or delivery names; remove_examples deletes the examples, with the plans and calculations of example vehicles, and refuses while a calculation is waiting or running. Organization and user isolation depend on application authorization, not row-level security.
  • MONITOR_TOKEN (at least 32 characters) is sent as a bearer token. Without it, /api/v1/metrics answers 404. Metrics contain installation-wide counts only, never delivery or user data.

Data stored by an installation

The database holds accounts (username, display name, password hash, language), hashed session tokens and sign-in counters, depots and other places with their addresses (a place can be a driver's home), vehicles, saved customers with their addresses, entrances and instructions, deliveries with their addresses, instructions and coordinates, and calculations and plans, which copy those deliveries. Connecting network addresses are stored only hashed, for sign-in limits.

Logs are bounded (10 MiB files, three per container) and exclude request bodies, headers, query strings and SQL statements. The basemap provider in MAP_STYLE_URL sees the map area viewed; names, addresses and identifiers stay on the server. When the operator sets the installation's own geocoder, address lookup uses it; the one in the geocoder overlay keeps no request log. Pointing GEOCODER_URL at a public service would send it every looked-up address. Without a geocoder, an administrator can turn on IGN's public service for their company: the Géoplateforme then receives the text of every looked-up address and of every imported row without coordinates, with the depot's position. Without either, no address is looked up.

Plans and calculations are deleted, and one delivery's personal data erased, with the retention commands. These commands cover neither saved customers nor depots and places, which are changed in the application; a depot or place that nothing names, other than the main depot, can be deleted there. Backups keep earlier copies until they are pruned.

On this page