Security & Architecture

Security & architecture

Lucas Barnes By Lucas Barnes, Founder Last updated

Send to Case is a Chrome extension that uploads files from your browser to matters in your Clio account. This page is written for IT managers and security reviewers: what we receive, what we don't, where your OAuth token lives, what it can do, and how to revoke it.

The one-paragraph summary

Files never touch our infrastructure. They are fetched by the extension's own offscreen document inside your browser, staged in a browser-private sandbox, and uploaded directly to Clio's S3-backed document storage over HTTPS. We receive two things: the short-lived OAuth handshake (which we proxy but do not persist) and scrubbed error events from Sentry (no file names, file contents, matter names, or client names). Your Clio access and refresh tokens live only in your browser's chrome.storage.local, on your device.

Figure A · Data flow

Files go straight to Clio

Every upload follows one path: the extension fetches and stages the bytes in your browser, then PUTs them directly to Clio's S3 storage. Our proxy only brokers the OAuth handshake — it is never in the file path.

File path
Your browser → Clio S3 storage
direct, over HTTPS
No file touches our servers
On your device
Your browser
An offscreen document fetches the file and stages it in OPFS, then PUTs the bytes to S3.
Manifest V3
Never persists
OAuth broker
Cloudflare Worker
Adds the Clio client secret and forwards the token exchange. Not in the file path.
api.sendtocase.com
Destination
Clio API + S3
Returns a presigned S3 URL; your browser writes the bytes directly to storage.
S3 presigned PUT
OAuth handshake only — proxied, not stored

Two things reach us: the proxied OAuth handshake and scrubbed Sentry errors. File contents are never among them.

How a file actually moves

Every upload — file picker, right-click link, batch scan, or captured portal download — follows the same path. No files transit our servers, and no data is staged outside your browser.

  1. Fetch. The extension's offscreen document (a sandboxed, extension-owned page Chrome runs in the background) fetches the file from its source URL. Redirects are validated one hop at a time and blocked if they target private IPs, localhost, or link-local addresses, so court-site links that pass through internal hosts stay safe.
  2. Stage. Bytes are written to OPFS (Origin Private File System), a browser-managed sandbox scoped to the extension. OPFS storage is isolated from the web page, from other extensions, and from your regular file system.
  3. Upload. The extension asks Clio's API for an S3 presigned upload URL, then PUTs the staged bytes straight to Clio's storage. Large files (>100 MB) use S3 multipart upload from the same offscreen context.
  4. Clean up. OPFS staging is deleted when the upload completes (or is cancelled).

Our Cloudflare Worker is involved only in the OAuth handshake and account/entitlement calls. It is not in the file path and never sees file contents, file names, or the S3 PUT.

What data we receive

1. The OAuth handshake (in transit only)

When you click "Connect to Clio" and again when your access token expires, the extension sends the authorization code or refresh token to our proxy at api.sendtocase.com, which adds our Clio client secret and forwards the request to Clio. Clio responds with access and refresh tokens, which our proxy returns to the extension. The proxy is a Cloudflare Worker and does not write tokens to any database, cache, or log.

2. Scrubbed error events

When something breaks, the extension and backend emit error events to Sentry so we can diagnose it. Those events include:

  • Stack trace and error message
  • Extension version, browser, and operating system
  • Size bucket (e.g. "small", "medium", "large") — not exact bytes
  • MIME type (e.g. application/pdf)
  • A short hash of the file name (for correlating related errors)

Before an event leaves your browser, a scrubber removes email addresses, file paths, query strings, and common PII patterns. File names are hashed in code before they are ever attached to a log or event.

3. Account and usage counters

If you create a paid account, we store an account row (Clio user ID, email, plan status) and a monthly upload counter used to enforce plan limits. We store the count, not what you uploaded or where. Payment details are handled by Stripe — they never reach our servers.

