mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
fix(plugins): security hardening from the PR #1415 audit
Remediates the findings from the adversarial audit (threat model: malicious plugin author + malicious artifact). Highlights: Critical - proxy: force nosniff + Content-Disposition: attachment on every proxied reply and drop location/content-disposition + non-2xx from the passthrough, so a plugin can't serve an HTML document at TREK's origin (sandbox-escape → account takeover) or an open redirect. High - db:own runs synchronously in the host: cap the plugin DB (max_page_count) and row-cap query() via iterate() so a recursive CTE / huge blob can't stall the event loop, OOM, or exhaust the shared volume. - supervisor: measure child RSS host-side (/proc/<pid>/statm) instead of trusting the spoofable heartbeat; add an activation timeout so a stuck onLoad can't hang activate() or peg a core unreaped. - safe-extract: enforce entry-count + cumulative-size limits INSIDE readZip before inflating (decompression-bomb OOM). - re-consent: activate() never widens granted permissions without explicit consent (409 CONSENT_REQUIRED); the row toggle + "Update All" route through the consent dialog, which now queues instead of overwriting. Medium/low - egress: gate dgram hostnames through the IP-vetting resolver; block the low-level socket escape (process.binding) + lock the wrapped prototypes; canonicalize IPv6 in isBlockedIp (hex-mapped/compressed metadata); reject degenerate `*.` / whole-TLD / spaced outbound hosts in the manifest + CSP. - ws:broadcast is membership-gated to the acting user's trips / own connections; users.getById is scoped to users the acting user can see (no enumeration). - safe-fetch streams + aborts at the byte cap (chunked codeload OOM); isPrivateIp reuses the canonicalizing check. - native-scan throws instead of silently passing past its entry cap. - SDK: dev serves binary assets as raw buffers + handles EADDRINUSE; manifest validator gains the reserved-id + outbound-host checks; wikis corrected. Tests updated for the new membership-gated behaviour + regression tests added (IPv6 canonicalization, wildcard hardening, outbound-host validation, ws/user scoping). 234 plugin tests + 24 SDK tests green.
This commit is contained in:
@@ -149,7 +149,7 @@ export async function runDev(dir: string, opts: { port?: number } = {}): Promise
|
||||
|
||||
async function handle(request: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
||||
const url = new URL(request.url || '/', 'http://localhost');
|
||||
const send = (status: number, body: string, type = 'text/plain; charset=utf-8', headers: Record<string, string> = {}) => {
|
||||
const send = (status: number, body: string | Buffer, type = 'text/plain; charset=utf-8', headers: Record<string, string> = {}) => {
|
||||
res.writeHead(status, { 'content-type': type, ...headers }); res.end(body);
|
||||
};
|
||||
|
||||
@@ -162,10 +162,14 @@ export async function runDev(dir: string, opts: { port?: number } = {}): Promise
|
||||
const file = path.join(abs, 'client', relFile);
|
||||
if (!file.startsWith(path.join(abs, 'client'))) return send(403, 'forbidden');
|
||||
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return send(404, 'not found — build your client/ bundle');
|
||||
let data: Buffer | string = fs.readFileSync(file);
|
||||
const raw = fs.readFileSync(file);
|
||||
const type = contentType(file);
|
||||
if (type.startsWith('text/html')) data = String(data).replace('</body>', `${LIVE_RELOAD}</body>`);
|
||||
return send(200, data.toString(), type);
|
||||
// Only the HTML doc is rewritten (live-reload inject) as a string; every other
|
||||
// asset is sent as the raw Buffer — a UTF-8 round-trip corrupts binary files.
|
||||
const body: string | Buffer = type.startsWith('text/html')
|
||||
? String(raw).replace('</body>', `${LIVE_RELOAD}</body>`)
|
||||
: raw;
|
||||
return send(200, body, type);
|
||||
}
|
||||
|
||||
// Plugin API routes mounted under /api/<route path>
|
||||
@@ -191,7 +195,11 @@ export async function runDev(dir: string, opts: { port?: number } = {}): Promise
|
||||
}
|
||||
|
||||
const port = opts.port ?? 4317;
|
||||
await new Promise<void>((resolve) => server.listen(port, resolve));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once('error', (e: NodeJS.ErrnoException) =>
|
||||
reject(e.code === 'EADDRINUSE' ? new Error(`port ${port} is already in use — pass --port <n> to pick another`) : e));
|
||||
server.listen(port, resolve);
|
||||
});
|
||||
const routes = plugin.routes ?? [];
|
||||
console.log(`\n trek-plugin dev — ${id} (${String(manifest.type)})`);
|
||||
console.log(` ${dbNote}`);
|
||||
|
||||
Reference in New Issue
Block a user