mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
14b348581a
Every plugin now runs in its own forked child process with a scrubbed env (no JWT_SECRET, no db path, nothing inherited). It talks to TREK only over a JSON-RPC channel, and the host's capability router registers ONLY the methods a plugin's granted permissions unlock — so an ungranted call is unreachable, not merely refused. The plugin's own data lives in a separate sqlite file it can never open directly; core reads (trips/users) go through membership-checked, column-projected host methods; ws broadcasts are force-namespaced. - protocol/envelope: the wire types + method→permission map (pure, shared by host and the isolated child) - host/rpc-host: the capability router = the enforcement point (dispatch, BAD_PARAMS / PERMISSION_DENIED / RESOURCE_FORBIDDEN / UNKNOWN_METHOD) - host/plugin-data: the per-plugin sqlite file (db:own), guarded against ATTACH/PRAGMA escape, idempotent migrations - host/create-rpc-host: wires the router to the real db/websocket (host-only) - runtime/plugin-sdk + plugin-host-entry: the child bootstrap + definePlugin ctx; turns each ctx call into an RPC, never imports a privileged module - supervisor: spawn on activate, heartbeat/reap, crash backoff + auto-disable, graceful shutdown — a plugin crash/OOM/hang can never reach the Nest loop - paths: code/data layout, dist-vs-tsx child entry resolution Nothing is wired into activation yet (that's the next slice); exercised by unit tests for the router/sdk/data and an integration test that forks a real child.
52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
/**
|
|
* Small pure helpers of the plugin module (#plugins): permission recognition and
|
|
* 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 path from 'node:path';
|
|
import { pluginsCodeRoot, pluginsDataRoot, pluginCodeDir, pluginDbFile, resolveChildEntry } from '../../../src/nest/plugins/paths';
|
|
|
|
afterEach(() => {
|
|
delete process.env.TREK_PLUGINS_DIR;
|
|
delete process.env.TREK_PLUGINS_DATA_DIR;
|
|
});
|
|
|
|
describe('envelope helpers', () => {
|
|
it('recognises known permissions, http:outbound:<host>, and rejects unknown', () => {
|
|
expect(isKnownPermission('db:own')).toBe(true);
|
|
expect(isKnownPermission('http:outbound')).toBe(true);
|
|
expect(isKnownPermission('http:outbound:api.example.com')).toBe(true);
|
|
expect(isKnownPermission('fs:read')).toBe(false);
|
|
expect(isKnownPermission('')).toBe(false);
|
|
});
|
|
|
|
it('every known method maps to a permission', () => {
|
|
for (const m of KNOWN_METHODS) {
|
|
expect(METHOD_PERMISSION[m]).toBeTruthy();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('paths', () => {
|
|
it('uses the env override when set', () => {
|
|
process.env.TREK_PLUGINS_DIR = '/custom/code';
|
|
process.env.TREK_PLUGINS_DATA_DIR = '/custom/data';
|
|
expect(pluginsCodeRoot()).toBe('/custom/code');
|
|
expect(pluginsDataRoot()).toBe('/custom/data');
|
|
expect(pluginCodeDir('x')).toBe(path.join('/custom/code', 'x'));
|
|
expect(path.basename(pluginDbFile('x'))).toBe('plugin.db');
|
|
});
|
|
|
|
it('falls back to the data-dir default when unset', () => {
|
|
expect(pluginsCodeRoot()).toContain('plugins');
|
|
expect(pluginsDataRoot()).toContain('plugins-data');
|
|
});
|
|
|
|
it('resolves a child entry with fork args', () => {
|
|
const r = resolveChildEntry();
|
|
expect(r.entry).toMatch(/plugin-host-entry\.(js|ts)$/);
|
|
expect(Array.isArray(r.execArgv)).toBe(true);
|
|
});
|
|
});
|