Dshamir is quietly closing the security holes everyone else inherited

A run spent cherry-picking upstream security fixes - including a way to hijack the AI through a booby-trapped filename.

securityinfrastructure

Dshamir spent this batch pulling defensive fixes out of the upstream project's open issue queue - the unglamorous work that matters most when client files are involved.

The standout: a crafted filename on an uploaded PDF or Word document could previously smuggle hidden instructions into the AI's prompt, a classic "prompt injection" trick where attacker text gets treated as commands. Dshamir now scrubs untrusted filenames before they ever reach the model. The same run tightens the encryption around download links, locks the database so the public-facing account can read nothing by default, caps bulk document downloads so a single request can't exhaust the server, and cuts off AI responses that hang past three minutes. Each fix is small, scoped, and ships with its own tests.

So what Anyone weighing this fork for real client matters should note it's actively patching genuine vulnerabilities, not just stacking on features.

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