From a0c8eecbd16d2b730530a726f82dedf95dd15d7c Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 23 Jun 2026 07:34:43 -0300 Subject: [PATCH] fix(db): make db-backup import size cap configurable (#4719) (#4757) Co-authored-by: Diego Rodrigues de Sa e Souza --- CHANGELOG.md | 4 +++ src/app/api/db-backups/import/route.ts | 34 ++++++++++++++++++++-- tests/unit/db-import-max-size-4719.test.ts | 34 ++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 tests/unit/db-import-max-size-4719.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c14928cd5..ec49834698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ _In development — bullets added per PR; finalized at release._ +### 🔧 Bug Fixes + +- **db-backups**: make the database-import size cap configurable via `OMNIROUTE_DB_IMPORT_MAX_MB` (default 100 MB, 4 GB ceiling) so backups larger than 100 MB can be restored; error message now points to the env var and to VACUUM (#4719). + --- ## [3.8.34] — 2026-06-23 diff --git a/src/app/api/db-backups/import/route.ts b/src/app/api/db-backups/import/route.ts index 12ee0c678a..b118e83d7b 100644 --- a/src/app/api/db-backups/import/route.ts +++ b/src/app/api/db-backups/import/route.ts @@ -11,7 +11,30 @@ import { getSettings } from "@/lib/db/settings"; import { setSystemPromptConfig } from "@omniroute/open-sse/services/systemPrompt.ts"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -const MAX_UPLOAD_SIZE = 100 * 1024 * 1024; // 100 MB +const DEFAULT_MAX_UPLOAD_MB = 100; +// Hard ceiling so a misconfigured/hostile value can't ask the route to buffer an +// unbounded file into memory. +const MAX_UPLOAD_MB_CEILING = 4096; + +/** + * Resolve the maximum accepted backup size (bytes) from the environment. + * + * Real databases bloat well past the historical 100 MB cap (#4719 — a 156 MB file that + * VACUUMs down to 5 MB still can't be re-imported), so the limit is now operator-tunable + * via `OMNIROUTE_DB_IMPORT_MAX_MB`. Invalid / out-of-range values fall back to the 100 MB + * default and are clamped to a 4 GB ceiling. + */ +export function resolveMaxUploadSizeBytes( + env: NodeJS.ProcessEnv = process.env +): number { + const raw = env.OMNIROUTE_DB_IMPORT_MAX_MB; + const parsed = raw === undefined ? NaN : Number(raw); + const mb = + Number.isFinite(parsed) && parsed >= 1 + ? Math.min(Math.floor(parsed), MAX_UPLOAD_MB_CEILING) + : DEFAULT_MAX_UPLOAD_MB; + return mb * 1024 * 1024; +} // Required tables that must exist in a valid OmniRoute database const REQUIRED_TABLES = ["provider_connections", "provider_nodes", "combos", "api_keys"]; @@ -68,10 +91,15 @@ export async function POST(request: Request) { } // Validate file size + const maxUploadSize = resolveMaxUploadSizeBytes(); const fileSize = fileBuffer.length; - if (fileSize > MAX_UPLOAD_SIZE) { + if (fileSize > maxUploadSize) { return NextResponse.json( - { error: `File too large. Maximum allowed size is ${MAX_UPLOAD_SIZE / (1024 * 1024)} MB.` }, + { + error: + `File too large. Maximum allowed size is ${maxUploadSize / (1024 * 1024)} MB. ` + + `Set OMNIROUTE_DB_IMPORT_MAX_MB to raise it, or VACUUM the database before exporting.`, + }, { status: 400 } ); } diff --git a/tests/unit/db-import-max-size-4719.test.ts b/tests/unit/db-import-max-size-4719.test.ts new file mode 100644 index 0000000000..9f15f4a022 --- /dev/null +++ b/tests/unit/db-import-max-size-4719.test.ts @@ -0,0 +1,34 @@ +// #4719 — DB backup import failed for databases larger than the hard-coded 100 MB cap. +// Real databases bloat (a 156 MB file VACUUMs down to 5 MB) but still couldn't be +// re-imported. The cap is now operator-tunable via OMNIROUTE_DB_IMPORT_MAX_MB, with the +// historical 100 MB as default and a 4 GB ceiling for invalid/hostile values. +import test from "node:test"; +import assert from "node:assert/strict"; + +const { resolveMaxUploadSizeBytes } = await import( + "../../src/app/api/db-backups/import/route.ts" +); + +const MB = 1024 * 1024; + +test("defaults to 100 MB when env is unset (#4719)", () => { + assert.equal(resolveMaxUploadSizeBytes({}), 100 * MB); +}); + +test("honors OMNIROUTE_DB_IMPORT_MAX_MB (#4719)", () => { + assert.equal(resolveMaxUploadSizeBytes({ OMNIROUTE_DB_IMPORT_MAX_MB: "256" }), 256 * MB); +}); + +test("clamps absurd values to the 4 GB ceiling (#4719)", () => { + assert.equal(resolveMaxUploadSizeBytes({ OMNIROUTE_DB_IMPORT_MAX_MB: "999999" }), 4096 * MB); +}); + +test("falls back to default for invalid / out-of-range values (#4719)", () => { + assert.equal(resolveMaxUploadSizeBytes({ OMNIROUTE_DB_IMPORT_MAX_MB: "abc" }), 100 * MB); + assert.equal(resolveMaxUploadSizeBytes({ OMNIROUTE_DB_IMPORT_MAX_MB: "0" }), 100 * MB); + assert.equal(resolveMaxUploadSizeBytes({ OMNIROUTE_DB_IMPORT_MAX_MB: "-5" }), 100 * MB); +}); + +test("floors fractional MB values (#4719)", () => { + assert.equal(resolveMaxUploadSizeBytes({ OMNIROUTE_DB_IMPORT_MAX_MB: "150.9" }), 150 * MB); +});