Juridisch
Sub-processors
Deze pagina is een rendering van het canonical bestand docs/security/sub-processors.md. Elke wijziging gaat via een PR die zowel dit bestand als het DPA-appendix aanpast en je tenminste 30 dagen vooraf op de hoogte stelt.
Sub-processors (Karet)
Last updated: 2026-09-01 · Owner: Founders · Reviewed: PRD-0035, PRD-0039, PRD-0090
This file is the canonical inventory of every third-party service that
processes Karet data on our behalf. ADR-0011 §5 makes adding a new
sub-processor a PR that touches this file plus the DPA appendix; a
PR that imports a new third-party SDK without a matching entry here
fails CI (scripts/check-sub-processors.ts).
The list is also mirrored on the public /legal/sub-processors page
(PRD-0036) for user-facing transparency. Customer notice before adding
a new sub-processor is 30 days (DPA appendix).
Active sub-processors
| Name | Purpose | Data categories | Region | Retention | SDK / hostnames |
|---|---|---|---|---|---|
| Vercel | Hosting, edge, blob, AI gateway | All app data, request logs, blob (receipts + voice memos) | EU (FRA1 / DUB1) | 90 days logs; blob per PRD-0036 retention | @vercel/blob, @vercel/*, vercel.sh, vercel-storage.com, ai-gateway.vercel.sh |
| Neon | Postgres (primary data store) | All transactional + journal data | EU (Frankfurt) | While workspace exists; 30-day delete window | @neondatabase/serverless, neon.tech |
| Anthropic | LLM (Claude) for chat, detectors, agents | Prompt content + workspace context (anonymised where possible) | EU processing via AI Gateway; zero-retention contract | 0 days (no-prompt-logging) | via ai + @ai-sdk/anthropic → ai-gateway.vercel.sh |
| OpenAI | LLM (GPT) fallback + embeddings | Prompt content + chat snippets | EU processing via AI Gateway; zero-retention contract | 0 days (no-prompt-logging) | via ai + @ai-sdk/openai → ai-gateway.vercel.sh |
| Resend | Transactional email (magic links, digests, security alerts) | Recipient address, subject line, rendered body | US storage. Resend offers no EU data region; the eu-west-1 setting on a sending domain controls only where mail is routed and sent from, not where data is stored. Transfers under SCCs + EU–U.S. Data Privacy Framework (incl. UK/Swiss extensions); Art. 28 DPA in force. | 30 days e-mail + log data (active account); deleted within 90 days after termination; backups persist 7 days | resend, api.resend.com |
| Salt Edge | PSD2 AIS bank sync (sole bank-data provider) | Bank account metadata (IBAN, holder name, balances) + transactions for connected accounts | UK entity holds the PSD2 AISP licence; EU data processing | While connection active; credentials + tokens deleted on disconnect, consent expires per PSD2 (90 days, re-consent required) | direct REST → www.saltedge.com/api/v6, hosted Connect flow on saltedge.com |
| Saxo Bank | Brokerage data (positions, trades) | Account positions + trade history | EU (HQ Copenhagen) | While connection active | direct REST → *.saxobank.com |
| Interactive Brokers | Brokerage data via Flex Queries | Account positions + activity statement | US (server-side Karet processes; user opts in) | While connection active | direct REST → ibkr.com |
| Bitvavo | Crypto exchange | Balances + trade history | EU (Netherlands) | While connection active | direct REST → bitvavo.com |
| SnapTrade | Aggregator fallback (DEGIRO, long-tail brokers) | Account snapshot data | Canada (DPA in place) | While connection active | direct REST → snaptrade.com |
| Have I Been Pwned | k-anonymity breach-password lookup | First 5 chars of SHA-1 hash of candidate password only | Cloudflare global | n/a (stateless) | direct REST → api.pwnedpasswords.com |
| Stripe | Billing (subscriptions, invoicing, tax) | Customer (email, BTW, KvK, billing address), payment-method reference (no PAN), invoice line items | EU (Stripe Payments Europe Ltd, Ireland) | 7 years invoices (NL art. 52 AWR); 30 days webhook bodies | stripe, *.stripe.com |
| PostHog (EU) | Product analytics, funnels | distinct_id (userId UUID), event names, non-PII properties | EU Cloud (eu.i.posthog.com, AWS eu-central-1) | 13 months (also mirrored in telemetry_events) | posthog-node, eu.i.posthog.com |
Notes on recent additions (2026-05)
- Stripe — DPA archived at
docs/security/dpas/stripe.pdf. PCI-DSS scope kept to SAQ A via hosted Checkout — Karet never touches PAN. Sub-sub-processors per Stripe's published list. - PostHog (EU) — DPA archived at
docs/security/dpas/posthog.pdf. Session Replay disabled at project level. No PII in event properties (lint enforced atsrc/lib/billing/telemetry.ts).
Correction — Resend region and retention (2026-09)
The Resend row previously read "EU region (eu-west-1)" and "90 days delivery logs". Both were wrong, and both overstated our privacy posture:
- Region.
eu-west-1is the sending region chosen per domain. Resend's own GDPR page states plainly that it offers no EU data residency and that all customer data is stored in the US, and that the domain region "controls where email is routed and sent from" — explicitly not where data lives. We had conflated the two. - Retention. 30 days is the active-account retention for e-mail and log data. 90 days is the post-termination deletion window, which is a different thing.
Source: https://resend.com/security/gdpr (checked 2026-09-01).
Open item for the founders, not resolvable in code. The marketing trust
strip, trust section and /trust page all state "Geen data buiten de EU" with
the supporting detail "Vercel fra1 en Neon Frankfurt". That claim is not
accurate while Resend is in the stack: every magic link, security alert and
digest carries a recipient address and a rendered body to US storage. The
options are to qualify the claim, to move transactional e-mail to an
EU-resident sender, or to accept and disclose the transfer. Nobody should edit
that copy without deciding which.
Notes on the PSD2 provider change (2026-08)
- Salt Edge replaced Tink (Visa) as the bank-data provider. PRD-0039 migrated all PSD2 AIS to Salt Edge and removed the Tink and GoCardless integrations; no Tink code, credentials or endpoints remain. Tink is therefore removed from the table above rather than kept as historical.
- The PSD2 AISP licence is held by Salt Edge's UK entity — Karet operates under it rather than holding its own AISP authorisation.
Pending (not yet active, listed because referenced in code)
- Sentry (EU) — error reporting. EU region required if/when adopted.
Known gap in the CI check
scripts/check-sub-processors.ts matches npm package imports against
the allowlist below. Every vendor Karet talks to over plain fetch —
Salt Edge, Saxo, Interactive Brokers, Bitvavo, SnapTrade, Have I Been
Pwned — is invisible to it. That is exactly why the Tink→Salt Edge swap
passed unnoticed: no SDK was added or removed, so there was nothing for
the check to catch.
Until the check also scans outbound hostnames, a direct-REST vendor
change is a manual review item on any PR touching src/lib/connectors/.
Allowlist (scripts/check-sub-processors.ts)
These package-name prefixes are recognised as "third-party SDK" imports by the CI check. A PR adding an import that matches one of the prefixes but has no row above will fail CI.
@anthropic-ai/ → Anthropic
openai → OpenAI
@ai-sdk/anthropic → Anthropic (via Vercel AI SDK)
@ai-sdk/openai → OpenAI (via Vercel AI SDK)
@ai-sdk/gateway → Vercel AI Gateway
@ai-sdk/react → Vercel AI SDK (client hooks; no PII leaves)
ai → Vercel AI SDK
@vercel/ → Vercel (blob, kv, postgres, etc.)
@neondatabase/ → Neon
posthog- → PostHog
@sentry/ → Sentry
@aws-sdk/ → AWS (KMS / SES)
stripe → Stripe
@upstash/ → Upstash (Redis-as-a-service)
resend → Resend
@react-email/ → React Email (rendering; not a sub-processor itself)
better-auth → Better Auth (open source; runs in our process)
drizzle-orm → Drizzle ORM (open source; runs in our process)
@react-email/, better-auth, and drizzle-orm are libraries that
run inside our process and don't ship data to a third party — listed
here so the lint doesn't flag them. Anything new matching the prefix
patterns above must be either added to the active table or explicitly
exempted in EXEMPT_PACKAGES in the script.