Dshamir quietly patches the attacks nobody else got to

Four commits, four different ways an attacker could have pried open a Mike deployment - each one closed.

securityinfrastructure

Dshamir worked through a short list of security gaps that mostly hide below the waterline. One: a document's filename could be used to smuggle instructions into the AI when it processed the file, so filenames now get scrubbed before they ever reach the model. Another: the links used to download case files leaked a tiny clue about whether a guessed access code was even the right length - the kind of crumb a patient attacker can follow. That's been sealed.

The heavier work is on stored data. Saved API credentials now get a unique encryption key per record instead of one shared key, so cracking one doesn't crack them all. And the database itself is locked to a deny-by-default posture, meaning the public-facing layer can't read raw records directly. Rounding it out: timeouts that kill stuck AI calls, and a cap that stops someone exhausting server memory with a bulk download.

So what Anyone running Mike on real client matters and stored credentials should look at this fork before the next pen test does.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

4 commits from Dshamir/AI-Legal, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
af4ed2db fix(security): sanitize filenames before LLM prompt interpolation Dshamir 2026-05-23 ↗ GitHub
commit body
Untrusted filenames from uploaded documents were interpolated directly
into LLM system prompts without sanitization, enabling prompt injection
via crafted PDF/DOCX filenames. Add sanitizeLlmInput() to strip control
characters, collapse newlines, truncate, and NFC-normalize all
user-supplied values before they enter the prompt.

Addresses upstream PR #158.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52f47ba4 fix(security): timing-safe HMAC, HKDF per-row salt, case-insensitive email Dshamir 2026-05-23 ↗ GitHub
commit body
- downloadTokens: pad buffers to equal length before timingSafeEqual
  to eliminate length-oracle side channel (PR #81)
- keyRotation: add HKDF key derivation with random 16-byte per-row
  salt; existing rows without salt decrypt via legacy SHA-256 path;
  all new encryptions use HKDF (PR #76)
- projects: use case-insensitive comparison for shared_with email
  in GET /projects/:projectId, matching access.ts pattern (PR #79)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76fedfc6 fix(infra): RLS deny-all on PostgREST, SSE stream timeout, zip doc cap Dshamir 2026-05-23 ↗ GitHub
commit body
- Add RLS to all public tables with deny-all default policy and
  auto-enable event trigger for future tables. PostgREST anon role
  can no longer read any data. Prisma service-role bypasses RLS (PR #145)
- Wrap runLLMStream() in Promise.race with 180s configurable timeout;
  sends SSE error event on timeout and closes connection (PR #112)
- Cap download-zip document_ids array at 50 to prevent memory
  exhaustion from unbounded batch downloads (PR #111)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eb70e06a feat(infra): chat cursor pagination, projectChat Zod validation Dshamir 2026-05-23 ↗ GitHub
commit body
- GET /chat now accepts a `before` cursor (ISO timestamp) for keyset
  pagination. Default limit set to 50 (PR #110).
- POST /projects/:projectId/chat validates request body via Zod
  schema with the validate() middleware (PR #155).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-526.md from inside the repo you want the changes in.

⬇ Download capture-thread-526.md