Appearance
Status note (2026-09-25): Dependency snapshot from September 2026. Package versions and upgrade advice are date-bound; verify installed manifests and current package documentation before acting.
Dependency Audit — September 2026
TL;DR: dependency snapshot for backend and frontend from September 2026. Date-bound upgrade advice — verify installed manifests and current package docs before acting on it.
Investigation only. No package.json, lockfile, source, or config files were modified. Authoritative state: Bloxclips-backend@dev (01ec072) and BloxClips-frontend@dev (64bc449), both clean checkouts. Latest-stable versions resolved via npm view on 2026-09-21. Local runtime node v22.19.0 / npm 11.13.0.
Executive summary
Backend: healthy, slightly stale in safe places. Prisma family is on the latest stable line (7.10.0; the latest tag for the prisma CLI currently points at an 8.0.0-rc, so "newer" here means prerelease — stay put). Express 5 stack is current. The real action is a set of in-range lockfile refreshes that fix real advisories (axios prototype pollution, qs DoS, ip-address SSRF bypass, resend's svix/uuid chain) — no package.json edits needed for most of them because caret/override ranges already allow the fixed versions; the lockfile is just pinned to vulnerable copies. Whop invoice pin (1.0.14 alias) is deliberate and must stay. Three packages look unused (@noble/ciphers, date-fns, @types/ws).
Frontend: healthy and current where it matters. Next 16.3.3 is one patch behind latest stable (16.3.5); React 19 one minor behind (19.2.1 vs 19.3.0); Tailwind v4 one minor behind. All upgradable in low-risk batches. The @dnd-kit/* trio (core/sortable/utilities) has zero imports anywhere — remove candidate. playwright standalone alongside @playwright/test is likely redundant. Whop embedded components are current; do not touch. Majors available but not wanted now: lucide-react 1.x (184 importing files), framer-motion 13, jsdom 30, eslint 10, TypeScript 7.
Counts
- Meaningful updates worth doing now (A): ~9 backend + ~12 frontend (nearly all patch/minor, most lockfile-only).
- Major/high-risk upgrades needing their own tickets (B): backend 2 (
@whop/sdk,pgpair); frontend 4 (lucide1.x, framer-motion13, jsdom30pair, eslint10). - Deliberately untouched: Prisma family, Whop invoice alias, Express, TypeScript 5,
@types/nodemajors, dotenv 17, AWS SDK drift. - Suspicious/unused: backend
@noble/ciphers,date-fns,@types/ws; frontend@dnd-kit/*, standaloneplaywright. - Security: 4 backend advisories + 1 frontend advisory fixed by in-range refreshes (see Security findings). Remainder is transitive/dev-only or needs a parent minor bump (discord.js → ws/undici).
Phase 1 — Environment (both repos)
| Aspect | Backend | Frontend |
|---|---|---|
| Node expectation | No engines, no .nvmrc; CI setup-node: 22, Docker node:22-bookworm-slim → Node 22 is the contract | Same: CI Node 22, Docker node:22-bookworm-slim, no engines |
| Package manager / lockfile | npm, package-lock.json, lockfileVersion: 3, Docker/CI use frozen npm ci | Same |
| TypeScript | ^5.9.3, installed 5.9.3 = latest 5.x; target es2020, module commonjs, strict, skipLibCheck | ^5 (major range), installed 5.9.3; strict, bundler resolution, react-jsx |
| Framework/runtime | Express ^5.2.1 (installed 5.2.1 = latest), Prisma 7.10.0, pg 8 | Next 16.3.3 exact, React ^19.2.1, Tailwind ^4 |
| Build/test commands | build: tsc, test: offline suite via scripts/runTests.cjs; --integration, --authority, --campaign-funding suites gated behind DB env; start: node dist/index.js | build: next build, lint: eslint, test: node --import tsx --test tests/**; test:charts, campaign E2E scripts/e2e/run-campaigns.mjs; CI also installs Playwright Chromium |
| Versioning style | Mixed: exact pins for Prisma family (7.10.0), @whop/sdk (1.1.2), pg (8.16.3), @types/pg (8.16.0); caret everywhere else | Mixed: exact for next/eslint-config-next (16.3.3) and playwright (1.63.0); caret for most; major-only ranges for tailwindcss/@tailwindcss/postcss (^4), typescript (^5), eslint (^9), @types/react* (^19), @types/node (^20) |
| Runtime constraint on upgrades | Anything requiring Node >22 is out (nothing proposed does). ts-node only drives dev/scripts; production runs compiled dist/ (+ prisma generate postinstall needs dummy DATABASE_URL at install) | output: standalone Docker image; NEXT_PUBLIC_* inlined at build time; nothing proposed changes that |
No repo configuration constrains upgrade ranges beyond the exact pins above (all intentional — see per-package notes). Reproducibility comes from the frozen lockfile, so the broad ^4/^5/^9/^19/^20 frontend ranges are not a reproducibility problem and need no narrowing (see Types/toolchain note).
Backend
Legend — Recommendation: A update now · B separate ticket · C leave as-is · D investigate/possibly remove. Risk: Low/Med/High (migration + blast radius).
| Package | Current (declared / installed) | Latest Stable | Recommendation | Risk | Reason |
|---|---|---|---|---|---|
@aws-sdk/client-s3 | ^3.1041.0 / 3.1041.0 | 3.1136.0 | C | Low | Rapid release churn (~95 builds); S3 usage is basic Put/Get/Delete + presign (src/utils/storage/r2.ts). No security/correctness driver. |
@aws-sdk/s3-request-presigner | ^3.1041.0 / 3.1041.0 | 3.1136.0 | C | Low | Must stay aligned with client-s3; same churn reasoning. If ever bumped, bump both together. |
@noble/ciphers | ^2.1.1 / 2.1.1 | 2.4.0 | D | Low | Zero imports in src/scripts (verified). Encryption uses node:crypto (src/utils/encryption.ts, AES-256-GCM). Candidate for removal after confirming no dynamic use. |
@prisma/adapter-pg | 7.10.0 exact | 7.10.0 | C | High if touched | Latest stable (latest tag for @prisma/client = 7.10.0). Pinned trio must move together; no material benefit in any newer release (only RCs exist). |
@prisma/client | 7.10.0 exact | 7.10.0 | C | High if touched | Same. Generator (prisma-client, CJS, extensionless imports) is load-bearing for compiled dist/; startup behavior recently stabilized — do not churn. |
@types/qrcode | ^1.5.6 | 1.5.6 | C | Low | Current. Note: lives in dependencies though types-only; harmless, not worth churn (see §Unused). |
@types/speakeasy | ^2.0.10 | 2.0.10 | C | Low | Current. Same miscategorization note as above. |
@whop/sdk | 1.1.2 exact | 1.1.5 | B | High | Patch-level behind but financial integration: auth, payouts/transfers, team listing, webhooks all flow through it. Own ticket with sandbox+live verification; never bundled. |
@whop/sdk-invoices (npm:@whop/sdk@1.0.14) | alias exact | — (deliberate) | C | High if touched | Deliberate pin: src/utils/campaignFunding/whopProvider.ts documents the company_id invoice-creation contract pinned to 1.0.14 + WHOP_API_VERSION_DATE 2026-08-21-1. Consolidation is a separate investigation, not this audit. |
axios | ^1.16.0 / 1.16.0 | 1.20.0 | A | Low | Security: GHSA prototype-pollution pair affects ≤1.17.0. In caret range → lockfile refresh only. Also moves form-data to fixed ^4.0.6 range (axios 1.20 dep). Used by Tax1099 client. Verify: build + offline tests. |
cookie-parser | ^1.4.7 | 1.4.7 | C | Low | Latest. |
cors | ^2.8.5 / 2.8.5 | 2.8.6 | A | Low | One patch behind, in range. Refresh with Batch 1. |
date-fns | ^4.1.0 / 4.1.0 | 4.4.0 | D | Low | Zero imports in src/scripts (verified). Candidate for removal. |
discord.js | ^14.26.4 / 14.26.4 | 14.27.0 | A | Med | One minor behind. Bonus: only path to fixed transitive undici/ws (via @discordjs/ws → ws@8.19.0, both flagged). Used: src/api/server.ts bot client. Verify: build + offline tests + bot login/startup. If ws stays vulnerable after bump, add a ws override (separate decision). |
dotenv | ^17.2.3 / 17.4.2 | 18.0.1 | C | Low | Installed 17.4.2 = latest 17.x. v18 is a new major; usage is only side-effect import 'dotenv/config' — zero benefit to major churn now. |
express | ^5.2.1 | 5.2.1 | C | Med if touched | Latest. Express 5 migration already done (router@2.2.0, path-to-regexp@8 chain healthy). No concerns left. |
express-rate-limit | ^8.5.0 / 8.5.0 | 8.7.0 | A | Low | Two minors, in range. Pairs with ip-address fix (its dep range ^10.2.0 allows the fixed 10.7.2). Verify rate-limit behavior via existing e2e rate-limit test. |
helmet | ^8.1.0 / 8.1.0 | 8.3.0 | A | Low | Two minors, in range. Header-only behavior; verify no CSP/header snapshot tests break. |
iso8601-duration | ^2.1.3 / 2.1.3 | 2.1.4 | A | Low | One patch, in range. Used by src/utils/youtube.ts. |
jsonwebtoken | ^9.0.3 | 9.0.3 | C | High if touched | Latest; auth-critical, no reason to move. |
pdf-lib | ^1.17.1 | 1.17.1 | C | Low | Latest. Used by tax PDF generator. |
pg | 8.16.3 exact | 8.23.0 | B | Med | Seven minors behind, but exact pin + known DB-timeout sensitivity + adapter coupling → own ticket with startup/pool-behavior verification (see Batch 2 note). Must move with @types/pg. |
qrcode | ^1.5.4 | 1.5.4 | C | Low | Latest. Used by TOTP route. |
resend | ^6.8.0 / 6.8.0 | 6.28.1 | A | Med | 20 minors but in range; concrete payoff: 6.28.x deps drop svix entirely (now postal-mime + standardwebhooks), eliminating the flagged svix→uuid advisory chain. Email is non-critical path; verify with campaign-funding suite (it mocks/uses resend paths). Batch 2 (tested with discord bump). |
speakeasy | ^2.0.0 | 2.0.0 | C | Med if touched | Latest published, but upstream is effectively unmaintained — that is a watch item, not an action: TOTP auth depends on it, replacement would be a migration project. Do not churn now. |
zod | ^4.3.5 / 4.3.5 | 4.6.5 | A | Low | Three minors, additive in Zod 4; 49 importing files but no breaking-change signal. In range. Verify with offline + campaign-funding suites (validation-heavy). |
@types/cookie-parser | ^1.4.10 | 1.4.10 | C | Low | Current. |
@types/cors | ^2.8.19 | 2.8.19 | C | Low | Current. |
@types/express | ^5.0.6 | 5.0.6 | C | Low | Current; v5 types correct for Express 5. |
@types/jsonwebtoken | ^9.0.10 | 9.0.10 | C | Low | Current. |
@types/node | ^24.10.1 / 24.10.1 | 26.6.2 (major) / 24.13.x (line) | A (within 24.x) | Low | Overall "major behind" is intentional (Node 22 runtime; v26 types unnecessary). Within-line 24.10.1 → 24.13.x is in caret range — refresh with Batch 1. Do NOT jump to v26. |
@types/pg | 8.16.0 exact | 8.23.1 | B | Med | Paired with pg; move together in the pg ticket. |
@types/ws | ^8.18.1 | 8.18.1 | D | Low | No ws imports anywhere in src/scripts; ws itself is only transitive (via @discordjs/ws). Likely leftover. Removal candidate pending tsc proof. |
prisma (dev) | 7.10.0 exact | 8.0.0-rc.15 (latest tag; prev: 7.10.0) | C | High if touched | On latest stable line. v8 is prerelease — never chase latest here. Pinned with the client/adapter trio. |
ts-node | ^10.9.2 | 10.9.2 | C | Low | Latest 10.x. Still the right tool: dev entry (dev, deploy, scrape scripts, rbac:*) all invoke ts-node; production runs compiled dist/. No tangible benefit to a tsx/swc migration — explicitly not proposed. |
typescript | ^5.9.3 | 7.0.2 (latest; Go rewrite) / 5.9.3 (5.x line) | C | High if touched | 5.9.3 = latest 5.x. v7 is a new toolchain generation — defer well past MVP. |
Backend detailed notes (only where useful)
- Prisma:
npm viewconfirms@prisma/clientlatest = 7.10.0(we are current) while theprismaCLI'slatesttag already points at8.0.0-rc.15(prev: 7.10.0). This is exactly the trap the task warns about: a naive "upgrade to latest" would install a release candidate of the migration CLI against stable client/adapter. Stay on7.10.0across all three; revisit only when Prisma 8 goes stable and offers something we need (nothing on the horizon affects our usage: Postgres adapter + CJS generator + standard migrations). - Whop alias:
whopProvider.ts(invoice path: funding, fee policycharge_buyer_feemapping, reconciliation) importsWhopClientfrom@whop/sdk-invoices(= real SDK1.0.14), while everything else (whopClient.ts, transfers, team listing) uses@whop/sdk@1.1.2. The header comment pins thecompany_idinvoice contract to1.0.14+ API version date2026-08-21-1. So the alias is load-bearing, not accidental: it lets invoice calls stay on the verified contract while the general client moves. Divergence cost (two SDK copies) is real but small and understood; consolidation = behavior re-verification against live/sandbox money paths → separate ticket at best, likely never. pgexact pin:src/utils/prismaClient.tsbuilds every client throughPrismaPg(node-postgres pool with explicit timeouts — the file carries scar tissue about Neon/ETIMEDOUT).pgis never imported directly, but it is a runtime peer of the adapter, so the direct dep is correct. The exact pin is presumably caution around that history; lifting to^8+8.23.0is reasonable but belongs in its own PR with API-startup measurement (measure:api-startup) + integration runs.@types/qrcode/@types/speakeasyindependencies: used (TOTP route + middleware import both), just miscategorized — types-only packages conventionally live in devDependencies. Moving them is cosmetically correct but touches the install graph for zero runtime effect; leave until a real edit touches those lines.- Overrides: all five still resolve to real parents (see §Overrides). None can be removed now — notably
qsandip-addressranges already permit the fixed versions; only the lockfile is stale.
Frontend
| Package | Current (declared / installed) | Latest Stable | Recommendation | Risk | Reason |
|---|---|---|---|---|---|
next | 16.3.3 exact | 16.3.5 | A | Med | One patch behind latest stable (latest: 16.3.5; canary is 16.4.0-canary, avoid). Conservative framework judgment still favors taking a same-major patch (likely CVE/bug fixes + the transitive fixes npm audit wants). Verify: build + lint + tests + Playwright campaign E2E. |
eslint-config-next | 16.3.3 exact | 16.3.5 | A | Low | Must stay exactly synchronized with next (it lints against Next internals; our eslint.config.mjs extends its core-web-vitals + typescript presets). Move lockstep with next. |
react | ^19.2.1 / 19.2.1 | 19.3.0 | A | Med | One minor, in range. Keep paired with react-dom + both @types/*. No canary/experimental. |
react-dom | ^19.2.1 / 19.2.1 | 19.3.0 | A | Med | Paired with react (same PR, same verification). |
@types/react | ^19 / 19.2.7 | 19.3.0 | A | Low | In range; move with React pair. |
@types/react-dom | ^19 / 19.2.3 | 19.3.0 | A | Low | In range; move with React pair. |
@dnd-kit/core | ^6.3.1 / 6.3.1 | 6.3.1 | D | Low | Current version but zero imports repo-wide (only package.json mentions; "sortable" hits are an unrelated local component name + HTML draggable). All three are dead weight. |
@dnd-kit/sortable | ^10.0.0 / 10.0.0 | 10.0.0 | D | Low | Same. (Compat note becomes moot if removed: sortable@10 peers on core@^6.3.0, currently consistent.) |
@dnd-kit/utilities | ^3.2.2 / 3.2.2 | 3.2.2 | D | Low | Same. Verify removal with build + lint + tests, then delete all three in one PR. |
@tanstack/react-query | ^5.103.0 / 5.103.0 | 5.103.2 | A | Low | Two patches, in range. Used by marketing hooks/providers. |
@vercel/analytics | ^2.0.1 | 2.0.1 | C | Low | Latest. (Telemetry wrapper redacts private report URLs — untouched.) |
@vercel/speed-insights | ^2.0.0 | 2.0.0 | C | Low | Latest. |
@whop/embedded-components-react-js | ^1.2.0 | 1.2.0 | C | High if touched | Latest; high-risk embed surface (support screen). Do not touch. |
@whop/embedded-components-vanilla-js | ^1.2.0 | 1.2.0 | C | High if touched | Latest; paired with the React wrapper — keep synchronized, currently are. |
clsx | ^2.1.1 | 2.1.1 | C | Low | Latest (latest tag = 2.1.1). Paired with tailwind-merge in shared/lib/utils.ts. |
framer-motion | ^12.23.24 / 12.23.24 | 13.4.0 | B | Med | Major behind (v13 latest). Used across marketing surfaces. No security driver; motion-behavior regression risk across many components → own ticket, post-MVP. |
lenis | ^1.3.26 | 1.3.26 | C | Low | Latest. Used by SmoothScroll. |
lucide-react | ^0.555.0 / 0.555.0 | 1.47.0 | B | Med | Major line behind (1.x stable after 0.555.0; caret on 0.x correctly holds us back). 184 importing files — icon renames/removals are the risk. Own ticket with visual review; post-MVP. |
react-hot-toast | ^2.6.0 / 2.6.0 | 2.6.1 | A | Low | One patch, in range. Toast system has dedicated tests (tests/toast.test.tsx). |
tailwind-merge | ^3.4.0 / 3.4.0 | 3.7.0 | A | Low | Three minors, in range; class-conflict resolution only. Covered by build + tests. |
@playwright/test | ^1.63.0 / 1.63.0 | 1.63.0 | C | Low | Current. Coordinated pair with playwright below — versions match, good. |
playwright | 1.63.0 exact | 1.63.0 | C (version) / D (presence) | Low | Version current, but the standalone package is likely redundant: @playwright/test already ships the playwright CLI used by CI (npx playwright install --with-deps chromium). Investigate: remove playwright, run npm ci + install + E2E in CI; keep if the bin disappears. |
@tailwindcss/postcss | ^4 / 4.1.17 | 4.3.3 | A | Med | Two minors behind the Tailwind line, in range. Paired with tailwindcss — always bump together (plugin version tracks core). CSS-output diff risk → verify build + visual/E2E. |
@testing-library/react | ^16.3.2 / 16.3.3 | 16.3.3 | C | Low | Already resolving to latest via caret. Correct for React 19. |
@testing-library/user-event | ^14.6.1 | 14.6.7 | C | Low | In range; lockfile will resolve latest on refresh. |
@types/jsdom | ^21.1.7 | 30.0.0 | B | Low-Med | Major behind, but paired with jsdom major (see below). Move only together with jsdom@30 in the test-infra ticket. |
@types/node | ^20 / 20.19.25 | 26.6.2 | C | Low | "Major behind" is fine and intentional enough: runtime is Node 22, lockfile pins 20.19.25 reproducibly, no missing-API pain reported. No reason to pin narrower (frozen lockfile already guarantees reproducibility) and no reason to jump majors. Leave. |
eslint | ^9 / 9.39.1 | 10.11.0 | C (stay on 9) / B (v10 later) | Med if touched | ^9 correctly holds below v10 (whose engines require Node `^20.19 |
eslint-config-next | (see next row) | — | A | — | Paired move with next. |
jsdom | ^26.1.0 / 26.1.0 | 30.1.0 | B | Low-Med | Two majors behind, but test-only and working (5+ test files construct JSDOM directly; engines on v30 wants Node ≥22 — fine). No capability need → test-infra ticket with @types/jsdom@30, post-MVP. |
tailwindcss | ^4 / 4.1.17 | 4.3.3 | A | Med | Same as plugin: paired minor bump, verify build + E2E (generated CSS changes). |
tsx | ^4.21.0 / 4.23.13 | 4.23.15 | A | Low | Two patches, in range. Load-bearing for npm test (node --import tsx). |
typescript | ^5 / 5.9.3 | 7.0.2 | C | High if touched | 5.9.3 = latest 5.x; ^5 correctly excludes the v7 rewrite. Never chase. |
postcss (override) | ^8.5.10 / 8.5.14 | 8.5.28 | A (refresh) | Low | Override range already allows the fixed 8.5.28; installed 8.5.14 is flagged HIGH (see Security). Lockfile refresh only; keep the override (see §Overrides). |
Frontend detailed notes
- Next 16.3.3 → 16.3.5:
latest = 16.3.5, so this is a same-major patch, not a framework migration. The repo already lives on the v16 line (agent-rules block, turbopack root,output: standalone). Take it, witheslint-config-nextlockstep (exact–exact pairing preserved). Do not look at16.4.0-canary. - React 19.2 → 19.3: minor,
react/react-dom/@types/*move as one unit.@tanstack/react-query@5peers on^18 || ^19— unaffected. - Tailwind v4 minors (4.1.17 → 4.3.3): core +
@tailwindcss/postcssmove together; thepostcssoverride (^8.5.10) stays and continues to dedupe the singlepostcss@8copy shared with Next. Verify with a production build (CSS diff is the risk, not types). - Broad major ranges (
^4,^5,^9,^19,^20): assessed, no narrowing recommended. Reproducibility is enforced by the frozen lockfile (npm ciin CI/Docker); narrowing would only add edit churn while reducing automatic uptake of compatible fixes. The two that matter (next,eslint-config-next) are already exact. @dnd-kit/*: the only "sortable" references in code are a localAdminPvTrackerSortableHeadcomponent (custom sort headers, no library) and an HTMLdraggableattribute. NoDndContext/SortableContext/drag sensors anywhere. Safe removal candidate — one PR deleting all three deps after build+lint+test proof.- Whop embeds: both at latest (
1.2.0), versions synchronized, used by the support screen. High-risk surface, zero update available anyway — untouched by definition.
Overrides
Backend
| Override | Chain (via npm ls) | Original reason (inferred) | Still required? |
|---|---|---|---|
follow-redirects@^1.16.0 | axios@1.16.0 → follow-redirects@1.16.0 | Pin the redirect handler to its fixed release (axios CVE history lives here). | Yes. Installed = latest (1.16.0); axios 1.20 still deps on ^1.16.0. Keep. |
ip-address@^10.2.0 | express-rate-limit@8.5.0 → ip-address@10.2.0 | Force a newer ip-address than the limiter's floor (8.5.0's own dep was 10.1.0). | Yes — and refresh. Range already allows the fixed 10.7.2; lockfile still holds vulnerable 10.2.0. npm update ip-address (no package.json change). Never remove: the limiter's floor is still old. |
lodash@^4.18.1 | discord.js → @discordjs/builders → @sapphire/shapeshift → lodash; also prisma → @prisma/studio-core → @visx/* → lodash | Dedupe/force lodash past historic vulns across two unrelated subtrees. | Yes. Installed = latest (4.18.1), dedupes both chains. Harmless to keep. |
path-to-regexp@^8.4.2 | express@5.2.1 → router@2.2.0 → path-to-regexp@8.4.2 | Enforce v8 (Express 5's router line; v8 fixed the ReDoS-class issues of the v6/v7 line). | Yes. Installed = latest. Removing risks a nested v6/v7 reappearing under some future router bump. |
qs@^6.15.1 | express@5.2.1 → qs and → body-parser → qs | Force qs past its DoS advisories (express's own floor is only ^6.14.0). | Yes — and refresh. Range allows fixed 6.16.0; lockfile holds flagged 6.15.1. Same lockfile-only refresh as ip-address. |
Frontend
| Override | Chain | Original reason (inferred) | Still required? |
|---|---|---|---|
postcss@^8.5.10 | @tailwindcss/postcss@4.1.17 → postcss@8.5.14 and next@16.3.3 → postcss@8.5.14 (deduped single copy) | Force a single, modern PostCSS 8 under both Tailwind v4 and Next (Tailwind v4 had peer churn around PostCSS versions). | Yes — and refresh. Range allows fixed 8.5.28; lockfile holds flagged 8.5.14. Keep the override permanently (it guarantees the dedupe); just refresh the lockfile. |
Net: no override can be removed. Three (ip-address, qs, postcss) additionally need a lockfile refresh because their ranges already cover the security-fixed versions.
Potentially unused/redundant dependencies
All verified by import grep (word-boundary, src/scripts/tests/surfaces/shared, generated Prisma client excluded). "Unused" below means zero static and no config/CLI/dynamic reference found; removal still requires the stated proof.
Backend
@noble/ciphers(D) — nonoblereference anywhere insrc/scripts. AES-GCM needs are served bynode:crypto(src/utils/encryption.ts, TIN helper insrc/utils/tax/tin.ts). Likely added speculatively. Proof needed before removal:npm why @noble/ciphers, full test suite +tscafter removal, confirm no dynamicrequire('…ciphers…').date-fns(D) — nodate-fnsimport anywhere. Date handling is nativeDate+iso8601-duration(YouTube durations). Proof: same as above.@types/ws(D) — nowsimport anywhere;wsexists only transitively (discord.js → @discordjs/ws → ws@8.19.0).@types/wstherefore types nothing we import. Proof: remove +tscclean.@types/qrcode/@types/speakeasyplacement (note, not removal) — both genuinely used (totp.ts,totpAuth.ts), but declared underdependenciesinstead ofdevDependencies. Cosmetically wrong, functionally harmless. Do not churn the install graph to fix; correct only if those lines are edited for another reason.pgis correctly placed — never imported directly, but it is the runtime peer behindPrismaPg(src/utils/prismaClient.ts). Not unused. Keep with@types/pg.
Frontend
@dnd-kit/core,@dnd-kit/sortable,@dnd-kit/utilities(D) — zero imports repo-wide (verified across.ts/.tsx/.mjs, excludingnode_modules/.next/package-lock). The only "sortable" hits are a hand-rolledAdminPvTrackerSortableHeadand an HTML attribute. Remove all three in one PR afternext build+eslint+npm testproof.playwrightstandalone (D) — version-fine (1.63.0, matches@playwright/test), but redundant: CI'snpx playwright install --with-deps chromiumbin is also provided by@playwright/test. Proof: drop it,npm ci, re-run the Chromium install + campaign E2E in CI; restore if the bin is missing.- Correctly-kept pairs:
clsx+tailwind-merge(both used inshared/lib/utils.ts);@vercel/analytics+@vercel/speed-insights(both rendered inTelemetry.tsx);jsdom+@types/jsdom(5+ test files importJSDOMdirectly;tsxpowersnpm test); both Whop embed packages (support screen imports).
Security findings
Only actionable items. (npm audit raw totals: backend 18, frontend 12 — mostly transitive/dev-only noise, not reproduced here.)
Backend — fix now via in-range refresh (Batch 1)
| Finding | Exposure | Fix |
|---|---|---|
axios@1.16.0 HIGH — prototype-pollution pair (≤1.17.0) | Runtime: Tax1099 HTTP client. Moderate severity advisories, network-reachable parsing. | Refresh to 1.20.0 (in ^1.16.0 range). Simultaneously moves form-data into fixed ^4.0.6 range (kills the HIGH form-data CRLF advisory, same chain). |
qs@6.15.1 MODERATE — DoS pair (≤6.15.3) | Runtime: every Express query parse. Override ^6.15.1 already allows 6.16.0. | Lockfile refresh to 6.16.0. |
ip-address@10.2.0 HIGH — SSRF/trust-boundary bypass pair (≤10.3.0) | Runtime: express-rate-limit IP handling (trust-boundary relevant). Override ^10.2.0 already allows 10.7.2. | Lockfile refresh to 10.7.2. |
resend@6.8.0 → svix@1.84.1 → uuid@10.0.0 MODERATE | Build/runtime email path (non-critical). resend@6.28.1 drops svix entirely. | Bump within ^6.8.0 (Batch 2 with verification). |
Backend — needs a parent bump (Batch 2)
| Finding | Exposure | Fix |
|---|---|---|
ws@8.19.0 HIGH + undici HIGH (via discord.js → @discordjs/ws) | Bot gateway connection (long-lived WS). Not API-request path, but persistent. | discord.js@14.27.0 minor bump; re-run npm ls ws undici. If ws remains in flagged range, add a ws override as a separate deliberate decision (do not sneak it into the bump PR). |
body-parser@2.2.2 LOW (limit-value DoS) | Transitive via Express; only triggers on explicitly misconfigured limits. | No action: fixed upstream only in a future Express/body-parser release. Watch item. |
Backend — explicitly not actionable
prisma → mysql2 / deepmerge-tsHIGHs: dev-only (prismaCLI + Studio), never in the production image path (dist/+ generated client). Fix offered isprisma@6.19.3— a downgrade-major the auditor suggests blindly; ignore. Resolves when Prisma 8 goes stable and we adopt it deliberately.@discordjs/rest → undiciMODERATE: same discord subtree as above; covered by the discord bump.
Frontend — fix now (Batches 1/3)
| Finding | Exposure | Fix |
|---|---|---|
postcss@8.5.14 HIGH (range ≤8.5.22) | Build-time (CSS processing in next build + Tailwind plugin). Not runtime user input, but trivially fixable. | Lockfile refresh to 8.5.28 (in override range). Verify next build. |
Frontend — covered by proposed bumps, no separate action
nanoid,flatted,minimatch,picomatch,brace-expansion,browserslist,js-yaml,ajv,baseline-browser-mapping,@babel/core,@humanfs/node: all transitive undereslint/next/tailwindsubtrees;fixAvailable: truevia thenext@16.3.5+ Tailwind4.3.3bumps. Re-runnpm auditafter Batch 3 and confirm attrition rather than chasing each leaf.
Recommended upgrade batches
All commands are illustrative — do not run the mutating ones until a ticket explicitly authorizes them. Read-only verification (npm ls, npm audit, npm view) may run anytime.
Batch 1 — trivial/low-risk maintenance (lockfile refreshes, minimal or no package.json edits)
Scope (backend): axios→1.20.0, cors→2.8.6, express-rate-limit→8.7.0 + ip-address→10.7.2, helmet→8.3.0, iso8601-duration→2.1.4, zod→4.3.5→4.6.5, @types/node→24.13.x, qs→6.16.0, form-data (via axios). Frontend: postcss→8.5.28, tsx→4.23.15 (+ @testing-library/user-event resolving to 14.6.7 if not already). Method: npm update <name> per package (respects existing ranges; not npm install <name>@latest, which would breach exact pins and jump majors).
Backend verification: npm run build · npm test (offline) · npm run test:authority if auth-adjacent files touched (they aren't, but cheap) · API startup smoke (measure:api-startup or boot + /api/health) · npm ls qs ip-address follow-redirects confirms single deduped copies · npm audit confirms the four advisories cleared. Frontend verification: npm run build · npm run lint · npm test · npm run test:charts.
Batch 2 — backend ecosystem (one PR, behavior-adjacent minors)
Scope: discord.js@14.27.0, resend@6.28.1. Why together: both are routine minors with small but real behavior surfaces (gateway connection; email provider SDK), and both close audit chains (ws/undici via discord; svix/uuid via resend). Kept separate from Batch 1 so a behavior regression is attributable. Verification: everything in Batch 1 backend plus npm run test:integration (DB) · campaign-funding suite (test:campaign-funding) for the resend paths · bot login/startup check · npm ls ws undici svix to confirm chain movement · Prisma generate untouched (no schema change expected).
Batch 3 — frontend ecosystem (one PR, UI/tooling minors + patch framework)
Scope: next 16.3.3→16.3.5 + eslint-config-next 16.3.3→16.3.5 (lockstep exacts) · react + react-dom + @types/react + @types/react-dom → 19.3.0 line · tailwindcss + @tailwindcss/postcss → 4.3.3 line · tailwind-merge→3.7.0, @tanstack/react-query→5.103.2, react-hot-toast→2.6.1. Why together: coupled framework/CSS/rendering behavior; a visual or hydration regression must be attributable to this PR alone — hence no Whop/dnd-kit/lucide changes inside it. Verification: npm run build · npm run lint · npm test · test:charts · campaign E2E (test:e2e:campaigns, Playwright/Chromium) · npm audit to confirm transitive attrition.
Batch 4 — framework/toolchain
Nothing justified. Intentionally empty. TypeScript stays on 5.9.3 (v7 is a rewrite generation), ESLint stays on 9 (v10 is a new major), ts-node stays (no tangible benefit to migrating the script runtime). Revisit post-MVP.
Batch 5 — high-risk integrations (tickets only, no action in this audit)
- Whop general SDK
1.1.2 → 1.1.5— own ticket: sandbox invoice/payout/transfer dry-runs, webhook signature verification, auth + funding + campaign-funding + authority suites, then staged live verification. Never bundled with maintenance. - Whop invoice-alias consolidation (
1.0.14→ unified) — investigation ticket first (does thecompany_idcontract still require the old client?), implementation only with finance-owner sign-off. pg 8.16.3 + @types/pg 8.16.0 → 8.23.x— own small ticket: startup measurement, pool/timeout behavior review (prismaClient.tsnotes), integration suite. Could ride alongside Batch 2's verification but ships as its own PR for revertability.- Prisma 8 (when stable) — adopt only on a stable
8.xwith a documented need; full migration replay per repo safety rules.
Deferred upgrades
| Upgrade | Why it waits |
|---|---|
Prisma 8 (8.0.0-rc.x) | Prerelease. Current 7.10.0 is the stable line for all three packages; zero material benefit; generator/startup behavior recently stabilized. |
TypeScript 7 (+ frontend ^5 staying) | New toolchain generation (Go rewrite). Backend 5.9.3 is latest 5.x; no language feature need. Post-MVP at earliest. |
dotenv 17 → 18 | New major; usage is side-effect-only config loading. No benefit, nonzero behavior risk. |
AWS SDK 3.1041 → 3.1136 | Pure churn (~95 releases); S3 usage is elementary and working. Refresh only if a CVE touches the exact APIs used. |
lucide-react 0.555 → 1.x | Major with 184 importing files; icon rename/removal risk needs visual review. Follow-up ticket (B), post-MVP. |
framer-motion 12 → 13 | New major (v13); animation behavior risk across marketing surfaces. Follow-up ticket (B), post-MVP. |
jsdom 26 → 30 (+ @types/jsdom 21 → 30) | Two-major jump in test infra; current setup works on Node 22. Paired ticket (B), post-MVP. |
eslint 9 → 10 | New major with config/rule churn; v9 current and supported by eslint-config-next@16. Ticket (B), post-MVP. |
@types/node 20 → 22/24/26 (frontend), 24 → 26 (backend) | No missing-API pain; frozen lockfile already reproducible. Jumping types majors buys nothing pre-MVP. |
speakeasy replacement | Upstream stagnant but functional and pinned at its only release line; replacing TOTP is a migration project, not maintenance. Watch only. |
body-parser LOW advisory | Fix exists only in a future Express-line release; not exploitable under our configuration. Watch npm audit after each Express bump. |
Prisma dev-only advisories (mysql2, deepmerge-ts) | Never shipped to production; auditor-suggested "fix" is a downgrade-major. Ignore until Prisma 8 adoption. |
@dnd-kit/* version questions | Moot — packages are unused; the decision is remove vs keep, not which version. |
ts-node → tsx migration | No tangible benefit: scripts run fine, production uses compiled output. Explicitly not proposed. |
Final recommendation
1. Do now (Batch 1 — lockfile-only refreshes, one PR per repo): Backend: axios, cors, express-rate-limit + ip-address, helmet, iso8601-duration, zod, @types/node, qs — clears 3 real advisories plus hardening. Frontend: postcss, tsx — clears the HIGH build-time advisory. Verification per Batch 1.
2. Open follow-up tickets for (in priority order): a. Batch 2 PR — discord.js@14.27 + resend@6.28 (closes remaining runtime advisory chains; needs bot + email verification). b. Batch 3 PR — next@16.3.5 + eslint-config-next lockstep, React 19.3 quartet, Tailwind 4.3.3 pair, tailwind-merge, react-query, hot-toast (needs build + lint + unit + chart + Playwright E2E). c. Removal PR — @dnd-kit/* ×3 (frontend) after proof; separately @noble/ciphers + date-fns + @types/ws (backend) after npm why + tsc + suite proof. Plus a verdict on standalone playwright redundancy. d. pg + @types/pg → 8.23.x small ticket with startup/pool verification. e. Whop general-SDK 1.1.2 → 1.1.5 ticket (sandbox → staged-live verification; finance-owner awareness). Invoice-alias consolidation stays an investigation, defaulting to "leave." f. Post-MVP backlog: lucide 1.x, framer-motion 13, jsdom 30 pair, eslint 10, Prisma 8 (stable only), TS 7 (far future).
3. Deliberately leave alone: Prisma trio (7.10.0), @whop/sdk-invoices alias (1.0.14 + API date), Express 5 stack, jsonwebtoken, pdf-lib/qrcode/speakeasy, cookie-parser, dotenv 17, AWS SDK pair alignment, ts-node, TypeScript 5, all five backend overrides + the postcss override, Whop embedded components, @vercel/*, clsx, lenis, @playwright/test version, @testing-library/* versions, @types/node majors, and every Deferred item above. The boring strategy is the correct one: lockfile refreshes for security, paired minors for hygiene, tickets for everything with a blast radius.