pretext · docs · security

Security

What we actually do — derived from the code, including the gaps. We crawl sites you point us at and publish what comes out, so the bar is: assume hostile input everywhere.

# Threat model

Pretext takes a URL you give it, crawls that site, feeds the scraped content to an LLM, and publishes the result. It also hosts public, unauthenticated MCP endpoints for vendors. That shape creates four risks we design against:

  • Server-side request forgery — a crafted URL steering our scraper at internal infrastructure.
  • Prompt injection — scraped third-party pages carrying instructions aimed at our generation models.
  • Abuse of public endpoints — unauthenticated MCP and demo surfaces consuming paid resources.
  • Tenant data leakage — one vendor's content or usage reaching another vendor's surface.

# SSRF protection

Every URL is validated before any fetcher touches it: schemes are limited to http/https, IP-literal hosts are rejected outright, and the hostname is DNS-resolved with the resolved address checked against blocked ranges — loopback, RFC 1918 private space, link-local, unique-local, carrier-grade NAT, multicast, reserved, and IPv6 transition ranges.

The multi-page crawl never widens that surface: discovered links are crawled only on the validated homepage's host (with www/apex treated as the same site), so a page can't redirect the crawler onto other hosts or subdomains.

# Prompt-injection hardening

Scraped content is scanned for injection markers (role overrides, “ignore previous instructions” variants, <system> tags, and similar) before any LLM call; a detection fails the generation permanently with no model invocation. Content that passes is structurally isolated in tagged data blocks the system prompt treats as data, never as instructions.

Model output is schema-validated, and vendor-voice fields (when to recommend, competitors, objections) are stubbed server-side after generation — a page that did smuggle instructions past detection still can't author those fields. The same pre-flight detection guards the public demo and skill-body generation paths.

# Authentication & sessions

  • Magic-link sign-in only — no passwords stored, ever. Links are single-use, expire after 15 minutes, and are only issued to approved accounts.
  • Session cookies are HMAC-SHA256-signed and verified with constant-time comparison; httpOnly, secure in production, SameSite=Lax, 30-day expiry enforced in the database.
  • Deep-link redirects after sign-in are sanitized and whitelisted server-side, so a tampered link can never become an open redirect.
  • Admin surfaces are role-gated; dashboard routes require an authenticated, approved user at the layout level.

# Rate limiting & abuse controls

  • Brief generation: per-user daily cap, counted against actual LLM-consuming runs.
  • MCP endpoints: 10 requests / 10 seconds per IP, a monthly per-vendor cap from their plan, and a separate hourly cap on ticket-creating misses.
  • Public demo surfaces use Vercel BotID to keep automated traffic from consuming the LLM-backed paths.
  • Client IPs are read from trusted, spoof-resistant headers and are stored and logged only as salted SHA-256 fingerprints — never raw.
  • Every vendor endpoint has an instant kill switch staff can flip if a tenant is being abused.

# Background job integrity

Brief generation runs on a signed queue: the worker verifies the QStash request signature (with key-rotation support) against the exact request URL before doing any work. Scheduled jobs are separate routes guarded by a bearer secret; unauthenticated calls get a 401 and do nothing.

# Multi-tenant isolation

Each vendor's MCP endpoint resolves its org by slug, checks the org is active (and not kill-switched) on every request, and serves it from a fresh, stateless server instance — no session state is shared across requests or tenants. Vendor dashboards only resolve the signed-in owner's org; staff access to another org is an explicit, role-gated impersonation path.

# Data handling

  • We store what the product needs and not much else: email, name, company, the briefs we generated for you, and vendor-published content. No passwords, no payment data, no phone numbers.
  • Data lives in managed infrastructure (Neon Postgres, Upstash Redis, Vercel) which encrypts at rest; Pretext does not add application-layer encryption on top.
  • Raw IP addresses are never written to logs or the database — only one-way salted fingerprints.
  • MCP call logs are pruned on a 30-day schedule; magic links expire in 15 minutes; sessions in 30 days.
  • Error monitoring (Sentry) runs with sampled traces and console breadcrumbs disabled.

# Honest gaps

Things we have not built yet, listed here so this page stays an audit and not a brochure:

  • No application-level CSP / HSTS / frame headers yet (transport security and DDoS mitigation come from the edge platform).
  • No audit log of staff/admin actions yet — it's on the roadmap ahead of broader staff access.
  • Injection detection is marker-based, not model-based; it's a fail-closed first line, not a guarantee, which is why structural isolation and server-side field stubbing back it up.

# Reporting a vulnerability

Found something? Email security@pretext.md with steps to reproduce. We read every report, respond within two business days, and won't pursue good-faith research conducted without data exfiltration or service disruption.