feat: Security

This commit is contained in:
2026-06-05 18:34:01 +02:00
parent ee293d7daf
commit 45675192a5
134 changed files with 4128 additions and 446 deletions

View File

@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import {
collectPluginReadRoots,
fileUrlToPath,
pluginFileParentDir
} from './plugin-local-file.rules';
describe('plugin-local-file.rules', () => {
it('resolves linux file URLs to absolute paths', () => {
expect(fileUrlToPath('file:///home/ludde/Desktop/TestPlugin/plugin-source.json'))
.toBe('/home/ludde/Desktop/TestPlugin/plugin-source.json');
});
it('collects plugin read roots from source and entrypoint URLs', () => {
expect(collectPluginReadRoots(
'file:///home/ludde/Desktop/TestPlugin/plugin-source.json',
'file:///home/ludde/Desktop/TestPlugin/dist/main.js'
)).toEqual([
'/home/ludde/Desktop/TestPlugin',
'/home/ludde/Desktop/TestPlugin/dist'
]);
});
it('treats directory file URLs as their own read roots', () => {
expect(collectPluginReadRoots('file:///home/ludde/Desktop/TestPlugin/')).toEqual([
'/home/ludde/Desktop/TestPlugin'
]);
expect(collectPluginReadRoots('file:///home/ludde/Desktop/TestPlugin')).toEqual([
'/home/ludde/Desktop/TestPlugin'
]);
});
});

View File

@@ -0,0 +1,59 @@
import type { ElectronApi } from '../../../../core/platform/electron/electron-api.models';
export function pluginFileParentDir(filePath: string): string {
const normalized = filePath.replace(/\\/g, '/').replace(/\/+$/, '');
const index = normalized.lastIndexOf('/');
return index > 0 ? normalized.slice(0, index) : normalized;
}
export function pluginReadRootForFileUrl(fileUrl: string): string {
const filePath = fileUrlToPath(fileUrl).replace(/\\/g, '/').replace(/\/+$/, '');
const basename = filePath.split('/').pop() ?? '';
if (fileUrl.endsWith('/') || !basename.includes('.')) {
return filePath;
}
return pluginFileParentDir(filePath);
}
export function collectPluginReadRoots(...fileUrls: Array<string | undefined>): string[] {
const roots = new Set<string>();
for (const fileUrl of fileUrls) {
if (!fileUrl?.startsWith('file://')) {
continue;
}
roots.add(pluginReadRootForFileUrl(fileUrl));
}
return [...roots];
}
export async function grantPluginReadRoots(
api: Pick<ElectronApi, 'grantPluginReadRoot'> | null | undefined,
...fileUrls: Array<string | undefined>
): Promise<void> {
if (!api?.grantPluginReadRoot) {
return;
}
const roots = collectPluginReadRoots(...fileUrls);
for (const root of roots) {
await api.grantPluginReadRoot(root);
}
}
export function fileUrlToPath(fileUrl: string): string {
const url = new URL(fileUrl);
const decodedPath = decodeURIComponent(url.pathname);
if (/^\/[A-Za-z]:\//.test(decodedPath)) {
return decodedPath.slice(1).replace(/\//g, '\\');
}
return decodedPath;
}