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 three kinds of data: the OAuth handshake (which we proxy and do not store, except briefly and encrypted when you add Send to Case from Clio's App Directory), pseudonymous error and performance reports through Sentry (file names replaced with codes, no file contents), and account and usage records, including short-lived IP address records. Your Clio access and refresh tokens are kept in your browser's chrome.storage.local, on your device. The privacy policy lists everything we collect.

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 brokers the OAuth handshake and account calls — 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
Not in the file path
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 — proxied; stored only briefly, encrypted, for "Add to Clio"

What reaches us: the proxied OAuth handshake, pseudonymous error reports, and account and usage records. File contents are never among them.

How a file actually moves

Every upload — file picker, right-click link, or batch scan — 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, account/entitlement calls, and recording usage events. 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; stored briefly only for "Add to Clio")

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 these tokens to any database, cache, or log. One exception: when you add Send to Case from Clio's App Directory ("Add to Clio"), the backend stores your tokens, encrypted, until the extension collects them. The collection code is valid for two minutes and works once, and unused entries are deleted within about an hour after they expire.

2. Pseudonymous error and performance reports

When something breaks, and to measure performance, the extension and backend send reports to Sentry so we can diagnose problems. We record every performance trace, not a sample, unless you turn off “Share usage statistics” in the extension, which stops the extension's traces, logs and session records and keeps its error reports. Reports include:

  • Stack trace and error message
  • Extension version, browser, and operating system
  • File sizes and MIME types (e.g. application/pdf)
  • A short hash of the file name (for correlating related errors)
  • Numeric Clio matter, folder, and document IDs (not names)
  • A pseudonymous user ID that we can link to the account

Before a report is sent, a scrubber removes email addresses and replaces file names and most of each web address's path with codes; the site's host name is kept. This filtering is best-effort: it does not recognize matter or client names, and part of a file name that appears elsewhere in an error message can survive. The privacy policy has the full list.

3. Account and usage records

Every account has an account row (Clio user ID, email, plan status) and a monthly upload counter used to enforce plan limits. We also record pseudonymous product-usage events, for example that an upload succeeded, its size and how long it took, but never file names, matter names, or client names. The extension stops sending them if you turn off “Share usage statistics”. Our backend keeps short-lived records of IP addresses for rate limiting and abuse prevention. 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 intentionally store or log the names (see the scrub limits above).
  • Court records or case data, or document metadata beyond the file sizes, file types, and numeric Clio IDs in error reports.
  • 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 or records navigation; error reports can include the host name of a site you uploaded from. 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 ledger a reviewer checks first. Green is what reaches us; oxblood is what we never receive.

We receive

3 kinds
  • +
    OAuth handshakeProxied to Clio in transit. Stored only for "Add to Clio": encrypted, until the extension collects it.
  • +
    Pseudonymous error reportsStack trace, version, file size, MIME type, a hashed file name, a coded user ID.
  • +
    Account & usage recordsClio user ID, email, plan status, upload counts, usage events, short-lived IP records.

We never receive

by design
  • ✕
    Files or file contentsUploaded browser → S3 directly; our servers aren't in that path.
  • ✕
    Matter or client names, on our serversWe pass matter IDs to Clio and don't store the names. Error reports aren't meant to carry them, but the scrub can't catch a name inside an error message.
  • ✕
    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 a report is sent, a scrubber removes email addresses and replaces file names and most of each web address's path with codes. It is best-effort: it does not recognize matter or client names.

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

The downloads permission, in plain terms

Send to Case uses Chrome's downloads permission to read your recent browser downloads, so the popup can list them as a shortcut when you choose a file to send. It does not start downloads on your behalf, and the 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 stored on any server we operate when you sign in from the extension — our proxy holds them only for the milliseconds of the token exchange. (Adding Send to Case from Clio's App Directory stores them on our backend, encrypted, until the extension collects them; see above.)
  • 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. Only "Add to Clio" stores them on our backend, encrypted and briefly.

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.
  • ✓Extension sign-in: 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.

For extension sign-in the proxy is a pass-through: it holds a token for the milliseconds of the exchange, then forgets it. "Add to Clio" is the one exception, held encrypted until the extension collects it.

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 five 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.
  • Tasks (create, on request): after a batch upload, and only when you click the button for it, create one task on the matter you uploaded to, assigned to you and listing the documents from that upload. Never automatic.
  • Calendar entries (create, on request): after a batch upload, and only when you click the button for it and type a date, create one calendar entry on the matter you uploaded to, on your default calendar, listing the documents from that upload. The date is the one you type; it is never read from a document. Never automatic.

The extension never deletes anything in Clio, and it does not change a task or a calendar entry after it creates one: to edit or remove one you use Clio. 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 five 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
tasks
Create one task on the matter you uploaded to, only when you click the button for it after a batch upload.
create
calendar entries
Create one calendar entry on the matter you uploaded to, only when you click the button for it and type a date, after a batch upload.
create

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. Disconnect also asks Clio to revoke the token, best-effort; step 1 is the way to be sure the refresh token is dead 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), holding among other things account and session rows, subscription and firm-seat records, upload counters, product-usage events, website referral records, uninstall survey answers, short-lived rate-limit and security records, and encrypted "Add to Clio" handoffs. The privacy policy lists what each holds and for how long.
  • Error monitoring: Sentry, with a scrubbing pipeline that removes email addresses and replaces file names with codes before transmit (best-effort).
  • 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