Release branch for 4.0.0.
This commit is contained in:
jubnl
2026-08-27 13:22:39 +02:00
committed by GitHub
parent e60427f813
commit 77dddd5e8a
3355 changed files with 403744 additions and 67215 deletions
@@ -3,9 +3,9 @@
* the code/data path resolution (both the env-override and default branches).
*/
import { describe, it, expect, afterEach } from 'vitest';
import { isKnownPermission, METHOD_PERMISSION, KNOWN_METHODS } from '../../../src/nest/plugins/protocol/envelope';
import { isKnownPermission, METHOD_PERMISSION, KNOWN_METHODS, HOOK_PERMISSION } from '../../../src/nest/plugins/protocol/envelope';
import path from 'node:path';
import { pluginsCodeRoot, pluginsDataRoot, pluginCodeDir, pluginDbFile, resolveChildEntry, serverCodeRoot, pluginPermissionArgs, pluginRealCodeDir, ensurePluginModuleType } from '../../../src/nest/plugins/paths';
import { pluginsCodeRoot, pluginsDataRoot, pluginCodeDir, pluginDataDir, pluginDbFile, resolveChildEntry, serverCodeRoot, pluginPermissionArgs, pluginRealCodeDir, ensurePluginModuleType } from '../../../src/nest/plugins/paths';
afterEach(() => {
delete process.env.TREK_PLUGINS_DIR;
@@ -21,9 +21,19 @@ describe('envelope helpers', () => {
expect(isKnownPermission('')).toBe(false);
});
it('every known method maps to a permission', () => {
// The compile side of this is `as const satisfies Record<KnownMethod, KnownPermission>`
// on METHOD_PERMISSION. These are the runtime mirror, and they also cover what the type
// system cannot: isKnownPermission's http:outbound: prefix branch.
it('every known method maps to a permission the host actually knows', () => {
for (const m of KNOWN_METHODS) {
expect(METHOD_PERMISSION[m]).toBeTruthy();
const perm = METHOD_PERMISSION[m];
expect(isKnownPermission(perm), `${m} -> ${perm}`).toBe(true);
}
});
it('every hook permission is a known permission', () => {
for (const [hook, perm] of Object.entries(HOOK_PERMISSION)) {
expect(isKnownPermission(perm), `${hook} -> ${perm}`).toBe(true);
}
});
});
@@ -91,3 +101,44 @@ describe('paths', () => {
}
});
});
/**
* The id these helpers get comes off a route parameter — POST
* /api/admin/plugins/:id/uninstall walks it into pluginCodeDir and hands the result
* to a recursive remove. Express decodes %2F only AFTER routing, so a traversal
* still matches the route and arrives here decoded.
*/
describe('plugin id containment', () => {
const traversals = [
'../../uploads',
'../..',
'..',
'.',
'a/b',
'a\b',
'C:\Windows',
'.hidden',
'',
];
it('refuses any id that could point outside its parent', () => {
for (const id of traversals) {
expect(() => pluginCodeDir(id), id).toThrow(/invalid plugin id/);
expect(() => pluginDataDir(id), id).toThrow(/invalid plugin id/);
}
});
it('still accepts the ids plugins actually use', () => {
for (const id of ['x', 'a1', '001', 'flight-tracker', 'Bad-Id', 'trip.todos', 'koffi_and_friends']) {
expect(path.basename(pluginCodeDir(id))).toBe(id);
expect(path.basename(pluginDataDir(id))).toBe(id);
}
});
it('keeps every accepted id inside the plugin roots', () => {
for (const id of ['x', 'flight-tracker', 'a.b', 'a-b_c']) {
expect(pluginCodeDir(id).startsWith(pluginsCodeRoot() + path.sep)).toBe(true);
expect(pluginDataDir(id).startsWith(pluginsDataRoot() + path.sep)).toBe(true);
}
});
});