Defense model at a glance
Details that matter
Rate limiting is post-authentication by design. Early in development the limiter keyed on client-controlled headers, which let an unauthenticated flood poison a victim’s bucket. It was rebuilt to key on the shop domain only after the proxy signature has been verified. The stats limit (1,200/min) is intentionally generous: the storefront widget polls every 5 seconds (12/min per open page), so the limit must absorb a store with a hundred concurrent product pages. Input validation is strict and fail-safe. Session identifiers must be UUIDs; product identity must be a numeric id or a slug handle matching a tight pattern; event types and device classes are allowlists; quantities are clamped to 1–999; cart tokens must be hex strings of bounded length. Anything malformed is dropped without erroring — the ingest endpoint never rejects a batch because one event was bad. Store scoping is total. Every analytics query filters by shop, and cross-shop isolation has dedicated regression tests (dedupe and identity resolution must never leak across shops). The order catch-up sync iterates all shops inside the scheduler, but every row it touches is processed under that row’s own shop scope. No customer PII exists in the schema. No names, emails, addresses, or full URLs — referrers are reduced to host names, identities are client-generated anonymous UUIDs, and the mandatory privacy webhooks reflect that (a customer data request is acknowledged with nothing to disclose; customer and shop redaction delete the rows that do exist). Secrets hygiene. Secrets live only in environment variables or the Shopify CLI session. The test harness pulls signing secrets from the CLI at runtime and keeps them in process memory.Accepted risks, published
Proxy signature replay window (±90 seconds)
Proxy signature replay window (±90 seconds)
Shopify’s app-proxy signatures tolerate a timestamp window and include no nonce, so a captured signed URL can be replayed for up to about 90 seconds. Mitigations: per-shop rate limits bound the damage, and view dedupe means replays cannot inflate view counts. Accepted because full replay protection would require infrastructure Shopify’s proxy contract doesn’t provide.
Deferred dependency advisory (turbo-stream reflected-input DoS)
Deferred dependency advisory (turbo-stream reflected-input DoS)
A published advisory affects the stream-encoding package inside the framework. The fixed version is a breaking major upgrade, deferred deliberately; exposure is limited because no untrusted request input is reflected into loaders. Tracked for the next framework upgrade window.
Single-instance in-memory state
Single-instance in-memory state
Rate-limit buckets and dedupe locks assume one process. This is a scaling constraint more than an exposure: it is listed here because a multi-instance deployment silently weakens both controls until they are moved to a shared store — see Deployment.
Security-relevant regressions are treated like any other defect: the failing case is reproduced as a test before the fix. The HTTP security suite (signature failures, rate-limit bursts, body caps, webhook HMAC) is the executable form of this page.