mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
feat(plugins): sandbox hardening — OS permission model, egress choke point, bound acting user, author signatures
Closes the four gaps the security review surfaced: - OS permission model on the prod plugin child (Node --permission with fs-read scoped to the compiled server dir + the plugin's own code dir, no fs-write/child_process/worker/native). A plugin can no longer read trek.db or the .jwt_secret/.encryption_key files, nor shell out — the direct-fs and RCE escapes that bypassed the RPC layer. Opt-out via TREK_PLUGIN_PERMISSIONS=off. - Egress guard extended from fetch to the net.Socket connect choke point, so node:http/https/net/tls obey the declared-egress allowlist too (no declared egress = no outbound). Under the permission model there is no clean escape to an unwrapped runtime. Kernel/network-namespace containment remains the container step. - Trip reads are membership-checked against the acting user the HOST binds from the authenticated invocation, not an asUserId the plugin supplies; a job/onLoad (no user) can't read user-scoped trips. - Optional minisign (Ed25519) author signatures verified offline, TOFU-pinned (migration 158). Unsigned plugins install on sha256 alone; a signed plugin can't silently drop its signature or swap its author key. Server suite green (permission-model activation verified against the Koffi reference plugin on dev1).
This commit is contained in:
@@ -48,16 +48,23 @@ export class PluginsProxyController {
|
||||
}
|
||||
|
||||
try {
|
||||
const reply = (await this.runtime.invoke(pluginId, 'invoke.route', {
|
||||
routeId: route.i,
|
||||
req: {
|
||||
method: req.method,
|
||||
path: sub,
|
||||
query: req.query,
|
||||
body: req.body ?? null,
|
||||
user: user ? { id: user.id, username: user.username, isAdmin: !!user.is_admin } : null,
|
||||
const reply = (await this.runtime.invoke(
|
||||
pluginId,
|
||||
'invoke.route',
|
||||
{
|
||||
routeId: route.i,
|
||||
req: {
|
||||
method: req.method,
|
||||
path: sub,
|
||||
query: req.query,
|
||||
body: req.body ?? null,
|
||||
user: user ? { id: user.id, username: user.username, isAdmin: !!user.is_admin } : null,
|
||||
},
|
||||
},
|
||||
})) as { status?: number; headers?: Record<string, string>; body?: unknown };
|
||||
// Bind the authenticated session user as the acting user for any trip
|
||||
// reads this invocation makes — the plugin cannot override it.
|
||||
user?.id,
|
||||
)) as { status?: number; headers?: Record<string, string>; body?: unknown };
|
||||
|
||||
res.status(reply?.status ?? 200);
|
||||
for (const [k, v] of Object.entries(reply?.headers ?? {})) {
|
||||
|
||||
Reference in New Issue
Block a user