ChurnShield handles Stripe payment events, customer PII, and sends dunning and win-back emails on behalf of SaaS operators. The integrity of that data is load-bearing for our customers’ businesses. This page explains how to report security issues, what’s in scope, and the defenses we already maintain.
We acknowledge every report within 48 hours on weekdays.
Machine-readable policy: /.well-known/security.txt
Reporting a vulnerability
Please do not open a public GitHub issue for security problems. Instead, email us directly. If you are demonstrating an issue against live customer data, please stop the demonstration as soon as you’ve confirmed the vulnerability is real and document what you observed without exfiltrating further data. Report by email and we’ll coordinate retesting on an isolated account.
What we ask you to avoid
- Don’t run scanners against
getchurnshield.comthat would consume our Stripe, Resend, Anthropic, or Supabase quotas. - Don’t attempt to access other customers’ data. A single record of PII is enough to demonstrate a finding — please don’t pull more.
- Don’t social-engineer our team.
- Don’t publicly disclose a vulnerability before we’ve confirmed a fix is live.
What we commit to
- Acknowledging your report within 48 hours.
- Keeping you informed of remediation progress.
- Crediting you by name once a fix is deployed (with your consent).
- Not pursuing legal action against good-faith security research that follows this policy.
Scope
In scope
getchurnshield.com and *.getchurnshield.com:
- Every page, every API endpoint (
/.netlify/functions/*) - The embeddable widget (
/widget.js) and the Stripe audit flow (/audit.html) - Any authentication, session, webhook, or MFA mechanism
Out of scope
- Third-party services we use (Stripe, Supabase, Netlify, Resend, Anthropic, Plausible, Twilio). Report those directly to the respective vendors.
- Social engineering of our team or customers.
- Physical attacks against our infrastructure providers.
- DoS / volumetric attacks (our CDN and rate limits handle those; no bounty for these).
- Findings that require a compromised customer device (keyloggers, browser extensions).
Our current security posture
ChurnShield takes a defense-in-depth approach. The list below is intentionally detailed so researchers know what to test without duplicating defenses that already exist.
Authentication
- Email magic-link login; no passwords to phish or leak.
- Session tokens and magic-link codes stored as SHA-256 hashes in Postgres.
- Magic-link codes are single-use with a 15-minute TTL; sessions expire after 7 days.
- Step-up MFA via 6-digit email code required for sensitive actions (API key generation, email change, webhook URL change, webhook secret rotation, opening the Stripe billing portal).
Authorization
- Every API handler derives
account_idfrom the session, never from the request body or query string. - All database tables have Row Level Security enabled with service-role-only policies.
- A BEFORE-UPDATE trigger blocks non-service-role changes to
plan,is_active,trial_end,trial_plan.
Input validation
- Allowlist validation on every settings payload.
- JSON body size capped at 64 KB on every POST handler.
- HTML output sanitized via
sanitize-htmlon all AI-generated email and dashboard content. - Prompt-injection mitigation: user-controlled fields are wrapped in structured
<customer_data>fences before being passed to the model. - CSV-injection prevention on exports (cells starting with
=,+,-,@, tab, or CR are prefixed with a quote).
SSRF and outbound requests
- All outbound webhook / Slack / OAuth fetches go through
src/lib/safe-fetch.js, which DNS-resolves the hostname and rejects any response in RFC1918, loopback, link-local, CGNAT, multicast, IPv6 ULA, or the AWS IMDS (169.254.169.254) range. - Redirects are surfaced, never followed.
Webhooks and integrations
- Stripe webhook signature verified via
stripe.webhooks.constructEvent. - Webhook event idempotency enforced via
public.stripe_webhook_events. - Scheduled processors require either Netlify’s scheduler signature or a
PROCESSOR_SECRETbearer header. Public HTTP invocations return 403. - Stripe Connect onboarding callback uses an HMAC-signed state parameter with a 30-minute TTL.
Transport and headers
- HSTS with
preload, 1-year max-age,includeSubDomains. - Strict Content Security Policy with
frame-ancestors 'none',object-src 'none',base-uri 'self', andupgrade-insecure-requests. X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin.- Cross-Origin Opener / Resource policies set to
same-origin.
Monitoring
- Every sensitive action is recorded in a
security_eventsaudit log: magic-link send, login, logout, session-expire, MFA challenge issue / verify / fail, API key generate / revoke, Stripe Connect authorize / callback. - Novel-device login alerts email the account holder whenever a session is minted from an IP and user-agent family the account hasn’t used recently.
- Distributed rate limiting on every public endpoint (per-IP and per-account where applicable).
Coordinated disclosure timeline
We aim to fix confirmed vulnerabilities within 30 days of the initial report. Critical issues are remediated immediately and disclosed publicly after the fix is deployed. We may request a longer embargo on complex fixes; if we do, we’ll explain why and propose a date.
PGP
We don’t currently publish a PGP key. Email us if you’d like us to generate one and we’ll publish it under /.well-known/.