What data we do not receive

  • Files or file contents. The extension uploads bytes directly from your browser to the S3 URL that Clio's API returns. Our servers are not in that path.
  • Matter contents, matter names, or client names. We send matter IDs to Clio's API on your behalf; we do not store or log the names.
  • Court records, case data, or document metadata beyond what the browser needs to render the page you started from.
  • Your Clio password. You enter it on Clio's site during OAuth. We never see it.
  • Browsing history. The extension does not declare any persistent content scripts and never watches your tabs, reads URLs, or records navigation. Code only runs on a page when you explicitly invoke it — for example, choosing "Find downloadable files" from the right-click menu, which temporarily injects a one-shot scanner via chrome.scripting.executeScript to find downloadable links on the current page. The scan happens in your browser; the list of found links is not sent to us.
Figure B · The data ledger

What we receive — and what we never do

The full ledger a reviewer checks first. Green is what reaches us; oxblood is what never can, by architecture.

We receive

3 things
  • +
    OAuth handshakeProxied to Clio in transit — never written to a database, cache, or log.
  • +
    Scrubbed error eventsStack trace, version, size bucket, MIME type, a hashed file name.
  • +
    Account & usage countersClio user ID, email, plan status, monthly upload count (paid accounts).

We never receive

by design
  • Files or file contentsUploaded browser → S3 directly; our servers aren't in that path.
  • Matter or client namesWe pass matter IDs to Clio; the names aren't stored or logged.
  • Your Clio passwordEntered on Clio's site during OAuth. We never see it.
  • Browsing historyNo persistent content scripts; code runs only when you invoke it.
The scrub

Before an event leaves your browser, a scrubber removes email addresses, file paths, query strings, and common PII patterns. File names are hashed in code before they are ever attached to a log or event.

The left column is everything we touch. Files, names, and passwords sit only in the right column — never received.

The downloads permission, in plain terms

Send to Case uses Chrome's downloads permission for two things:

  • Performing uploads. When you pick a file from a right-click or batch flow, the extension may ask Chrome to download it into the offscreen upload pipeline described above.
  • Capturing a portal download (opt-in, per click). Some sites use buttons that trigger downloads through in-page code (javascript: / blob: / fragment links) that a Chrome extension cannot fetch directly. If you right-click one of those and choose "Send to Case", we open a small confirmation window and watch only your next download from that tab, for up to two minutes, so we can match it to your click and upload it. Matching is scoped to the tab and time window you opened; anything you download outside that scope is ignored. Extension-initiated downloads are filtered out so we can never "match" our own activity.

Send to Case does not scan, index, or enumerate your existing download history beyond fetching recent entries when you open the "Recent downloads" panel in the popup — and even that list stays in the browser.

Where your OAuth token lives

Your Clio access and refresh tokens are stored in chrome.storage.local, the browser-managed keystore scoped to the extension on your device. They are:

  • Not synced to other devices and not stored in chrome.storage.sync.
  • Not written to any server we operate — our proxy holds them only for the milliseconds of the token exchange.
  • Readable only by the Send to Case extension on the Chrome profile that installed it.
  • Cleared when you disconnect from Clio, uninstall the extension, or clear extension storage in Chrome.

The OAuth exchange uses PKCE (RFC 7636). The one-time code verifier lives in chrome.storage.session for the length of the sign-in flow and is deleted as soon as the code is exchanged.

Figure C · Token storage

Your token lives on your device

Clio access and refresh tokens are kept in the browser's own keystore, scoped to the extension. They never land on a server we operate.

On your device
chrome.storage.local
Holds your Clio access + refresh tokens, scoped to this extension on the Chrome profile that installed it.
  • Not synced to other devices; never in chrome.storage.sync.
  • Never written to a server we operate — the proxy holds them only for the milliseconds of the exchange.
  • Readable only by the Send to Case extension on the profile that installed it.
  • Cleared when you disconnect, uninstall, or clear extension storage in Chrome.
PKCE

The OAuth exchange uses PKCE (RFC 7636). The one-time code verifier lives in chrome.storage.session for the length of the sign-in flow and is deleted as soon as the code is exchanged.

The proxy is a pass-through: it holds a token for the milliseconds of the exchange, then forgets it. Persistence is on your device only.

What the token can do

