mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
fix: cap the size of a synced settings blob
This commit is contained in:
@@ -12,6 +12,9 @@ import { MAX_ACCOUNT_SLOTS } from '@/lib/account-utils';
|
||||
import { mergeStoredTemplates } from '@/lib/template-merge';
|
||||
import { mergeStoredSubscriptions } from '@/lib/calendar-subscription-sync';
|
||||
|
||||
/** Largest settings blob a client may store (templates and signatures included). */
|
||||
const SETTINGS_MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function classifyError(error: unknown): { message: string; status: number } {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
const msg = error instanceof Error ? error.message : 'Unknown error';
|
||||
@@ -142,7 +145,16 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { username, serverUrl, settings } = await request.json();
|
||||
// One settings blob per account, read back on every sync: bound it
|
||||
// before parsing, the way the account archive is.
|
||||
if (Number(request.headers.get('content-length') ?? 0) > SETTINGS_MAX_BYTES) {
|
||||
return NextResponse.json({ error: 'Settings are too large' }, { status: 413 });
|
||||
}
|
||||
const text = await request.text();
|
||||
if (text.length > SETTINGS_MAX_BYTES) {
|
||||
return NextResponse.json({ error: 'Settings are too large' }, { status: 413 });
|
||||
}
|
||||
const { username, serverUrl, settings } = JSON.parse(text);
|
||||
if (!username || !serverUrl || !settings) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
@@ -128,4 +128,20 @@ describe('settings route keys on the account the credential belongs to', () => {
|
||||
}));
|
||||
expect([...store.keys()]).toEqual([`alice@example.org|${SERVER}`]);
|
||||
});
|
||||
|
||||
it('refuses a settings blob over the size cap before parsing it', async () => {
|
||||
// One blob per account, re-read on every sync: it was accepted at any size.
|
||||
await signIn({ username: 'alice@example.org' });
|
||||
const { POST } = await import('@/app/api/settings/route');
|
||||
const big = JSON.stringify({ username: 'alice@example.org', serverUrl: SERVER, settings: { signature: 'x'.repeat(2 * 1024 * 1024 + 10) } });
|
||||
const res = await POST(new NextRequest('https://webmail.example/api/settings', { method: 'POST', headers: SAME_ORIGIN, body: big }));
|
||||
expect(res.status).toBe(413);
|
||||
expect(store.size).toBe(0);
|
||||
const claimed = await POST(new NextRequest('https://webmail.example/api/settings', {
|
||||
method: 'POST',
|
||||
headers: { ...SAME_ORIGIN, 'content-length': String(3 * 1024 * 1024) },
|
||||
body: JSON.stringify({ username: 'alice@example.org', serverUrl: SERVER, settings: { theme: 'dark' } }),
|
||||
}));
|
||||
expect(claimed.status).toBe(413);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user