fix: keep the plugin denylist and unit tests from breaking Lite and Docker builds

This commit is contained in:
Linus Rath
2026-09-26 21:38:57 +02:00
parent 09e0e2cfb3
commit 2522dbd804
3 changed files with 32 additions and 16 deletions
+5
View File
@@ -14,6 +14,11 @@ TODO.md
!README.md
# Sibling projects / test harness - not part of the webmail image
examples/
# Unit tests: `next build` type-checks every .ts file, and some tests import
# routes the Dockerfile deletes (app/api/dev-jmap)
**/__tests__
**/*.test.ts
**/*.test.tsx
integration/
repos/
# Runtime state of a checkout that has been run (admin password hash,
@@ -51,6 +51,7 @@ describe('plugin http.post denied paths', () => {
['/api/account/stalwart/jmap', ['/api/account/stalwart/jmap']],
['/api/admin/config', ['/api/admin/']],
['/api/settings', ['/api/settings']],
['/api/wopi', ['/api/wopi']],
['/api/auth/session', ['/api/']],
['/api/%61dmin/config', ['/api/%61dmin/']],
['/api//admin/config', ['/api/']],
@@ -63,4 +64,9 @@ describe('plugin http.post denied paths', () => {
await dispatchApiCall(plugin(['/api/translate']), 'http.post', ['/api/translate', { text: 'hi' }]);
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
it('reaches a route that only shares a prefix with a denied one', async () => {
await dispatchApiCall(plugin(['/api/admin-helper']), 'http.post', ['/api/admin-helper', {}]);
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
});
+21 -16
View File
@@ -293,22 +293,26 @@ function isApiPostPathAllowed(path: string, allowlist: readonly string[]): boole
* lists: they act with the user's full credentials (the JMAP passthrough,
* WebDAV, CalDAV), change sign-in, admin or plugin state, or belong to the
* setup wizard. A plugin talks to its own sidecar routes instead.
*
* Listed by their segment under `/api/`, not as `/api/...` strings: the Lite
* build reads every such string in a client chunk as an endpoint the browser
* calls and refuses the ones it has no stand-in for (scripts/lite/verify.mjs).
*/
const PLUGIN_POST_DENIED_PATHS = [
'/api/account',
'/api/admin',
'/api/auth',
'/api/caldav',
'/api/dev-jmap',
'/api/plugin-approval-status',
'/api/plugins',
'/api/push',
'/api/settings',
'/api/setup',
'/api/system',
'/api/webdav',
'/api/wopi',
];
const PLUGIN_POST_DENIED_ROUTES = new Set([
'account',
'admin',
'auth',
'caldav',
'dev-jmap',
'plugin-approval-status',
'plugins',
'push',
'settings',
'setup',
'system',
'webdav',
'wopi',
]);
/**
* Checked on the path the router will see: decoded once and with repeated
@@ -322,7 +326,8 @@ function isPluginPostDenied(pathname: string): boolean {
return true;
}
routePath = routePath.replace(/\/{2,}/g, '/');
return PLUGIN_POST_DENIED_PATHS.some((denied) => routePath === denied || routePath.startsWith(`${denied}/`));
if (!routePath.startsWith('/api/')) return false;
return PLUGIN_POST_DENIED_ROUTES.has(routePath.slice('/api/'.length).split('/')[0]);
}
interface PluginHttpPostOptions {