security & vendor-risk overview
This page is the canonical answer to the security questionnaires partner publishers and enterprise listeners send. We aim to make due-diligence a 5-minute read instead of a 5-week PDF round trip. Anything missing? Email story@storyflo.com.
Last updated: 2026-05-08 · Version 1.0
quick facts
| Company stage | Private alpha |
| Production environment | Vercel (frontend) + Fly.io (backend) + Cloudflare R2 (audio) |
| Primary data location | US (sjc); EU listener data on Cloudflare R2 EU jurisdiction (WEUR) |
| Encryption in transit | TLS 1.2+ everywhere; HSTS preloaded |
| Encryption at rest | Fly Postgres (AES-256), R2 (AES-256), listener BYO keys (Fernet) |
| Authentication for listeners | Token in URL (magic-link distribution); no passwords |
| Authentication for publishers | Per-tenant access_token; magic-link OAuth on roadmap |
| Authentication for operators | /admin/login + cookie + 30-day remember-me |
| SOC 2 | Type 1 audit planned within 12 months of public launch |
| GDPR | DPA at /legal/dpa; DSR self-service at /legal/gdpr |
| Pen-test | Internal red-team only during alpha; external pen-test at GA |
data residency
storyflo's primary infrastructure runs on Fly.io in San Jose (sjc). Multi-region scaling to iad (US East) and fra (Frankfurt, EU) is on the immediate roadmap; until then EU listeners hit sjc with a higher latency.
Audio cache (R2): three buckets — storyflo-audio-cache (default global), storyflo-audio-eu (Cloudflare WEUR jurisdiction), and storyflo-audio-apac (Cloudflare APAC). Newly-rendered audio auto-promotes to all three; the region-aware url_forpicks the closest bucket based on the listener's CF-IPCountry header.
Postgres: Fly Managed Postgres, primary in sjc. Read replica in fra for EU latency is on the short-term roadmap. Daily encrypted backups; 30-day retention. Point-in-time recovery (PITR) being enabled this quarter.
encryption
- In transit: TLS 1.2+ enforced everywhere. HSTS with
max-age=63072000; includeSubDomains. Strict-Transport-Security preload list inclusion. - At rest (Postgres): Fly Managed Postgres uses AES-256 encryption at the volume level.
- At rest (audio cache): Cloudflare R2 encrypts every object with AES-256. Bucket access is keyed (no public ACLs except for the read-only audio CDN).
- Listener-supplied API keys (BYO-TTS): encrypted with Fernet (symmetric AES-128-CBC + HMAC) at rest using the
BYO_TTS_ENCRYPTION_KEYsecret. Plaintext never logged, never returned via the API; only the last 4 characters surface in the UI for identification. - Webhook signatures:all outbound webhooks signed with HMAC-SHA256 using the publisher's dedicated webhook secret. Receivers verify with
X-Storyflo-Signature.
access controls
- Listener data is keyed by
listener_token. The token IS the credential. No cross-tenant lookup paths exist. - Publisher dashboardrequires the publisher's
access_tokenon every request. Tokens are 64-hex secrets shown ONCE at onboarding. - Admin endpoints require the
X-Storyflo-Email-Tokenheader. Token lives on Fly secrets, never on disk. - Operator accessto the production database is via Fly's SSH-tunneled
fly postgres connect. SSH access requires hardware-key 2FA on Fly account. - Provider creds (OpenAI, ElevenLabs, Circle, Stripe) live exclusively on Fly secrets. Rotation: documented in the on-call runbook (linked from /admin).
sub-processors
Full list maintained on the DPA page. Material changes communicated by email to all affected publishers + listeners with 30-day notice.
incident response
Detection: Sentry (errors), Telegram alerts (cost-guardrail breaches, error-budget burn, reward-worker stalls, dead-letter queue depth). All alerts route to the on-call human within seconds.
Triage: 24h ack target; full SLA on /security. Severity classification: Critical (RCE, full-DB read, auth bypass) → 7-day fix; High (privilege escalation, data leak) → 30-day; Med/Low → 90-day.
Notification: Affected publishers / listeners notified within 72 hours of confirmed breach (per GDPR Art. 33). Public post-mortem published on /changelog within 30 days for any incident affecting more than 10 listeners or any single publisher's data.
business continuity + disaster recovery
- RTO (recovery time objective): 4 hours for full restoration from a region-wide Fly outage, assuming Postgres backup is replayable.
- RPO (recovery point objective): 24 hours currently (daily encrypted backup). Drops to ~5 minutes once PITR ships.
- Audio durability: R2 stores objects with 99.999999999% (eleven nines) durability. Multi-region promotion provides additional redundancy.
- Audio renderability: render-job rows are idempotent on retry; if a worker crashes mid-render the job re-claims from queue. Maximum 3 retry attempts before the dead-letter queue surfaces it.
platform posture
Storyflo is a proprietary platform with an intentionally open API surface — agents, models, and integrators can reach us through public endpoints, but the platform itself isn't a self-hostable codebase.
- Open by design: the MCP server at
api.storyflo.com/mcp/v1, the OAuth 2.1 flow, the REST API, the OpenAPI tool spec, and the published SDK packages (storyflo-sdkon npm,storyfloon PyPI, both MIT) — that's the agent / model integration surface. - Closed by design: the rendering router, the newsletter parser, the audio cache topology, the x402 pricing logic, the publisher Stripe Connect flow, and all platform infrastructure stay private.
Publisher security teams who need a deeper look-under-the-hood for vendor-risk review can request read-only audit access via security@storyflo.com.
open questions / not yet there
Where we're honest about gaps:
- No SOC 2 audit yet. Type 1 planned within 12 months of public launch; vendor-risk teams comfortable accepting this lifecycle stage during alpha can use this page as the equivalent control narrative.
- No external pen-test yet. Internal red-team only. External pen-test scheduled at GA.
- No paid bug-bounty program yet. See /security for our pre-bounty commitments and the 6-month timeline to launch one.
- No HIPAA / PCI-DSS scope. We don't process health data or store payment-card numbers (Stripe handles all card data).