mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: refuse plugin approval requests while user plugins or their review are turned off (GHSA-qc44-275p-gqw9)
This commit is contained in:
@@ -11,6 +11,11 @@ vi.mock('@/lib/stalwart/auth-context', () => ({
|
||||
}));
|
||||
vi.mock('@/lib/security/same-origin', () => ({ rejectCrossOriginRequest: () => null }));
|
||||
|
||||
let features: Record<string, boolean> = {};
|
||||
vi.mock('@/lib/admin/config-manager', () => ({
|
||||
configManager: { ensureLoaded: async () => {}, getPolicy: () => ({ features }) },
|
||||
}));
|
||||
|
||||
let currentUser = 'alice@example.org';
|
||||
const entries: Array<Record<string, unknown>> = [];
|
||||
const bundles = new Map<string, Uint8Array>();
|
||||
@@ -55,6 +60,7 @@ beforeEach(() => {
|
||||
entries.length = 0;
|
||||
bundles.clear();
|
||||
currentUser = 'alice@example.org';
|
||||
features = { pluginsEnabled: true, pluginsUploadEnabled: true, requirePluginApproval: true };
|
||||
});
|
||||
|
||||
describe('POST /api/plugin-approval-status', () => {
|
||||
@@ -87,6 +93,19 @@ describe('POST /api/plugin-approval-status', () => {
|
||||
expect(entries).toHaveLength(0);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['plugins are off', { pluginsEnabled: false }],
|
||||
['user plugins are off', { pluginsUploadEnabled: false }],
|
||||
['approval is not required', { requirePluginApproval: false }],
|
||||
])('takes no package while %s', async (_label, off) => {
|
||||
features = { ...features, ...off };
|
||||
const res = await submit(await zipOf(MANIFEST));
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(entries).toHaveLength(0);
|
||||
expect(bundles.size).toBe(0);
|
||||
});
|
||||
|
||||
it('limits how many requests one user can have open', async () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
expect((await submit(await zipOf({ ...MANIFEST, id: `p-${i}x` }))).status).toBe(200);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { rejectCrossOriginRequest } from '@/lib/security/same-origin';
|
||||
import { configManager } from '@/lib/admin/config-manager';
|
||||
import { cookies } from 'next/headers';
|
||||
import { decryptSession } from '@/lib/auth/crypto';
|
||||
import { sessionCookieName } from '@/lib/auth/session-cookie';
|
||||
@@ -75,6 +76,15 @@ export async function POST(request: NextRequest) {
|
||||
const username = await resolveUsername();
|
||||
if (!username) return NextResponse.json({ error: 'unauthenticated' }, { status: 401 });
|
||||
|
||||
// Only take packages while users may install their own plugins and
|
||||
// those need review (GHSA-qc44-275p-gqw9): otherwise nobody asked for
|
||||
// the queue, and every request still stores a ZIP on disk.
|
||||
await configManager.ensureLoaded();
|
||||
const { features } = configManager.getPolicy();
|
||||
if (!features.pluginsEnabled || !features.pluginsUploadEnabled || !features.requirePluginApproval) {
|
||||
return NextResponse.json({ error: 'Plugin approval requests are turned off' }, { status: 403 });
|
||||
}
|
||||
|
||||
// The request carries the package itself. The admin reviews the code
|
||||
// and manifest the server read from it, and the approval is pinned to
|
||||
// the hash the server computed, not to anything the client claims.
|
||||
|
||||
Reference in New Issue
Block a user