Inspiration

Doctors want AI on real cases: "she's on tizanidine, can I start cipro for her UTI?" But every chatbot needs the patient's data to answer, so clinicians either paste charts into tools their compliance team never approved, or go without. "We won't use your data" is a promise. We wanted something a hospital could check instead of trust.

There's a second problem: the answers aren't safe either. We asked a 9B model 23 drug-safety questions that the FDA label answers directly. On its own it failed 22. Through BlindSpot, the same model passed all 23, each with a citation. In one case the bare model told us ciprofloxacin is "generally safe" with tizanidine. The label says the combination is contraindicated.

What it does

BlindSpot is a clinical question tool that runs on the doctor's laptop.

  • Connects to the EHR the way approved apps do (SMART on FHIR with PKCE, FHIR R4, plus HL7 v2 lab results over MLLP). Pick a patient; their meds, allergies, labs and conditions come along as context, read only on the laptop.
  • Answers with citations from FDA drug labels and NLM sources, written by a local model. Every claim links to the label sentence it came from.
  • Finds those passages without anyone seeing the question. The server ranks every passage against an encrypted question it can't read, then the laptop fetches the top 8 with private information retrieval, so the server can't tell which ones it sent. A relay in front hides who is asking.
  • Proves it, every answer. A "Sent" window shows every byte that left the laptop (about 330 KB of encrypted query and 176 KB of private fetches, 0 B readable). A proof panel then replays the server's side with only what it received, tries to read it, and reports four checks: no secret key left the laptop, the server can't decrypt the question, the question's words never appear in what was sent, and the server can't tell which passages were read.
  • Keeps consults private at rest: chats are saved per patient in an encrypted local store, and voice input is transcribed on the laptop with Whisper.

How we built it

Private search, following Tiptoe (Henzinger, Dauterman, Corrigan-Gibbs, Zeldovich, SOSP 2023). The laptop embeds the question with bge-small (384 numbers) and encrypts it with homomorphic encryption. The server multiplies it by the whole passage-embedding matrix without decrypting anything:

$$\mathsf{Enc}(s) = \mathsf{Enc}(q)\cdot D^{\top}, \qquad s_i = \langle q, d_i\rangle$$

Only the laptop can decrypt the scores $s$ and pick the top passages.

Private fetch with SimplePIR (Henzinger, Hong, Corrigan-Gibbs, Meiklejohn, Vaikuntanathan, USENIX Security 2023). To fetch passage $j$, the laptop sends an LWE encryption of the unit vector $e_j$; the server returns $D_{\text{text}}\cdot \mathsf{Enc}(e_j)$. It touches every passage for every fetch, so its work looks the same whichever one we wanted.

The library is built from FDA drug labels (DailyMed / openFDA) and MedlinePlus, crawled with Tavily and Exa, and rebuilt on a schedule on Railway (server, relay, a storage bucket and a cron job). Tavily and Exa only ever see our crawl queries, never a clinician's question.

The client is Python (FastAPI) with a plain HTML/CSS/JS interface. A guarded HTTP client logs every outgoing request to an egress ledger, which is what the Sent window and the proof read from. Answers come from qwen3.5:9b through Ollama, with a clinician-facing prompt. For the demo we built a mock EHR with 45 synthetic patients that speaks real SMART on FHIR and HL7.

Challenges we ran into

  • Speed. Encrypted search over thousands of passages on a laptop is slow: our search step takes around 30 seconds, and a full answer around 40. We made the wait honest (a progress bar that shows real stages) rather than hiding it, and it's the first thing we want to cut.
  • Proving a negative. "Nothing readable left" is easy to claim and hard to show. The ledger plus the replayed proof took several rounds before it caught every way plain text could slip out, including the EHR sign-in and the model download.
  • Small models and long labels. A 9B model loves to answer from memory. Getting it to answer only from the retrieved label text, cite each claim, and say "the sources don't cover this" when they don't, took most of our prompt work.
  • Real EHR plumbing: SMART discovery, PKCE, refresh tokens and HL7 acknowledgements, all without ever sending the chart anywhere.

Accomplishments that we're proud of

  • The same model goes from 22 of 23 wrong to 23 of 23 right just by reading the label through BlindSpot.
  • 0 readable bytes leave the laptop per question, and the app proves it after every answer instead of asking anyone to trust us.
  • A real clinician workflow: EHR sign-in, patient picker, per-patient sessions, saved consults, voice input.

What we learned

Private information retrieval and homomorphic encryption are practical today for a focused, high-value corpus like drug labels. The hard part isn't the math, it's the product around it: making privacy visible and checkable, and making a small local model trustworthy enough to cite.

What's next for BlindSpot

  • Faster search with Tiptoe-style clustering, so the library can grow past tens of thousands of passages.
  • A third-party relay (Oblivious HTTP) in place of our own.
  • Pilots with clinics: patient identifiers never reach us, which makes HIPAA review far simpler than for a cloud chatbot.
  • More sources: clinical guidelines, recalls and safety notices, all crawled with Tavily and Exa.

Built With

Share this project:

Updates

Submission history