| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| README.md | 2026-08-18 | 3.9 kB | |
| v0.3.1 -- Security fix_ credential leak in inquiry_follow-up endpoints source code.tar.gz | 2026-08-18 | 963.7 kB | |
| v0.3.1 -- Security fix_ credential leak in inquiry_follow-up endpoints source code.zip | 2026-08-18 | 1.3 MB | |
| Totals: 3 Items | 2.3 MB | 0 | |
First real pilot-user feedback, triaged and fixed. Three critical bugs (hit hourly by a working sales team), two "check before building" items that turned out to be backend-complete, and a security leak found along the way.
Security
- Credential leak, fixed: staff-facing inquiry and follow-up
endpoints returned password hashes, TOTP secrets, and recovery codes
to any authenticated staff user.
GET /inquiries,GET /inquiries/:id, andGET /inquiries/:id/follow-upsembedded the fullUserrow forassignedTo/createdBy— includingpasswordHash,totpSecret, andrecoveryCodes— because a bare Prismainclude: { relation: true }returns every scalar column on the related model, not just the ones the UI displays. Any staff user with read access to inquiries (not just admins) could see this data in their browser's network tab on every list/detail/follow-up load. Fixed by scoping both call sites (InquiryService.findAll/findOne,FollowUpService.findAllForInquiry) to explicitselects ({id, name, email}/{id, name}), verified absent both in a through-the-wire regression test and by inspecting raw response JSON on a live deployed instance. If you are running v0.3.0 or earlier, upgrade to v0.3.1 and rotate any staff password or TOTP secret you consider exposed — anyone who had inquiry-read access and inspected network traffic (or logs, if any client/proxy captured response bodies) during that window could have captured these values.
Fixed
- A rep's own inquiry could silently reassign to admin.
InquiryService.create()ran round-robin unconditionally whenever a project was set, with no notion of who created the inquiry —Inquiryhad nocreatedByIdcolumn at all. New default policy: an interactively-created inquiry is assigned straight to its creator; round-robin only runs for machine-driven intake (inbound lead API, bulk import), which has no human creator to retain ownership for. Configurable viaCompanyConfig.presalesCreatorRetainsLead(default on). - Editing a user, for any field, always failed.
UserForm.tsx's edit-mode submit sent a create-shaped payload (includingemail) to a.strict()update endpoint that never declared it — every save 400'd. A deeper client-side bug sat in front of it: the form's validation resolver was alwayscreateUserSchema, which requirespassword— edit mode never renders that field, so submission failed silently at the validation layer before a request was ever sent. Both fixed; a new sharedpickForSchema()helper (projects onto an update schema's own declared keys, rather than subtracting fields off a create-shaped object) closes the class of bug, applied at all three known sites (this one, Masters, Broker commission payment) plus a regression test covering every create/update schema pair this package exports. - Direct URL access to an admin page rendered the full page shell for any authenticated user, regardless of permission — the backend correctly 403'd the underlying data fetch, but nothing distinguished "no data yet" from "not allowed here." Every protected route now renders an access-denied screen instead when the user lacks the permission that gates it.
Added
- Bulk Excel inquiry import now has a staff UI. The backend
(row-level validation, applicant dedup,
ExcelJS-based parsing) has existed since Phase 3 with no caller inapps/web— upload UI, a downloadable XLSX template (GET /inquiries/import-template, single source of truth shared with the parser's own header map), and per-row error reporting added to the Inquiries page. - Follow-up log now shows who logged it.
FollowUp.createdByIdhas been captured since Phase 3 and was already being fetched — just never rendered.