* fix(backend): bump sharp, nodemailer, multer, js-yaml, joi for security fixes
Backport of the same dependency bump on main (#1374). Resolves the
same 12 code-scanning alerts flagged on stable's backend deps: sharp
libheif RCE, nodemailer address-parser ReDoS + domain-validation
bypasses, multer upload DoS/race conditions, js-yaml parsing DoS, and
joi prototype pollution. All patch/minor bumps within the currently
used major version.
* fix(backend): set multer's fieldArrayIndexLimit to actually close CVE-2026-82333
The advisory is explicit that the 2.3.0 version bump alone doesn't
remediate the array-index DoS — an app must also set
limits.fieldArrayIndexLimit. Set it on every multer instance, sized to
what each route's form actually needs.
---------
Co-authored-by: Paul Nothaft <[email protected]>
Stable backport combining #860 (never reached stable) and #900:
- jest.config.js gains testTimeout: 120000 — stable still ran on Jest's
5s default for anything unpinned, while its migration chain (134 core
migrations via backports) is nearly as long as beta's.
- All 19 suite-level jest.setTimeout(30000/60000) pins raised to 120s;
local pins override the config default (#860's rationale).
- All 15 hook-ARGUMENT timeout pins on migration-booting beforeAll
hooks raised to 120s (#900's rationale — the 3.97.0-beta.0 release PR
failed on exactly this class on the beta side).
Untouched: the three suites whose pinned hooks don't run migrations
(webhookDelivery, imageProcessor.storage, storageBackend) and
publicQuotes' 30s pin on the rate-limit lockout test.
No test logic changed.
Co-authored-by: Paul Nothaft <[email protected]>
Closes#570.
PR #555 shipped the CRM module with strong service-layer coverage
but no HTTP-layer tests. This adds Supertest-based route coverage
across the externally-reachable public routes (P0) and an auth-gate
sweep of every CRM admin route (P1+P2).
## What's covered
### P0 — Public routes (49% of new tests)
The three public routes are the security-sensitive surface — any IP
with the raw token from a leaked email can hit them. Tests pin the
publicTokenGuards.loadActionToken contract end-to-end:
- **publicQuotes** (8 tests) — GET load + POST respond: 404 unknown,
400 malformed, 410 expired, 200 valid w/ sanitised payload (no
customer_account_id / created_by_admin_id leakage), 429 after 20
bad attempts (IP lockout), 400 invalid action.
- **publicContracts** (10 tests) — GET load + POST sign + POST
upload-signed-pdf + GET pdf: same guard outcomes per endpoint,
plus the pre-multer token check (malformed token rejected before
multer reads the body — prevents the disk-spam attack the
preMulterTokenGuard was added for).
- **publicPaymentCheck** (6 tests) — different shape (no
loadActionToken; service does its own validation): validator gate
on token shape, all 4 canonical actions pass through the
validator, negative amountMinor rejected.
The NULL-expires_at defensive branch in loadActionToken is
documented but not tested here — current schema declares
quote/contract_action_tokens.expires_at NOT NULL, so the branch is
unreachable at the route level. Worth a direct unit test on
loadActionToken if anyone wants to cover it.
### P1 + P2 — Admin routes (51% of new tests, 25 cases)
One consolidated `adminCrmAuth.test.js` file rather than nine
per-route files — the auth-gate contract is identical for every CRM
admin route, so a parametrised `describe.each` is more efficient
and lands the same coverage:
Per route (adminQuotes, adminContracts, adminInvoices, adminCalendar,
adminDeals, adminTaxReport, adminBusinessProfile):
- 401 without Authorization header (adminAuth gate)
- 401 with invalid JWT signature (adminAuth signature check)
- 2xx with super-admin token + CRM feature flags on (permission +
feature-flag gates both pass)
Plus 4 tests for the CRM additions in adminCustomers
(hour-entries / bill / trigger-monthly-bill) — those endpoints
are mixed in with pre-existing customer routes, so they get
explicit coverage rather than bulk via the parametrised sweep.
## Harness extensions to integration/helpers/crmDb.js
Three new helpers (one place for any future route test to find):
- `mintAdminToken(adminId, opts)` — JWT signed with the test
JWT_SECRET, shape matches what adminAuth expects.
- `createPublicToken(db, tableName, opts)` — insert a row into
quote/contract_action_tokens with controllable expires_at /
used_at / token. Note: Date values are explicitly ISO-stringified
before insert — bare Date objects round-tripped inconsistently
through knex+SQLite, sometimes via .toString() → literal
`"[object Object]"` which parsed back to NaN and silently defeated
the expiry guard. Caught it in test bring-up.
- `buildRouteApp(mount, router)` — minimal Express app (json + cookies)
with a catch-all error handler that mirrors middleware/errorHandler
(uses err.statusCode, not err.status — getting that wrong silently
maps every 4xx to 500 in tests).
- `assignAdminRole(db, adminId, roleName)` — promotes a seedMinimal
admin into super_admin (or any seeded role) for happy-path tests.
## Out of scope (follow-up)
Deeper integration tests for the document mint/send paths
(adminQuotes.send → PDF persisted + token minted + email queued;
adminInvoices.Storno → new row with shared deal_uuid + original
cancelled; adminContracts.countersign → integrity_hash computed)
are deferred. The service-layer behind those is already covered by
the existing __tests__/services/ suites — this PR pins the
HTTP-layer contract, which is what #570 actually asked for.
## Counts
- 4 new test files, 49 tests total
- ~860 LOC of test code + ~85 LOC of new harness in crmDb.js
- All tests pass in <2.5s (no real network, no real disk except the
per-test tmpdir, no email sending)