Security

Last updated: 2026-05-25

This page summarises how EchoRelay protects customer data, what controls are in place, and how to report a vulnerability. It complements (it does not replace) our Privacy Policy and signed Data Processing Agreement.

Encryption at rest

PostgreSQL data volumes, Redis snapshots, and S3 backup objects are all encrypted at the storage layer. Sensitive values stored inside tenant configuration — outbound bearer tokens, HTTP basic credentials, Webhook signing secrets — are encrypted with AES-256-GCM using a single project-level key (SECRET_KEY), never persisted in plaintext.

Encryption in transit

All inbound traffic is TLS-only. Cloudflare terminates TLS at the edge and re-establishes a Full (Strict) TLS connection to origin — we do not accept plaintext from Cloudflare. Internal cluster traffic between Framework, Consumer, PostgreSQL, and Redis stays on a private network and never traverses the public internet.

Key handling

  • Project API keys (the er_live_… / er_test_… bearer presented by your caller) are hashed with SHA-256 at rest. The plaintext is shown once at creation; we cannot recover it for you.
  • Keys auto-rotate on a default 90-day cadence with a 7-day overlap and 24-hour post-expiry grace, so a careful client never sees an outage from rotation. Operators can rotate or hard-revoke at any time; a revoke takes effect on the next inbound request.
  • The MCP and REST read APIs never return a stored outbound secret in plaintext — bearer tokens and basic-auth passwords come back masked.

Authentication & account access

  • Passwords hashed with bcrypt (Symfony's modern default cost).
  • TOTP-based 2FA mandatory for every account; backup codes provided at enrolment.
  • Trusted-device cookies are capped per account and expire on a fixed schedule.
  • Sessions expire on idle and are pinned to the originating IP family.

Access control

Every project surface — config read, config write, key mint, billing — is gated by an owner/editor/viewer role checked at the Symfony voter layer. Cross-project access is impossible by construction: the voter denies any request whose URL slug doesn't match a project the authenticated user is a member of.

Backups & disaster recovery

PostgreSQL and the tenant-config JSON tree are backed up daily at 03:00 UTC to an encrypted S3-compatible bucket hosted by Hetzner. Restore is exercised on a documented runbook. Recovery objective: full management plane restored within 4 hours from the most recent nightly snapshot.

Audit log

Configuration mutations (lines, endpoints, targets), key lifecycle events (mint, rotate, revoke), and billing events (credit purchase, refund, balance adjustment) are recorded with the acting user ID, timestamp, and source IP. The log is retained for the lifetime of the account and is part of every GDPR Article 15 export.

Sub-processors

The exact list of third-party services that may touch your data is maintained in §5 of our DPA and updated whenever it changes.

Responsible disclosure

We welcome security research. Please report findings to [email protected] (PGP key on request). We acknowledge every report within 72 hours and aim to close confirmed issues within 30 days.

We do not run a paid bug-bounty programme today. Reporters who follow responsible-disclosure norms (no data exfiltration beyond what's needed to demonstrate the issue, no service disruption, no exposure of other customers) are listed in our hall of fame on request.

Out of scope: rate-limit findings on public endpoints, missing security headers without exploitability, social-engineering of staff, and reports generated solely by automated scanners without manual validation.

The machine-readable version of this policy is available at /.well-known/security.txt (RFC 9116).

Open-source attribution

Every direct dependency we ship is listed at /licenses with its copyright notice and license text, as required by the permissive licenses they're distributed under.

Currency: