Skip to content
Merged

Dev #106

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Emberly is an open source platform for modern file storage, sharing, and identity verification. Build your digital presence with powerful tools for teams and individuals.

[![Build Checks](https://github.com/EmberlyOSS/Emberly/actions/workflows/build.yml/badge.svg)](https://github.com/EmberlyOSS/Emberly/actions/workflows/build.yml) [![CodeQL Advanced](https://github.com/EmberlyOSS/Emberly/actions/workflows/codeql.yml/badge.svg)](https://github.com/EmberlyOSS/Emberly/actions/workflows/codeql.yml) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/EmberlyOSS/Emberly?utm_source=oss&utm_medium=github&utm_campaign=EmberlyOSS%2FEmberly&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)
[![Build Checks](https://github.com/EmberlyOSS/Emberly/actions/workflows/build.yml/badge.svg)](https://github.com/EmberlyOSS/Emberly/actions/workflows/build.yml) [![CodeQL Advanced](https://github.com/EmberlyOSS/Emberly/actions/workflows/codeql.yml/badge.svg)](https://github.com/EmberlyOSS/Emberly/actions/workflows/codeql.yml) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/EmberlyOSS/Emberly?utm_source=oss&utm_medium=github&utm_campaign=EmberlyOSS%2FEmberly&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FEmberlyOSS%2FEmberly.svg?type=shield&issueType=security)](https://app.fossa.com/projects/git%2Bgithub.com%2FEmberlyOSS%2FEmberly?ref=badge_shield&issueType=security)

## Features

Expand Down Expand Up @@ -47,7 +47,7 @@ Emberly is an open source platform for modern file storage, sharing, and identit
- Promo code management with configurable discounts
- User management dashboard
- Application review queue with multi stage triage
- Service status monitoring via Kener integration
- Service status page link ([emberlystat.us](https://emberlystat.us))
- Analytics and usage reporting

## Quick Start
Expand Down Expand Up @@ -105,7 +105,6 @@ The application will be available at http://localhost:3000.
**Infrastructure & Services**

- [S3 compatible storage](https://aws.amazon.com/s3/) - File storage
- [Kener](https://kener.ing/) - Status page monitoring
- [Next.js Auth](https://next-auth.js.org/) - Authentication
- [Sentry](https://sentry.io/) - Error tracking

Expand Down Expand Up @@ -176,15 +175,13 @@ Get help and connect with the community:

This project is licensed under the GNU Affero General Public License v3 (AGPL-3.0). See the [LICENSE](LICENSE) file for details.


## Code of Conduct

This project adheres to the Contributor Covenant Code of Conduct. By participating, you agree to uphold this code. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for the full text.

## Acknowledgments

Thank you to all [contributors](https://github.com/EmberlyOSS/Emberly/graphs/contributors) who have helped make Emberly possible. We also appreciate the open source projects and communities that make this platform possible.

Thank you to all [contributors](https://github.com/EmberlyOSS/Emberly/graphs/contributors) who have helped make Emberly possible. We also appreciate the [open source projects and communities](https://github.com/EmberlyOSS/Emberly/network/dependencies) that make Emberly possible.

<a href="https://github.com/EmberlyOSS/Emberly/graphs/contributors">
<img src="https://contrib.rocks/image?repo=EmberlyOSS/Emberly" alt="Contributors" />
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,69 @@ All notable changes to this project will be documented in this file.

The format is based on "Keep a Changelog" and follows [Semantic Versioning](https://semver.org/).

## [2.4.6] - 2026-06-13

### 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 (CodeQL `js/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 `/^-+|-+$/g` alternation pattern in filename slug generation with pointer-based leading/trailing trim.
- **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 (CodeQL `js/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.
- **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/status` route (now returns 404), admin settings panel, and integration test handler have all been removed.
- `StatusIndicator` in the site footer is now a lightweight static link to [emberlystat.us](https://emberlystat.us) — no external API calls, no runtime failures, no "Status unknown" states.
- `KENER_API_KEY`, `KENER_BASE_URL`, `UPTIME_KUMA_BASE_URL`, and `UPTIME_KUMA_SLUG` environment variables are no longer used and can be removed.

### 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 to `globalThis` so 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()`, and `getUniqueFilename()` now run concurrently with `Promise.all` instead of sequentially, removing 1-2 unnecessary round-trips from the critical path.
- **`bcrypt.hash` moved outside the DB transaction** — Password hashing was running inside `prisma.$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`** — `BucketSyncStats` was passed directly as a `logger.info` context argument (expects `Record<string, unknown>`); fixed by spreading with `{ ...stats }`. Two `logger.warn` calls passed raw `Error` objects where a context object is expected; fixed by passing `{ error: String(err) }`.
- **TypeScript — `PaginationData.pages` property in `user-list.tsx`** — Three references used `.pages` on the `PaginationData` type returned by `useUserManagement`, which defines the property as `pageCount`. Corrected to `.pageCount`.
- **TypeScript — `emailVerified` type mismatch in `app/api/files/route.ts`** — Prisma returns `emailVerified: Date | null` but `AuthenticatedUser` expects `boolean`. The squad-owner user object is now spread with `emailVerified: ownerUser.emailVerified !== null` before assignment.
- **ESLint — `require()` imports in migration scripts** — `scripts/migrate-config.js` and `scripts/hash-file-passwords.js` used CommonJS `require()`, 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`** — Six `interface X extends Y {}` declarations with no added members replaced with `type X = Y` aliases, 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`: `Copy` in `bucket/page.tsx`, `User` in `blog/page.tsx`, `Share2`/`User` in `blog/[slug]/page.tsx`, `Footer`/`getConfig`/`providedPassword` in `[filename]/page.tsx`, `toast` in `squads/client.tsx`, `codeSent` state in `alpha-migration/page.tsx`, and `userName` parameter in `dashboard/client.tsx`.
- **ESLint — `let` → `const` in `theme-initializer.tsx`** — `cssVariables` was declared with `let` but never reassigned; declaration moved to the single assignment site as `const`.
- **Integration test fetch timeouts** — `testStripe`, `testResend`, `testCloudflare`, `testDiscord` (bot + webhook), and `testGitHub` had no request timeout, allowing the admin integration-test endpoint to hang indefinitely if a provider didn't respond. All now use `AbortSignal.timeout(8000)`, consistent with the existing Vultr handler.
- **Quarantine failure logging** — `Promise.allSettled` results 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 `emailVerified` backfill** — Redis-cached sessions written before the `emailVerified: boolean` field was added would deserialize with `undefined`, breaking the upload auth contract. A coerce-on-read now sets `false` for any entry missing the field.
- **Legacy `kener`/`uptimeKuma` config keys stripped from admin response** — The integration config schema uses `.passthrough()`, so old DB records containing stale Kener or Uptime Kuma keys would survive `deepMerge` and be returned to SUPERADMIN via `GET /api/settings`. `maskSecretsForAdmin` now 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 = 0` admin override ignored** — `if (!baseQuotaMB)` treated an explicit zero-quota override as "unset", falling through to plan-based quota. Changed to `== null` checks so `0` is honored as a deliberate override.
- **Storage-bucket subscription precedence missing from Stripe re-check path** — After a successful Stripe sync, `getPlanLimits` returned the latest active subscription without first checking for a `storage-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_HOST` recomputed per request** — `process.env.NEXT_PUBLIC_BASE_URL` was parsed with `new 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 before `getToken()`, so media requests skip JWT verification entirely.
- **`proxy.ts` — `VIDEO_EXTENSIONS` array → `Set`** — `VIDEO_EXTENSIONS.includes(ext)` was an O(n) linear scan on every file URL request. Converted to a module-level `Set` for O(1) lookup.
- **`proxy.ts` — Trailing-slash strip regex replaced** — `pathname.replace(/\/$/, '')` replaced with `endsWith('/')`+`slice`, consistent with the ReDoS hardening applied elsewhere.
- **`proxy.ts` — `getClientIP` double-read of `x-forwarded-for`** — The function read `x-forwarded-for` a second time at the fallback return if the header was already `null` at the top. Simplified to a single read with null-coalescing chain.
- **`proxy.ts` — Noisy `console.log` removed 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.

## [2.4.5] - 2026-06-02

### Added
Expand Down
4 changes: 0 additions & 4 deletions app/(main)/[userUrlId]/[filename]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import { ProtectedFile } from '@/packages/components/file/protected-file'
import { DynamicBackground } from '@/packages/components/layout/dynamic-background'
import { Footer } from '@/packages/components/layout/footer'
import { Icons } from '@/packages/components/shared/icons'
import {
Avatar,
Expand All @@ -21,7 +20,6 @@
import { Input } from '@/packages/components/ui/input'

import { authOptions } from '@/packages/lib/auth'
import { getConfig } from '@/packages/lib/config'
import { prisma } from '@/packages/lib/database/prisma'
import {
buildDirectMediaMetadata,
Expand Down Expand Up @@ -97,13 +95,12 @@

export async function generateMetadata({
params,
searchParams,

Check warning on line 98 in app/(main)/[userUrlId]/[filename]/page.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

'searchParams' is defined but never used. Allowed unused args must match /^_/u
}: FilePageProps): Promise<Metadata> {
const { userUrlId, filename } = await params
const urlPath = `/${userUrlId}/${filename}`
const headersList = await headers()
const session = await getServerSession(authOptions)
const providedPassword = (await searchParams).password as string | undefined

// Find the file
const file = await findFileByUrlPath(userUrlId, filename, {
Expand Down Expand Up @@ -167,7 +164,6 @@
searchParams,
}: FilePageProps) {
const session = await getServerSession(authOptions)
const config = await getConfig()
const { userUrlId, filename } = await params
const urlPath = `/${userUrlId}/${filename}`
const providedPassword = (await searchParams).password as string | undefined
Expand Down
Loading
Loading