[2.4.6] - Defense & Speed
Security
- ReDoS — Polynomial Regular Expression Hardening
- Replaced all
/\/+$/trailing-slash regex patterns applied to user-controlled values with linear while-loop equivalents, eliminating O(n²) backtracking risk (CodeQLjs/polynomial-redos). - Affected files:
packages/lib/utils/index.ts(urlForHost),packages/lib/files/upload-validation.ts(domain cleaning),packages/lib/files/filename.ts(slug trimming),app/api/files/route.ts(URL construction). - Replaced
/^-+|-+$/galternation pattern in filename slug generation with pointer-based leading/trailing trim.
- Replaced all
- SSRF — Integration Test Endpoint Input Validation
- Discord server ID (
serverId) now validated against snowflake format (/^\d{17,20}$/) before being interpolated into the Discord API URL (CodeQLjs/request-forgery). - Cloudflare account ID (
accountId) now validated against 32-character lowercase hex format before URL construction. - Both integrations return a clear validation error message rather than making an outbound request with unsanitised input.
- Discord server ID (
- Miscellaneous CodeQL Findings Resolved
- Incomplete URL substring sanitisation (alerts 6, 7) — hardened URL host checks.
- Shell command built from environment values (alert 16) — environment input sanitised before shell interpolation.
- Use of externally-controlled format string (alert 15) — format string construction tightened.
- Additional SSRF alerts (10–13, 17–19) addressed across various API routes.
Changed
- Status Page Integration Removed
- Removed the Kener / Uptime Kuma dynamic status integration entirely. The polling logic,
/api/statusroute (now returns 404), admin settings panel, and integration test handler have all been removed. StatusIndicatorin the site footer is now a lightweight static link to emberlystat.us — no external API calls, no runtime failures, no "Status unknown" states.KENER_API_KEY,KENER_BASE_URL,UPTIME_KUMA_BASE_URL, andUPTIME_KUMA_SLUGenvironment variables are no longer used and can be removed.
- Removed the Kener / Uptime Kuma dynamic status integration entirely. The polling logic,
Added
- CodeQL Workflow — Automated static analysis via GitHub Actions (
/.github/workflows/codeql.yml) now runs on push and pull request for continuous security scanning. - SECURITY.md — Added security policy documenting responsible disclosure process and supported versions.
- License Scan — Added license scan report and status badge to repository.
Performance
- VirusTotal scan moved off the critical path — VT hash lookups previously blocked the upload response for 5-10s on non-media files. The scan now runs in the background after the file is stored and the response is returned. Files detected as malicious are automatically quarantined (removed from storage, marked private) and logged.
- Stripe subscription sync debounce survives hot-reloads — The per-user 5-minute Stripe sync cache (
stripeSyncCache) was stored as a module-level variable, causing it to reset on every Next.js hot-reload in development and trigger a live Stripe API call on every upload. Moved toglobalThisso the TTL is respected across reloads. - S3 provider singleton persisted across hot-reloads — Storage provider was re-initialized on every request in development for the same reason. Also moved to
globalThis, eliminating redundant initialization logs and the associated config DB read per request. - File buffer, storage provider, and filename generation parallelized —
arrayBuffer(),getStorageProvider(), andgetUniqueFilename()now run concurrently withPromise.allinstead of sequentially, removing 1-2 unnecessary round-trips from the critical path. bcrypt.hashmoved outside the DB transaction — Password hashing was running insideprisma.$transaction, blocking the database connection during a CPU-intensive operation. The hash is now computed before the transaction opens.
Fixed
- Sitemap — Marked sitemap route as dynamic to prevent build-time errors when database is unavailable during static export.
- TypeScript — Logger argument types in
sync-buckets.ts—BucketSyncStatswas passed directly as alogger.infocontext argument (expectsRecord<string, unknown>); fixed by spreading with{ ...stats }. Twologger.warncalls passed rawErrorobjects where a context object is expected; fixed by passing{ error: String(err) }. - TypeScript —
PaginationData.pagesproperty inuser-list.tsx— Three references used.pageson thePaginationDatatype returned byuseUserManagement, which defines the property aspageCount. Corrected to.pageCount. - TypeScript —
emailVerifiedtype mismatch inapp/api/files/route.ts— Prisma returnsemailVerified: Date | nullbutAuthenticatedUserexpectsboolean. The squad-owner user object is now spread withemailVerified: ownerUser.emailVerified !== nullbefore assignment. - ESLint —
require()imports in migration scripts —scripts/migrate-config.jsandscripts/hash-file-passwords.jsused CommonJSrequire(), which is forbidden by the project's ESLint config. Both converted to ESM (import) and renamed to.mjs. - ESLint — Empty interfaces in
react-jsx-compat.d.ts— Sixinterface X extends Y {}declarations with no added members replaced withtype X = Yaliases, satisfying@typescript-eslint/no-empty-object-type. - ESLint — Unused variables across multiple files — Removed or prefixed unused imports and variables flagged by
@typescript-eslint/no-unused-vars:Copyinbucket/page.tsx,Userinblog/page.tsx,Share2/Userinblog/[slug]/page.tsx,Footer/getConfig/providedPasswordin[filename]/page.tsx,toastinsquads/client.tsx,codeSentstate inalpha-migration/page.tsx, anduserNameparameter indashboard/client.tsx. - ESLint —
let→constintheme-initializer.tsx—cssVariableswas declared withletbut never reassigned; declaration moved to the single assignment site asconst. - Integration test fetch timeouts —
testStripe,testResend,testCloudflare,testDiscord(bot + webhook), andtestGitHubhad no request timeout, allowing the admin integration-test endpoint to hang indefinitely if a provider didn't respond. All now useAbortSignal.timeout(8000), consistent with the existing Vultr handler. - Quarantine failure logging —
Promise.allSettledresults in the VirusTotal quarantine path were silently discarded. Storage delete or DB update failures now log an error with the file ID so they can be investigated. - Session cache
emailVerifiedbackfill — Redis-cached sessions written before theemailVerified: booleanfield was added would deserialize withundefined, breaking the upload auth contract. A coerce-on-read now setsfalsefor any entry missing the field. - Legacy
kener/uptimeKumaconfig keys stripped from admin response — The integration config schema uses.passthrough(), so old DB records containing stale Kener or Uptime Kuma keys would survivedeepMergeand be returned to SUPERADMIN viaGET /api/settings.maskSecretsForAdminnow explicitly deletes both keys before returning. - Empty filename slug fallback — Filenames composed entirely of non-ASCII/symbol characters would reduce to an empty slug after sanitization, producing broken storage paths. A
nanoid(6)fallback is now used when the slug is empty. storageQuotaMB = 0admin override ignored —if (!baseQuotaMB)treated an explicit zero-quota override as "unset", falling through to plan-based quota. Changed to== nullchecks so0is honored as a deliberate override.- Storage-bucket subscription precedence missing from Stripe re-check path — After a successful Stripe sync,
getPlanLimitsreturned the latest active subscription without first checking for astorage-bucket-*subscription. Users with both sub types active could receive non-unlimited limits for that request. The re-check now mirrors the original early-exit logic. proxy.ts—BASE_URL/MAIN_HOSTrecomputed per request —process.env.NEXT_PUBLIC_BASE_URLwas parsed withnew URL()on every invocation. Both the base URL string and its hostname are now computed once at module load time.proxy.ts— Duplicate media-rewrite block eliminated — Video/audio range-request detection logic appeared twice (before and after the auth checks). Unified into a single block that runs beforegetToken(), so media requests skip JWT verification entirely.proxy.ts—VIDEO_EXTENSIONSarray →Set—VIDEO_EXTENSIONS.includes(ext)was an O(n) linear scan on every file URL request. Converted to a module-levelSetfor O(1) lookup.proxy.ts— Trailing-slash strip regex replaced —pathname.replace(/\/$/, '')replaced withendsWith('/')+slice, consistent with the ReDoS hardening applied elsewhere.proxy.ts—getClientIPdouble-read ofx-forwarded-for— The function readx-forwarded-fora second time at the fallback return if the header was alreadynullat the top. Simplified to a single read with null-coalescing chain.proxy.ts— Noisyconsole.logremoved from hot paths — Debug logs for unverified-user blocks and password-breach redirects fired on every affected request, adding synchronous I/O overhead in the middleware layer.
What's Changed
- Dev by @NodeByteLTD in #85
- fix(sitemap): mark as dynamic by @CodeMeAPixel in #86
- Dev by @CodeMeAPixel in #87
- Dev by @CodeMeAPixel in #88
- fix(sdlc): security fixes by @CodeMeAPixel in #89
- Potential fix for code scanning alert no. 18: Server-side request forgery by @CodeMeAPixel in #90
- Potential fix for code scanning alert no. 11: Server-side request forgery by @CodeMeAPixel in #92
- Potential fix for code scanning alert no. 17: Server-side request forgery by @CodeMeAPixel in #91
- Potential fix for code scanning alert no. 17: Server-side request forgery by @CodeMeAPixel in #93
- Potential fix for code scanning alert no. 15: Use of externally-controlled format string by @CodeMeAPixel in #94
- Potential fix for code scanning alert no. 16: Shell command built from environment values by @CodeMeAPixel in #95
- Potential fix for code scanning alert no. 5: Replacement of a substring with itself by @CodeMeAPixel in #96
- Potential fix for code scanning alert no. 6: Incomplete URL substring sanitization by @CodeMeAPixel in #97
- Potential fix for code scanning alert no. 7: Incomplete URL substring sanitization by @CodeMeAPixel in #98
- Potential fix for code scanning alert no. 10: Server-side request forgery by @CodeMeAPixel in #99
- Potential fix for code scanning alert no. 19: Server-side request forgery by @CodeMeAPixel in #100
- chore(sync): master into dev by @CodeMeAPixel in #101
- Add license scan report and status by @fossabot in #102
- Sync by @CodeMeAPixel in #103
- Dev by @CodeMeAPixel in #104
- Dev by @CodeMeAPixel in #105
- Dev by @CodeMeAPixel in #106
New Contributors
Full Changelog: v2.4.5...v2.4.6