Security & GDPR
Soryk's security model is grounded in one design choice: your data lives in Shopify, not in our database. There's no app-side store of your customers, agents, products or orders to breach. What we do hold (sessions, transient OTPs, audit mirror, push subscriptions) is documented below.
Stateless Architecture
Soryk is stateless by design. We do NOT host:
- Personal information of merchants' customers
- B2B order data
- Commission ledgers (computed on-demand from Shopify)
- Quote payloads (encoded as JWT tokens)
- Audit logs (mirrored to merchant's own audit metaobject)
We DO host:
- OAuth shop access tokens (encrypted at rest, required for Shopify API access)
- Optional: VAPID keys for push notifications (can be regenerated)
- Optional: Anthropic API key (for Territory AI audit, configurable)
Result: GDPR-coherent by design. No DPA dance for B2B customer data.
Architecture
- No proprietary database. All business data — agents, rules, payments, audit log — lives in Shopify metaobjects you own.
- Upstash Redis for transient state: OTPs (10-min TTL), quote tokens, rate-limit counters, push subscriptions, analytics cache, and the append-only audit mirror (last 5,000 events).
- JWT sessions for agent / buyer / admin auth. Signed with HS256, stored in HttpOnly secure cookies. 90-day TTL.
- Resend for transactional email (magic links, OTPs, quote sends).
- Anthropic API for territory AI audit (sends only territory metadata, never PII).
- push notifications () for notifications.
Data residency
Hosted Soryk runs on Vercel's Frankfurt (FRA1) region by default. Upstash Redis is provisioned in EU regions. Resend is configured with the EU sending region. All processing of EU customer data happens within the EU.
If you self-host, you control where your deployment runs. We recommend matching your Shopify store's data region for compliance.
GDPR & DPA
- GDPR-compliant by design. We process only what Shopify already stores about your customers (which they've already collected lawfully under your store's privacy policy).
- DPA — Data Processing Agreement available on request. Email dario@onlyfresh.com with your company name and we'll send the signed copy.
- Sub-processors — Vercel (hosting), Upstash (transient cache), Resend (email), Anthropic (territory AI, optional). All EU-resident or EU-DPF aligned. Full sub-processor list maintained in the DPA.
- Right to erasure / portability — handled via Shopify's native customer data flows. Soryk has nothing extra to delete, because it has nothing extra to store.
Authentication
- Admin — Shopify session token (validated against the embedded app environment).
- Agent — JWT cookie issued after email-OTP magic link or Google SSO.
- Buyer — JWT cookie issued after email-OTP or Google SSO. Email must match a contact on a Shopify B2B Company.
JWTs are signed with HS256 against JWT_SECRET. Verification supports a previous-secret via JWT_SECRET_PREVIOUS for graceful rotation (see JWT rotation).
Rate limiting
All login endpoints and the bulk-payment endpoint are protected by Upstash sliding-window rate limiting.
| Bucket | Limit | Window |
|---|---|---|
agent:login / buyer:login (per email) | 5 | 5 min |
agent:login:ip / buyer:login:ip (per IP) | 30 | 5 min |
admin:payments (per shop) | 60 | 1 min |
Excess returns HTTP 429 with a Retry-After header. Without Upstash configured, the limiter degrades open (dev convenience).
Audit log immutability
Every privileged admin action (create agent, approve discount, record payment, set override, etc.) writes a soryk_audit_log metaobject and mirrors it to an append-only Upstash list (LPUSH + LTRIM 5000). The mirror is the tampering guard:
- If someone deletes a metaobject directly via the GraphQL Admin API, the mirror still has it.
- The Audit log page surfaces a warning if
orphanCount > 0(entries in the Redis mirror with a missing metaobject ID).
Tag injection prevention
Order tags can carry user-supplied values (agent email, manager email, buyer email). Without sanitization, a comma or quote in an email could split or corrupt tags downstream. Soryk sanitizes every value through buildTag(namespace, value) + sanitizeTagValue:
- Lowercase + trim.
- Replace
[, " whitespace]+with_. - Strip characters not in
[a-z0-9._@+-]with_.
Read-side, the commission engine indexes the email→agent map both raw and sanitized so attribution stays consistent even when the email contains + aliases or dots.
Buyer ownership enforcement
Every buyer-side API call verifies that the requested resource belongs to the session's companyId. The catalog endpoint, in particular, looks up the requested CompanyLocation → Company chain server-side before running contextual pricing. Cross-tenant requests get HTTP 403, never silent fallback.
JWT secret rotation
Zero-downtime rotation in 4 steps:
- Issue new secret. Generate a fresh strong random string.
- Set previous. Move the current
JWT_SECRETvalue toJWT_SECRET_PREVIOUS, then set the new one asJWT_SECRET. Deploy.verifyWithRotation()accepts both during this window. - Wait one TTL. Default JWT TTL is 90 days. After that, every active session has been re-issued under the new secret.
- Drop previous. Unset
JWT_SECRET_PREVIOUS. Done.
For a compromise scenario, skip step 3 and force-logout everyone immediately by also bumping a session-version field — sessions become invalid on next request and users re-auth.
What happens on uninstall
You uninstall Soryk from Shopify Apps:
- Shopify revokes the access token. Soryk can no longer make any Admin API call.
- Your data stays in Shopify. Products, customers, orders, draft orders, companies, B2B catalogs — all intact.
- Soryk metaobjects (agents, rules, payments, etc.) also stay in Shopify under your store. Re-installing Soryk later picks them back up.
- Transient Upstash state (active sessions, OTPs, rate-limit counters, audit mirror) is retained for 30 days, then purged.
To explicitly purge everything: uninstall, then email us with your shop domain — we'll wipe the Upstash side immediately.