Skip to content

Privacy and Telemetry

GiftWrapt is built for families running it for themselves. The default posture is nothing leaves your server unless you turned it on. No analytics, no usage telemetry, no error reporting, no anonymous pings.

  • No third-party error tracker is integrated by default. Out of the box, no DSN is set and the admin toggle is off; errors land in your server logs (stdout, accessible via docker logs / Vercel logs / your runtime’s log viewer) and stay there. An operator who wants error reporting can opt in to a Sentry-compatible backend they operate themselves; see Observability for the full opt-in flow. Events from an opted-in deployment go to the backend the operator configured (their Glitchtip, their Sentry org, their proxy), never to a maintainer-controlled domain.
  • No analytics SDK. No Google Analytics, no Plausible, no Posthog, no Vercel Web Analytics, not even a hit counter. This isn’t on the opt-in list either; client-side analytics stays out.
  • No “anonymous metrics” backchannel. The app does not check in with us to report version numbers, deploy counts, or anything else. Operators can optionally expose a Prometheus metrics endpoint on their own deployment (see Observability), scraped by their own Prometheus.

The framing is “default off, operator-routed when on.” Self-hosters running this for their household get the same posture they always had unless they go out of their way to opt in. The hosted instance at giftwrapt.dev either has these off or routes to a maintainer-operated backend, disclosed in its own privacy notice if applicable.

Reporting errors to an external service is a useful operator convenience that we deliberately do not enable by default, because the cost - exfiltrating fragments of user data through stack traces, payload dumps, breadcrumbs, and source maps - isn’t justified for a family-scale wish-list app unless the operator has actively decided they want it. The opt-in path makes that decision explicit instead of accidental.

When something blows up in production:

  • The error is logged to stdout via pino (src/lib/logger.ts) with the surrounding context (request id, user id where applicable, never session tokens or PII bodies).
  • The web app’s ErrorBoundary renders a generic fallback page. Server-side handlers return sanitized JSON (the mobile API uses a coded envelope, the web side returns Nitro’s generic 500 shape with no stack trace).
  • Nothing is transmitted to anyone but you.

If you hit a bug worth reporting, the manual path is the only path: grab the relevant log lines, screenshot the UI if applicable, and open an issue at github.com/shawnphoffman/giftwrapt. That’s a feature, not a bug.

There are a small number of outbound network calls. All of them are explicitly opt-in by the operator (configured via env or admin UI) and serve a feature the operator chose to enable. None are telemetry.

DestinationWhenWhatToggle
Your AI provider (Anthropic / OpenAI / etc.)Only when Intelligence / scraping features runItem titles, URLs, scraped page contentAdmin UI: AI provider settings; env AI_*
ResendOnly when email features are enabledRecipient address, email bodyAdmin UI: email settings; env RESEND_API_KEY
Your scraper provider (browserless / FlareSolverr / etc.)Only when scraping a URLThe URL being fetchedAdmin UI: scraper settings; env BROWSERLESS_URL / FLARESOLVERR_URL
Your object storage (R2 / AWS / your Garage cluster)Always, for image uploadsThe image bytes themselvesenv STORAGE_*
Vercel live.vercel.com scriptOnly on Vercel preview builds (VERCEL_ENV=preview)The page URL (for preview-comment overlay)Use a different host, or build with VERCEL_ENV unset
Sentry-compatible backend (the one you set)Only when SENTRY_DSN is set AND Enable Sentry Error Reporting is on in adminServer + browser error events; see Observability for the scrub listenv SENTRY_DSN; admin toggle enableSentry

Notably not in the list: WebAuthn / passkey registration. Passkeys are negotiated between the browser and your operating system’s authenticator (Touch ID, Windows Hello, a YubiKey); the only network traffic is between the user’s browser and your own GiftWrapt server. No external “passkey provider” is involved.

Source of truth for outbound calls is the CSP connect-src directive in vite.config.ts. If a new dependency tries to phone home, that’s where it surfaces. As of mid-2026 the production CSP under self-host / Docker / Vercel-production is 'self'-only with frame-src 'none' - no third-party origin is allowlisted at all. Vercel preview builds open up https://vercel.live and the matching pusher WSS so the preview-comments overlay still works, and only those.

  • Session cookie (better-auth.session_token) is httpOnly, Secure over HTTPS, SameSite=Lax. Set by your own server.
  • localStorage holds UI preferences only (theme, list-view density, etc.). Never tokens. Never analytics IDs.
  • No third-party cookies. The app never embeds an external <iframe> or <script> that could set one.

Production builds do not ship source maps to the client. Stack traces in your server logs reference minified line numbers; rebuild with maps locally if you need to deobfuscate.

What if a Contributor Proposes Adding Telemetry?

Section titled “What if a Contributor Proposes Adding Telemetry?”

The position is “default off, operator-routed.” A contributor PR that wires in error tracking, analytics, or any other phone-home gets:

  • Rejected outright if it’s enabled by default, or if it routes to a maintainer-controlled domain.
  • Considered for inclusion only if (a) it’s strictly opt-in via env AND an admin toggle, (b) it ships with credential scrubbing enabled by default, (c) the docs are updated to describe what’s emitted and where it goes, (d) the operator can point it at infrastructure they themselves operate.

The opt-in Sentry + Prometheus integration described above meets that bar. Client-side analytics (Plausible, Umami, PostHog, Vercel Analytics) does not, and stays out.