Security and privacy
Private access
Google OIDC uses authorization code flow with PKCE, state and nonce. The server validates the signed ID token, accepted issuer, exact audience, expiry and nonce through a maintained library. Request only openid email profile. Account identity is (issuer, sub), not email. Choose and pin the Workers-compatible auth library after a spike. Google OIDC.
Use an opaque random session token in an HttpOnly, Secure, SameSite=Lax cookie; keep its hash in D1. Proposed session policy: 30-day absolute expiry, explicit renewal, immediate revocation on sign-out. Showing or rotating the forwarding address and account deletion require authentication within ten minutes. Check Origin and CSRF protection on commands. Deny cross-origin private RPC access. Do not cache private rendered HTML at the edge.
Every API and queue consumer checks account status and tenant ownership. Composite account foreign keys stop a correction from linking another tenant's card. Automated two-account tests cover IDs, cursors, revisions, R2 reads and exports. Redact financial facts, email addresses, tokens and merchant strings from logs. No personal information belongs in error trackers or public documentation.
Forwarding is a trust boundary
The random tag routes the account; it does not authenticate a bank. A copied tag can receive forged mail. Use a 128-bit random value, store a keyed digest for lookup and encrypt the display value with a versioned application secret. Rotation and lookup use primary D1, not eventually consistent KV.
Treat all message headers and MIME as untrusted. Inspect provider authentication evidence with explicit provenance; a forwarded From header or an attacker-supplied Authentication-Results header proves nothing. The engineer must demonstrate the trusted header boundary and ARC/DKIM behaviour using redacted Gmail and Outlook samples. Until that test passes, unsupported forwarding modes stay held for review. A tag is never sufficient evidence for a confirmed reward.
Proposed app limit is 64 KiB raw MIME, below Cloudflare's documented 25 MiB inbound limit. Oversized messages fail visibly with setup guidance; any increase needs CPU and cost review. Stream MIME with a byte ceiling, limit nesting and part count, ignore attachments and remote images, parse text without executing HTML, and never auto-follow links. Known bank templates and ambiguity checks precede posting. Email limits · Email handler.
Merchant inference
Use a conservative business-descriptor allowlist before any shared lookup or model call. Descriptor normalization alone does not anonymize a person's name. Transfers, personal names and uncertain descriptors stay private and bypass AI. A keyed hash is not permission to publish private text.
Personal corrections win for that account. Three distinct agreeing users make a descriptor a promotion candidate, not an automatic global MCC fact. A reviewed promotion requires business-only evidence. No amount, account ID or card details go to a classifier. Model confidence is not bank MCC confidence. The old 0.7 confidence threshold is a proposal until calibration proves useful. Disable AI and queue no indefinite backlog until benchmark, model budget and privacy gates pass.
Proposed retention and deletion policy
These values are Estimated technical targets pending Owner and privacy approval. Do not collect real pilot mail until approved.
| Data | Proposed lifetime | Mechanism and limit |
|---|---|---|
| Raw MIME | Seven days maximum | expires_at check on reads, hourly deletion job and R2 lifecycle backstop; no indefinite review extension |
| Structured facts and rule provenance | Active-account history | Keep only fields needed for capture, correction and explanation |
| Private runtime logs | Seven days maximum | Redacted metadata only; deployment must verify provider log retention configuration |
| Queue and DLQ payloads | One day | IDs only; expire jobs whose raw source is gone; review says source expired |
| Account access | Revoked in deletion command | Sessions and tags disabled before acknowledgement |
| Primary account-linked data | Within 24 hours of accepted deletion | Bounded resumable job; report overdue cleanup operationally |
| D1 recovery copies | Up to 30 days provider recovery window | No promise of per-account erasure from every recovery copy; verify actual expiry and contract before policy approval |
| Restore-block tombstone | Proposed 31 days | Opaque keyed deletion marker, restricted access; surviving marker must precede any restore |
D1 Paid documents a 30-day Time Travel window. That is a recovery feature, not proof of a legal deletion deadline. A restore runs in an isolated environment with intake disabled, replays deletion markers, and verifies no deleted account can regain access before traffic resumes. R2 raw MIME has no additional backup by this design. Any export or future backup requires its own retention entry. D1 recovery limits.
The design does not assume Singapore-only data storage or make a PDPA compliance claim. Owner and suitable privacy review must resolve processor terms, location, notice, deletion exceptions and breach handling before real data.