TeslaSync handles sensitive vehicle data β GPS positions, driving patterns, battery state, account credentials, and the keys to issue commands to real cars. Security isn't a feature of the platform; it's a load-bearing constraint on every design decision. This document describes how to report security issues, what TeslaSync does to protect data, and what you as an operator have to configure for the protections to hold.
Do not open a public GitHub issue for security vulnerabilities.
Email security@ev-dev-labs.com with:
- A description of the issue
- Steps to reproduce
- The version (git SHA or release tag) you observed it on
- Any logs, screenshots, or proof-of-concept that help us validate
We acknowledge reports within 48 hours and work with you on coordinated disclosure before any public discussion. We'll credit you in the release notes when the fix ships unless you ask us not to.
TeslaSync's threat model has three concentric circles:
- Tesla account credentials and tokens β if these leak, an attacker can read your fleet's state and (on accounts with command permissions) issue commands to real vehicles. Highest sensitivity.
- Personal data β VINs, GPS coordinates, driver names, addresses, alert/automation rules, charging cost data. Sensitive; leakage damages privacy and may violate local data-protection law.
- Operational data β logs, metrics, traces, schema, audit trail. Lower sensitivity but still useful to an attacker for reconnaissance.
The platform is designed so that compromise of one circle doesn't automatically expose the others. The forward-auth boundary, the encrypted token store, the Helix redaction decorator, and the audit log each address a different layer.
- Stored in the
tokenstable in PostgreSQL - Encrypted at rest with AES-GCM using a wrapping key sourced from
ENCRYPTION_KEY - In production, the API refuses to start if
ENCRYPTION_KEYis missing β there's no quiet fallback to plaintext storage - Refresh tokens are rotated automatically by the token-refresh worker; expired refresh tokens trigger re-auth flows rather than silent failure
- TOTP secrets (when 2FA is enabled) are encrypted with the same scheme
For dev environments, ENCRYPTION_KEY may be unset β the platform logs a clear warning and stores tokens in plaintext so developers can iterate. Never deploy this configuration outside a local machine.
TeslaSync uses forward-auth. The platform itself does not implement login UI; that's delegated to a reverse-proxy auth provider (Authentik, Authelia, oauth2-proxy, Keycloak, or a custom proxy). The proxy authenticates the user and injects an identity header. TeslaSync reads that header and looks up / creates the User.
This design has two consequences:
- The platform's auth is as strong as your proxy configuration. If the proxy is misconfigured (header injectable from outside, exemption rules too loose), TeslaSync inherits that weakness.
- Spoofing the identity header from outside the cluster is blocked at the ingress level β the proxy strips any incoming header before injecting its own. Verify your ingress does this.
A small number of routes are intentionally public:
/healthz,/readyzβ health probes (no sensitive data)- Tesla OAuth callback β the protocol requires it
/.well-known/appspecific/com.tesla.3p.public-key.pemβ Tesla's partner-key flow requires it- Drive-sharing token URLs β protected by a per-share random token
- Automation webhook receivers β protected by webhook secrets
Each public route has its own protection. "Public" means "bypasses forward-auth", not "unprotected".
For programmatic access (CI, scripts, integrations), the platform supports HMAC-signed API keys with scoped permission levels. Keys are stored hashed (not plaintext). Revoke via the API keys page in Settings.
The recommended deployment looks like this:
Internet
β
βΌ HTTPS (TLS 1.2+ enforced at ingress)
Reverse proxy / ingress
β ββ /.well-known β web (no auth)
β ββ everything else β ForwardAuth β web
βΌ
Web container (Nginx + React)
β
βΌ internal cluster network (HTTP)
API service (ClusterIP, not externally reachable)
β
βΌ internal cluster network
postgres, redis, mosquitto (ClusterIP)
What this means in practice:
- Only the ingress is externally reachable
- The API, workers, and data services are never directly exposed
- East-west traffic is plain HTTP because TLS termination at the ingress is the correct boundary; mTLS between internal services is supported via service mesh if you operate one
- CORS is restricted by
CORS_ORIGINSfor separate-origin deployments; same-origin deployments don't need CORS
For Docker Compose deployments, the equivalent is: front the stack with Nginx / Caddy / Traefik on the host, expose only the proxy port, and don't publish the API or data-service ports to the host.
- PostgreSQL holds the bulk of the data. Encrypt the underlying disk if your threat model includes disk theft (cloud providers do this by default; bare-metal deployments may need to opt in).
- Redis L2 cache holds live signal state and Helix rate-limit counters. No long-term sensitive material; an attacker with Redis access learns "current state of the fleet", not credentials.
- MQTT broker holds in-flight messages briefly. No persistence beyond what the subscriber consumes.
- MongoDB (optional) holds raw signal captures for debugging. Only enable when actively debugging; the data is verbose and sensitive.
If you've enabled Helix with a cloud provider, every call leaves your network. The platform mitigates this in three ways:
- Off by default β every Helix feature is independently toggled per user. A fresh install never sends data to any AI provider.
- Redaction decorator β by default (
AI_REDACTION_ENABLED=true), VINs, GPS coordinates, driver names, and email addresses are stripped before the payload leaves your server. The redaction is applied automatically to every call. - Provider choice β
ollamakeeps inference local. Use it if you can't send data to a cloud provider for legal or policy reasons.
The redactor is on by default and can be turned off, but the configuration is deliberately explicit β there's no silent path to leaking data.
Every sensitive operation writes to audit_logs:
- User authentication events
- Settings changes
- Token rotations
- Helix AI calls (in
ai_call_logspecifically, with provider, model, tokens, cost, latency, error) - Vehicle command executions
- Data export jobs
Retention default is 365 days for the row itself (AUDIT_RETENTION_DAYS), and the IP / User-Agent columns are redacted after 30 days (AUDIT_IP_RETENTION_DAYS) to minimise long-term PII. Tune both per your retention policy.
Three layers protect against abuse:
| Layer | Default | Tune via |
|---|---|---|
| Per-IP middleware | (configurable) | Middleware config |
| Per-feature Helix | AI_RATE_LIMIT_PER_MIN per user |
Env var |
| Per-feature daily cost | AI_DAILY_BUDGET_USD per Helix call total |
Env var |
Beyond our own limits, Tesla's own rate limits apply β exceeded limits surface to the user as TESLA_API_RATE_LIMITED with a Retry-After header where Tesla provides one.
- HTTP request body size capped (1 MB default, tunable)
- Geofence coordinates validated against valid lat/lng ranges
- CSV imports row-capped per upload
- All SQL is parameterised; no string concatenation anywhere in the codebase. Repository-level enforcement.
- JSON request shapes validated against typed structs; unknown fields rejected
- Output encoding handled by the standard library β no manual HTML/JSON construction
The published container images run as a non-root user. Helm chart defaults:
runAsNonRoot: trueallowPrivilegeEscalation: falsecapabilities.drop: [ALL]- Read-only root filesystem for the API container where possible
If you build your own images, preserve these defaults.
The platform's design assumes a few baseline operator commitments:
- Set
ENCRYPTION_KEYin production. Without it, Tesla tokens are stored in plaintext. The API refuses to start without it in production builds. - Front the stack with HTTPS. TLS 1.2 minimum; TLS 1.3 preferred.
- Configure forward-auth. A naked deployment without an auth proxy in front is not a valid production configuration.
- Don't expose the API or data services externally. Use
ClusterIP/ private bindings. - Rotate
ENCRYPTION_KEYcarefully if you do. Plan the rotation (re-encrypt existing tokens) before changing the key; otherwise tokens become unrecoverable. - Keep dependencies current. GitHub Dependabot and the CodeQL workflow flag vulnerabilities in the repository; act on the alerts.
- Audit your
srcExcludeand your ingress rules. A misconfigured ingress that lets/.well-knowngo through auth breaks Tesla Fleet Telemetry. A misconfigured ingress that exempts more than/.well-knownis a security hole. - Set realistic Helix budgets and rate limits. If you enable a cloud provider without
AI_DAILY_BUDGET_USD, a runaway page can rack up a bill. The budget is the safety net.
| Version line | Security fixes |
|---|---|
| Latest minor | β Active |
| Previous minor | β Security-only fixes |
| Older | β End of life β upgrade |
We aim to keep the upgrade path between adjacent minors safe and well-documented. If you're more than one minor behind, plan the upgrade β a clean upgrade is non-destructive but the longer you wait the more migrations land between you and current.
- GitHub Dependabot opens PRs for vulnerable dependencies in both Go and JavaScript trees
- CodeQL workflow runs static analysis on every push to
main - We aim to close high-severity CVEs within days; lower-severity issues are batched into the next release
When you report an issue, we aim to:
- Acknowledge within 48 hours
- Confirm or refute the issue within 7 days
- Ship a patch for confirmed issues within 30 days (high severity) or the next planned release (lower severity)
- Coordinate disclosure timing with you, with a default 90-day embargo
If we can't meet a target, we'll tell you why and propose a new one. Silent stalling isn't acceptable from us.
- Configuration β every security-relevant env var
- Architecture β the trust boundaries between services
- Helix AI β redaction decorator, audit ledger, provider matrix
- Kubernetes deployment β production-checklist for safe defaults