Security
How we protect your data, your tenants, and your voters, by construction.
Last updated
# EU data residency
All Upvoted user data is stored exclusively within the European Union. This is a structural commitment, not a configurable option.
- Primary database: PostgreSQL 17 hosted on Neon, region
eu-central-1(Frankfurt, Germany). - CDN and frontend: Cloudflare Pages with EU-priority routing.
- File storage (logos, CSV exports): Cloudflare R2, EU jurisdiction.
- Backups: object storage located in the EU, 30-day retention.
No user data transfer outside the EU occurs as part of the nominal service operation.
# Encryption
In transit: all traffic between browsers, the embed widget, the API and the database uses TLS 1.2 minimum (TLS 1.3 preferred). HSTS preloaded on upvoted.pro.
At rest: all PostgreSQL databases and storage volumes are encrypted at the disk level (AES-256) by the hosts (Neon, Cloudflare R2).
Passwords: bcrypt hashing with cost factor 12 (~250 ms per hash). Passwords are never stored in plain text, never logged, and never transmitted to sub-processors.
Tokens: opaque tokens (email verification, refresh tokens) are stored as SHA-256 hashes; the original only exists on the user's side (httpOnly cookie or email link).
# Authentication
Administrators: email + password authentication (bcrypt cost 12, minimum 10 characters) with short-lived JWT session (1h) and rotating refresh token (7 days, httpOnly Secure SameSite=Strict cookie).
Voters: magic-link authentication only, no passwords. The link expires after 24h, is single-use, and scoped to a single vote or comment.
SSO / SAML: planned for the Business plan (V3) with support for Okta, Google Workspace and Microsoft Entra providers.
Rate limiting: 5 magic-link requests per IP/minute, 10 links per email/hour, plus IP+UA fingerprinting for abuse detection.
# Multi-tenant isolation
Upvoted uses two-layer independent isolation to prevent any data leak between tenants:
- Application layer: every request is routed through middleware that resolves
tenant_idfrom the JWT (admin side) or the Host header (public side).tenant_idis never accepted from a client request body. - Database layer: every table containing tenant data is protected by a PostgreSQL Row-Level Security (RLS) policy enforced even at the table-owner level (FORCE ROW LEVEL SECURITY). A session can only access rows matching its
tenant_id.
A single error in either layer is not enough to cause a leak. An integration test continuously verifies that a session from tenant A returns zero rows when queried for tenant B's data.
# Sub-processors
Upvoted relies on the following sub-processors to deliver the service. The current list is available on request at [email protected].
- Stripe Inc. (Ireland), payments, billing, EU VAT calculation. Upvoted stores no payment card data.
- Resend (United States, EU region selected), transactional emails (verification, reset, notifications).
- PostHog EU (Germany), product analytics and feature flags.
- Cloudflare (United States, EU edge), CDN, DNS, DDoS protection, Cloudflare for SaaS for custom domains (V2).
- Neon (United States, region
eu-central-1), PostgreSQL database.
All sub-processors have signed a Data Processing Agreement (DPA) covering personal-data protection under the GDPR.
# Backups & disaster recovery
Daily full PostgreSQL backups with 30-day retention, plus point-in-time recovery over 7 days via WAL archiving. Recovery objectives:
- RPO (recovery point): less than 15 minutes
- RTO (recovery time): less than 4 hours
Backups are stored on encrypted object storage (AES-256) in the EU and their restoration is tested quarterly.
# Incident response
In the event of a security incident affecting personal data, Ekklo SAS commits to:
- Notifying the CNIL within 72 hours per Article 33 of the GDPR
- Notifying affected users without unreasonable delay when the incident presents a high risk
- Publishing a public post-mortem on upvoted.pro/blog within 30 days
Real-time status available at status.upvoted.pro.
# Report a vulnerability
If you discover a security vulnerability, report it to [email protected]. We acknowledge receipt within 48 business hours and commit to fixing critical issues as quickly as possible.
A formal bug bounty program will be set up in V2. In the meantime, we publicly recognize contributions on this page.