Skip to content
GitHub
Get started →

Security

Threat model

Assets we protect:

  1. Customer’s OpenAI API key (monetary value if stolen)
  2. Customer’s database credentials (data breach risk)
  3. OAuth refresh tokens for Shopify, Airtable, Google Sheets (continuous access)
  4. End-user voice audio (transient, not stored)
  5. End-user conversation transcripts (optionally stored 30 days)

Adversaries we defend against:

  1. Abuser — tries to run up our OpenAI bill via rate-limit bypass
  2. Snooping site owner — tries to read another tenant’s data
  3. Malicious end-user — tries to prompt-inject the AI into exposing other users’ data
  4. Network adversary — MITM on widget ↔ API or widget ↔ OpenAI
  5. Compromised dependency — npm supply chain attack

Controls summary

ControlWhat it protects
CORS per registered domainStolen script tags can’t work from another site
AES-256-GCM encryption of all customer secretsDB breach doesn’t reveal OpenAI keys / DB creds
HMAC-SHA256 request signing on /queryNon-widget callers can’t abuse /query
Ephemeral OpenAI tokensLeaked token damage bounded to ~2 hours
Read-only adaptersMalicious AI output can’t mutate your data
Identifier whitelistingAI can’t query fields you didn’t expose
Parameterized valuesSQL / NoSQL / GraphQL injection impossible
Rate limiting (per-IP + per-site)Brute force and abuse patterns contained
Zod input validationMalformed payloads rejected at the boundary

Rate limiting

EndpointPer IPPer Site IDBurst
/v1/*/token60/min200/min20/sec
/v1/*/query300/min30/sec
/v1/*/describe-image30/min5/sec
/v1/*/analytics/event600/min50/sec
/v1/auth/*20/min5/sec

Backed by Redis sliding-window counters. Exceeded → 429 with Retry-After.

Secret encryption

  • Algorithm: AES-256-GCM
  • Master key: 256-bit random, stored in Cloudflare Secrets or AWS KMS — never in DB
  • Per-record IV: 96-bit random, stored alongside ciphertext
  • AAD: site_id, to prevent cross-tenant ciphertext substitution
  • Format: {iv_base64}:{ciphertext_base64}:{tag_base64}

Master key rotation: quarterly, via re-encrypt job. Decrypt operations are audit-logged.

Request signing (widget ↔ API)

Every /query call includes X-Spelo-Signature: sha256=<hmac> over the body, using the session’s signing secret. The API verifies against the session’s secret.

Prevents someone scraping spelo.js and POSTing /query with another site’s ID.

Database read-only enforcement

Three layers:

  1. Connection-level — customers create read-only DB users; documented in every adapter’s setup guide.
  2. Query-level — adapters only emit SELECT, find(), GET. No write paths exist in code.
  3. Identifier whitelist — table / column / field names must appear in filterable_fields / searchable_fields / display_fields.

Values are always parameterized ($1, ?, bind variables) — never string-concatenated.

OpenAI key isolation

  • Customer enters key once in dashboard
  • API encrypts and stores it
  • Widget never receives the key
  • Widget receives ephemeral session tokens (2h TTL)
  • Leaked ephemeral → damage capped at ~2 hours of rate-limited OpenAI usage

Alternative: “Spelo managed AI” — we use our key and bill the customer.

OAuth token handling

  • Encrypted at rest (AES-GCM)
  • Scoped to read-only where the provider supports it
  • Auto-refreshed before expiry (background job)
  • Revocable from dashboard (calls provider’s revoke endpoint)

Account deletion revokes — not just deletes — OAuth tokens.

Input validation

Every incoming request validated with Zod:

  • site_id: [a-z0-9]{8,32}
  • URLs: https:// (or http://localhost in dev)
  • Filter values: typed against CollectionConfig
  • Form inputs: length + pattern checks
Content-Security-Policy:
script-src 'self' https://spelo.ai;
connect-src 'self' https://api.spelo.ai https://api.openai.com wss://*;
media-src 'self' blob:;

Egress IPs

Customer firewalls often want to allow only specific IPs. Our egress CIDRs:

  • us-east primary — 44.198.24.X/29
  • us-west failover — 52.53.72.X/29

The live authoritative list is at spelo.ai/security/ips.

Incident disclosure

If we experience a security incident affecting customer data:

  • Notify affected customers within 72 hours (GDPR-aligned)
  • Publish a post-mortem at spelo.ai/incidents within 7 days
  • Rotate affected secrets immediately

Penetration testing

Annual third-party pen tests; reports available under NDA to enterprise customers. Contact security@spelo.ai.

Bug bounty

Responsible disclosure is welcome. Email security@spelo.ai with reproduction steps. Rewards scale with severity and novelty.

Compliance roadmap

  • DPA template
  • Privacy policy + ToS
  • SOC 2 Type I (Q2)
  • SOC 2 Type II (Q4)
  • HIPAA BAA (on request for healthcare customers)
  • ISO 27001 (post Series A)

See also