Clio's OAuth does not expose fine-grained scopes for API access: an access token inherits the permissions of the Clio user who approved it. In other words, the token can do what that user can do in Clio — no more, no less. In practice, Send to Case uses the token for three kinds of calls:

  • Matters (read): search and list matters so you can pick one.
  • Documents (create): create document records and obtain the S3 upload URL your browser writes to.
  • User profile (read): confirm the signed-in Clio user so the popup shows the right account.

If your firm restricts a user's access inside Clio (for example, to a subset of matters), the token inherits those restrictions automatically.

Figure D · Token capability

The token inherits your permissions

Clio's OAuth has no fine-grained scopes — an access token can do whatever the Clio user who approved it can do, no more. In practice the extension makes three kinds of calls.

What the user can do, the token can do — no more, no less. No fine-grained scopes
matters
Search and list matters so you can pick one.
read
documents
Create document records and obtain the S3 upload URL your browser writes to.
create
user profile
Confirm the signed-in Clio user so the popup shows the right account.
read

The scope labels are illustrative groupings, not literal Clio scope strings — Clio has none. Restrict a user inside Clio and the token inherits it automatically.

How to revoke access

You can cut off the extension's access to Clio in three ways:

  1. From Clio (recommended for firm-wide revocation). In Clio, go to Account Settings → Apps & Integrations → Connected Apps, find "Send to Case", and click Revoke. This invalidates the refresh token immediately, so the extension cannot mint new access tokens even if a user forgets to sign out.
  2. From the extension. Open the Send to Case popup and click Disconnect. This deletes the tokens from chrome.storage.local on that device.
  3. By uninstalling the extension. Removing the extension from Chrome deletes its local storage, including the tokens. To also invalidate the refresh token server-side, follow step 1 as well.
Figure E · Revocation

Three ways to cut off access

Revoking from Clio is the strongest — it invalidates the refresh token server-side, so the extension can't mint new access tokens even if a user forgets to sign out.

Recommended
1
From Clio
Account Settings → Apps & Integrations → Connected Apps → find "Send to Case" → Revoke.
Invalidates the refresh token immediately, firm-wide.
2
From the extension
Open the popup and click Disconnect.
Deletes tokens from chrome.storage.local on that device.
3
By uninstalling
Remove the extension from Chrome.
Clears local storage incl. tokens. Do step 1 too to invalidate server-side.

Steps 2 and 3 clear the device; only step 1 kills the refresh token on Clio's side.

Infrastructure at a glance

  • Extension: Chrome Manifest V3. Three in-browser surfaces — a service worker (orchestration, messaging), a popup (UI), and an offscreen document (the upload worker). The extension does not declare any persistent content scripts. Open a Chrome Web Store listing's "View source" to inspect the shipped bundle.
  • Backend proxy: A single Cloudflare Worker at api.sendtocase.com. It handles the Clio token exchange, Stripe webhooks, and low-volume account endpoints. It does not see or proxy file contents.
  • Database: Cloudflare D1 (SQLite at the edge) for account rows, subscription status, and monthly usage counters.
  • Error monitoring: Sentry, with a scrubbing pipeline that strips PII before transmit.
  • Payments: Stripe Checkout and billing portal. Card data is handled by Stripe; we receive only subscription metadata.

Reporting a security issue

If you believe you have found a security vulnerability, please email [email protected] with details (steps to reproduce, the affected component, and any proof of concept). We will acknowledge your report within two business days.

Coordinated disclosure. We ask that you give us a reasonable opportunity to investigate and remediate before any public disclosure — typically up to 90 days from our acknowledgement. We will not pursue legal action against researchers who report in good faith, avoid privacy violations and service disruption, and do not access or modify data that is not their own.

Remediation timelines. Once we have triaged and confirmed a vulnerability, we target the following timeframes to ship a fix, prioritised by severity:

  • Critical (data exposure, account takeover) — within 7 days.
  • High — within 30 days.
  • Medium — within 60 days.
  • Low — addressed in a future scheduled release.

We will keep you updated on our progress and let you know when a fix has shipped.

Related documents