cpatpa closes the security findings before anyone ships on them

Phase 1 of cpatpa's fork works through a numbered list of audit findings and shuts each one down.

securityinfrastructure

cpatpa's fork started with a security pass that flagged real problems, and this round of work fixes them one by one. Two files that held keys to the project's file storage and database were sitting in the codebase unused - anyone could have wired them up by accident - so they're gone, with a note spelling out that those keys must never live in the front-end. The fork also locks down which web addresses are allowed to call the system, caps how large an uploaded file or request can be, and puts a hard time limit on document conversion so a single bad file can't hang the service. Malformed Word documents - a classic way to smuggle in an attack - now get screened before processing. And raw database error messages, which can leak internal detail, are replaced with generic responses to users while the specifics stay in the server logs.

So what Anyone evaluating this fork for client work should note it's being hardened deliberately, not just built fast.

View this fork on GitHub →

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

Commits in this thread

3 commits from cpatpa/PIP, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
204351ca Remove dead frontend secrets surface (C3, critical) Claude 2026-05-14 ↗ GitHub
commit body
Two frontend modules referenced privileged secrets but had zero
importers anywhere in frontend/src/:

- frontend/src/lib/supabase-server.ts read SUPABASE_SECRET_KEY (the
  Supabase service-role key, full god mode on the database).
- frontend/src/lib/storage.ts read R2_SECRET_ACCESS_KEY and instantiated
  an S3 client at module-evaluation time.

Their continued presence meant any future import from a client
component would silently leak these secrets to every browser session.
Both files deleted. SUPABASE_SECRET_KEY removed from
frontend/.env.local.example. README updated to make explicit that the
service-role key belongs in backend/.env only.

Closes security finding C3.
a46810a5 Harden backend against audit findings H2, H3, H4, H5, H7, M5, M6 Claude 2026-05-14 ↗ GitHub
commit body
CORS (H2): the backend now refuses to boot in production without
FRONTEND_URL set, and accepts a comma-separated allowlist of origins.
No localhost fallback in production.

JSON body limit (H3): global limit reduced from 50 MB to 1 MB. Chat
endpoints get a 10 MB override via a per-route parser. Both limits are
env-configurable.

LibreOffice conversion timeout (H4): docxToPdf now races the soffice
call against a hard timeout (default 60 s) so a malformed DOCX cannot
hang the request indefinitely. Throws DocxConversionTimeoutError on
expiry; existing call sites already log-and-continue on conversion
failure.

DOCX zip and XML guards (H5): new safeZip.ts wraps JSZip.loadAsync
behind loadZipSafely (rejects archives whose declared uncompressed
footprint exceeds DOCX_MAX_UNCOMPRESSED_BYTES, default 200 MB) and
adds assertSafeXml (rejects payloads above DOCX_MAX_XML_BYTES, default
50 MB, or containing DOCTYPE or ENTITY declarations). All four
JSZip.loadAsync sites in docxTrackedChanges.ts migrated. Predefined
XML entities continue to be processed correctly.

Email shape validation (H7): the JWT-supplied email is now validated
against a conservative shape before being interpolated into the
PostgREST `cs` filter on shared_with.

Multer upgrade (M5): bumped from 1.4.5-lts to 2.x. The 1.x line has
known DoS CVEs. API surface unchanged for our usage.

Helmet CSP (M6): replaced contentSecurityPolicy:false with a strict
default-src:'none'; frame-ancestors:'none' CSP on all API responses.

Supporting infrastructure: new logger.ts exposes devLog/devWarn that
no-op in production, and httpErrors.ts provides sendServerError and
friends so routes can be migrated off raw error message exposure.

Updated docs/security/01-threat-model.md and CHANGELOG.md with
remediation details.
35712cbe Redact route error responses and production logs (M3, M4) Claude 2026-05-14 ↗ GitHub
commit body
M3: routes no longer return raw Postgres error messages to clients.
The 24 sites of the
  if (error) return void res.status(500).json({ detail: error.message });
pattern across chat, projects, documents, tabular, workflows, user, and
tabular review-creation now use the sendServerError helper. The full
error is logged server-side with context; the client receives a
generic message.

M4: console.log calls in routes/documents.ts and lib/chatTools.ts
migrated to devLog. devLog is a no-op when NODE_ENV=production, so
document content excerpts, storage paths, and edit-resolution payloads
no longer leak to stdout in production. console.error retained where
appropriate.

Also removed backend/bun.lock and frontend/bun.lock. PIP standardises
on npm.

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-359.md from inside the repo you want the changes in.

⬇ Download capture-thread-359.md