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
- Team access uses shared mode over HTTPS, with a certificate the users' browsers trust. Never expose local mode publicly.
- Only the HTTPS proxy is published; behind an existing HTTPS proxy, nothing is published and only the
edgeweb 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. - The private directory (
workspace.env,workspace.json,private-setup.url) and backups are readable by operators only, and never committed. - The first-use link is used once by the operator and never shared with dispatchers.
MONITOR_TOKENis set only if a monitor scrapes/api/v1/metrics.TRUST_PROXY_CLIENT_HEADERstays off behind a proxy other than the bundled one, unless that proxy overwritesX-VRP-Client. Behind Coolify or another existing proxy, keep the bundlededgein front of the API: it takes the visitor's address from the proxy'sX-Forwarded-For, and other containers on that shared network could set that header.- Backups are encrypted before leaving the host and a restore has been exercised. See Backup and restore.
GEOCODER_URLis 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 frombackendor 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_sessioncookie: Secure, HttpOnly, SameSite=Strict, Path=/, no Domain. Local mode uses a separatevrp_local_sessioncookie 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: 1and the session'sX-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/docsor/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-referrerand aPermissions-Policythat 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_ordersdeletes the chosen deliveries and refuses those a waiting or running calculation uses;delete_depotdeletes a depot or place that is not the main depot and that no vehicle or delivery names;remove_examplesdeletes 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/metricsanswers 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.