cpatpa closes the upstream audit findings

Three tightly scoped commits work through the bulk of the critical and high items flagged in the upstream security audit.

securityinfrastructure

cpatpa's team went through the audit report methodically. Two dead files that still referenced production secret keys are gone, so a stray import can no longer leak credentials to every browser session. Upload size caps are tighter, document conversions now time out instead of hanging, and a new safeguard rejects 'zip bombs' - archives that look small but expand to hundreds of megabytes - along with malformed XML tricks attackers use to crash parsers.

Database errors no longer flow straight to the client, which is a classic source of accidental schema leaks, and a stricter browser-side policy blocks the page from loading anything the server didn't authorise. None of this is glamorous, but it is exactly what a procurement security questionnaire asks about.

So what GCs and legal-ops leads vetting Mike-based vendors will recognise these as the table-stakes controls they should expect before signing.

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