Skip to content

Data Flow — Chat Attachments (ADR-0043 Phase 1)

  • Status: Draft (companion to DPIA + DPA template)
  • Date: 2026-05-09
  • Purpose: regulator-facing diagram of every region a byte may visit. Auditors get a single page; engineering keeps it accurate.

1. End-to-end flow (image upload, vision-enabled tenant)

Section titled “1. End-to-end flow (image upload, vision-enabled tenant)”
[Visitor browser, $any region]
│ HTTPS multipart POST (TLS 1.3)
[Cloudflare Worker — apps/app — closest CF edge]
├── 1. Validate session: tenant slug + visitor cookie
├── 2. Magic-byte sniff: PNG/JPEG/WebP/HEIC head bytes
├── 3. Edge classifier (CF Workers AI — global edge):
│ "is this an ID card / face / credit card?"
│ ├─ "yes" + tenant policy = block → reject 422
│ └─ "yes" + tenant policy = warn → proceed, mark sensitive
├── 4. Rate-limit check (KV_SESSIONS — APAC primary)
├── 5. Quota check (D1 query — APAC primary)
├── 6. R2 PUT (Cloudflare R2 — APAC primary, Singapore)
│ Object key: attachments/{tenantId}/{visitorId}/{nanoid}.{ext}
│ Encrypted at rest: AES-256, CF-managed keys
│ Custom metadata: {tenantId, visitorId, sensitive: bool}
├── 7. D1 row insert (apps/app D1 — APAC primary)
│ messages.attachments_json += {id, url, type, size, hashedName}
└── 8. Audit log entry (D1 — same row)
chat.attachment.upload, no PII in payload
│ HTTP 200 response with {id, url} to widget
[Visitor widget]
│ Visitor sends chat turn referencing attachment id
[Cloudflare Worker — apps/app]
├── If sensitive flag set → skip vision, route to agent inbox only
└── Else: pre-flight to vision
│ HTTPS POST to AI Gateway (CF) → Anthropic
[Anthropic Claude Haiku 4.5 — US-WEST-2 by default,
EU available on Enterprise tier]
├── Image content block + text content block
├── Inference happens entirely in Anthropic's infrastructure
├── ZDR contract: input bytes NOT retained beyond inference
└── Returns text answer
[Cloudflare Worker]
├── Persist text answer to D1 messages
└── Stream back to visitor
Stage Location(s) Encryption Retention
In-flight upload TLS 1.3 over public internet → CF edge TLS 1.3 in transit None — transient
Edge classifier CF Workers AI — global edge TLS internal None — stateless inference
Rest in R2 CF R2 — APAC (Singapore) AES-256 server-side Until tenant-defined retention or visitor erasure
In Anthropic memory US-WEST-2 (or EU on Enterprise) TLS internal + Anthropic infrastructure None under ZDR; up to 30 days without ZDR
In D1 (metadata only) CF D1 — APAC AES-256 Same as R2
In audit log CF D1 — APAC AES-256 Per ADR-0023; tombstone after PII erasure
In Sentry breadcrumbs Sentry EU (project default) AES-256 + EU residency 90 days; bytes scrubbed via beforeSend

Documented for auditors who ask the negative question:

  • Not in browser localStorage / IndexedDB beyond the live session
  • Not in Cohere (text-only reranker; never receives image bytes)
  • Not in Stripe (billing only; no visitor data)
  • Not in Resend (we use CF Email Sending — see ADR-0042 — and email never carries attachment bytes)
  • Not in CDN public cache (Cache-Control: private for attachments; only avatars get public+immutable)
  • Not in CI logs (test fixtures use synthetic images, not real visitor data)
  • Not in backups beyond retention window (D1 daily backups inherit retention; R2 objects in scope of erasure cascade)
Field Visitor sees / sends Server stores AI provider sees
Original filename Yes (widget shows it) Hash + sanitised display name Original filename if image is sent (Anthropic API takes filename in metadata; we send attachment.{ext})
Source IP — (browser hides own IP) Yes (audit log; geolocated to country) No
Browser UA Yes (audit log) No
EXIF GPS in image Yes (browser) Stripped server-side before R2 PUT (TODO: phase 1 implementation must include this) No (post-stripping)
Image bytes Yes Yes (R2) Yes if vision-enabled and not sensitive-blocked

Defaults derived from plan; tenant admin overrides where allowed.

Plan AI vision default Sensitive-data policy Retention default
Free on warn 30 days
Team on warn 90 days
Business configurable, default on block 1 year, configurable to 7 years
Enterprise configurable, default off block per-conversation, max 7 years, legal-hold flag
Transfer Mechanism
Visitor (any) → CF edge (any) None required (TLS in transit only; edge processing is part of the service)
CF edge → R2 APAC (Singapore) CF DPA + SCCs (PDPA §28 equivalent + GDPR Chapter V)
CF edge → Anthropic US-WEST Anthropic Enterprise + ZDR addendum + SCCs
CF edge → Sentry EU EU SCCs + UK addendum
EU visitor → US transit SCCs as above; surfaced to visitor in privacy notice
Thai visitor → all transits DPA discloses recipients; PDPA §28 SCC-equivalent in DPA

7. Where this document lives in the regulator-facing pack

Section titled “7. Where this document lives in the regulator-facing pack”

When a regulator (PDPC TH, EU DPA) requests documentation on processing:

  1. DPA (docs/compliance/dpa-template.md — instantiated per customer) — defines processor / controller relationship
  2. DPIA (docs/compliance/dpia-attachments.md) — risk analysis
  3. This document — visual + tabular flow proving the DPIA is implemented
  4. Audit log — proves it operated as documented

The four together form a self-contained answer to “where does this image go and why is that allowed.”

This document MUST be updated within 7 days when:

  • A new sub-processor is engaged
  • A region changes for any sub-processor
  • A new attachment-related route or endpoint is added
  • Retention policy changes for any plan tier
  • Tenant-side knobs are added or modified

Authoritative source for changes: ADR-0043 + sub-processor change log in docs/compliance/dpa-template.md §6.