Back
Security
Last updated: May 18, 2026
Factual disclosure for IT departments, school administrators, and privacy-conscious users. No marketing copy. Each claim cites the code path or infrastructure config it is backed by.
1. Audio Storage
Default postureAudio submitted to the Judge is processed in memory and discarded after the report is returned. No audio file persists in our storage layer by default.
Optional storageUsers may opt in to retaining takes for longitudinal practice journals. Opting out deletes previously stored audio.
Storage layerSupabase private buckets (
simulator-audio). Public access is off at the bucket level. Row-Level Security policies restrict each user to their own folder: path segment 0 must match auth.uid(). Policy defined in supabase/migrations/0009_simulator_audio.sql.RetentionAudio associated with minor accounts (ages 13-17) is deleted within 90 days unless the report is explicitly saved. Adult takes follow the same default-discard policy.
Delete on requestAccount deletion at /account/delete queues a deletion record in the
deletion_requests table (supabase/migrations/0087_mark_cdl_deletion_requests.sql). All stored audio is purged within 30 days.Sub-processorsAudio passes through Replicate (CREPE pitch model) to extract features, and Anthropic (Claude) receives the feature data (not raw audio) to generate feedback. The raw audio file never leaves our processing pipeline. Full sub-processor list in /privacy.
2. Authentication
ProviderSupabase Auth (GoTrue). Email magic-link, email+password, and Google OAuth 2.0. Auth config managed in
supabase/config.toml.Password policyMinimum 8 characters, enforced server-side. Supabase project has
password_hibp_enabled: true (scripts/supabase-auth-harden.ts), blocking passwords that appear in HaveIBeenPwned breach lists. At signup, the app also runs a k-anonymity HIBP prefix lookup before creating the account (lib/password-strength.ts, lib/auth-helpers.ts checkPasswordViaApi): only the first 5 hex chars of the SHA-1 hash are sent to HIBP. The plaintext password never leaves our server.SessionsSession tokens are stored in httpOnly, Secure, SameSite=Lax cookies managed by Supabase Auth. No session data is stored in localStorage.
COPPA gateDate of birth is required at signup. Users under 13 are hard- blocked from creating an account. The block is enforced both client-side (
app/signup/SignupForm.tsx) and server-side (app/api/signup-gate/route.ts). The server gate is load-bearing; the client check is user-experience only.Sign out everywhereUsers can revoke all active sessions from /account/security. Implemented via
supabase.auth.signOut(scope: "global").MFATime-based one-time password (TOTP) MFA is on the roadmap. Supabase Auth supports it natively; we will expose the enrollment UI in a future Mark.
3. Payment Security
ProcessorStripe (live mode). We never touch card numbers, CVVs, or raw payment credentials.
PCI scopeCard entry happens entirely within Stripe-hosted Elements iframes. Our servers are outside PCI DSS cardholder data scope.
Webhook integrityIncoming Stripe webhooks are verified with
stripe.webhooks.constructEvent using a signing secret. Events with invalid signatures are rejected before any state changes. Webhook handler lives at app/api/stripe/webhook/route.ts.Stored dataWe store Stripe customer IDs and subscription IDs in
profiles.stripe_customer_id and the active_entitlements table. No card data is ever written to Supabase.4. Data in Transit
ProtocolTLS 1.2 minimum on all connections. Vercel and Supabase both enforce TLS termination at the edge.
HSTSVercel automatically sets
Strict-Transport-Security: max-age=63072000 on all responses. HTTP requests are redirected to HTTPS.Internal routingAPI routes on the same Next.js deployment communicate via same-origin. Supabase connections use TLS over the public Supabase API endpoint.
Audio uploadAudio is uploaded to our API endpoint over HTTPS, never over a plain connection. The app blocks submission on non-secure contexts.
5. Account Export and Deletion
Data exportOne-click export at /account/export. Calls
GET /api/account/export which returns a JSON file (orchestra-kingdom-export-YYYYMMDD.json) containing your profile, practice history, Judge reports, and metadata. No audio blobs are included in the export.Account deletionRequest at /account/delete. Submitting the form POSTs to
POST /api/account/delete-request and creates a record in the deletion_requests table with a purge_after timestamp 30 days out. A cron job surfaces overdue requests to the founder for manual execution.Deletion windowWe commit to completing deletion within 30 days of a verified request. The 30-day timer is enforced in the database schema: each deletion_requests row has purge_after = requested_at + 30 days.
Subscription prorationIf you have an active subscription, we prorate any unused time before closing the account. Contact support@orchestrakingdom.com if you need a same-day deletion.
6. Compliance Approach
COPPAUsers under 13 cannot create accounts. The gate is enforced at
app/api/signup-gate/route.ts. We collect no data from children under 13. For the full children's privacy policy, see /coppa.FERPA awarenessWe do not receive student education records from schools. Accounts are created by students or parents directly. Audio submitted for Judge analysis is processed and discarded; it is not shared with educational institutions or third parties.
GDPR alignmentEU users have the right to export (covered) and delete (covered, 30-day window). We do not use personal data for advertising. We identify sub-processors in our Privacy Policy. We do not transfer data outside of the Vercel and Supabase infrastructure footprints (US-based, with EU data residency options available via Supabase project configuration).
Ad trackingWe do not serve third-party advertising. We do not sell or share user data with ad networks. Analytics are collected via PostHog (self-hostable, session-replay opt-in only) for product improvement only.
RLS postureEvery table in the public schema has Row-Level Security enabled. Policies are defined in numbered migration files under
supabase/migrations/. Users can only read and write rows where user_id = auth.uid().Security questions or responsible disclosure:
support@orchestrakingdom.comWe aim to acknowledge security reports within 48 hours.