fix(electrobun): address all 22 Codex review #2 findings
CRITICAL:
- DocsTab XSS: DOMPurify sanitization on all {@html} output
- File RPC path traversal: guardPath() validates against project CWDs
HIGH:
- SSH injection: spawn /usr/bin/ssh via PTY args, no shell string
- Search XSS: strip HTML, highlight matches client-side with <mark>
- Terminal listener leak: cleanup functions stored + called in onDestroy
- FileBrowser race: request token, discard stale responses
- SearchOverlay race: same request token pattern
- App startup ordering: groups.list chains into active_group restore
- PtyClient timeout: 5-second auth timeout on connect()
- Rule 55: 6 {#if} patterns converted to style:display toggle
MEDIUM:
- Agent persistence: only persist NEW messages (lastPersistedIndex)
- Search errors: typed error response, "Invalid query" UI
- Health store wired: agent events call recordActivity/setProjectStatus
- index.ts SRP: split into 8 domain handler modules (298 lines)
- App.svelte: extracted workspace-store.svelte.ts
- rpc.ts: typed AppRpcHandle, removed `any`
LOW:
- CommandPalette listener wired in App.svelte
- Dead code removed (removeGroup, onDragStart, plugin loaded)
This commit is contained in:
parent
8e756d3523
commit
1cd4558740
28 changed files with 1342 additions and 1164 deletions
71
ui-electrobun/src/bun/handlers/plugin-handlers.ts
Normal file
71
ui-electrobun/src/bun/handlers/plugin-handlers.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Plugin discovery + file reading RPC handlers.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { join } from "path";
|
||||
import { homedir } from "os";
|
||||
|
||||
const PLUGINS_DIR = join(homedir(), ".config", "agor", "plugins");
|
||||
|
||||
export function createPluginHandlers() {
|
||||
return {
|
||||
"plugin.discover": () => {
|
||||
try {
|
||||
const plugins: Array<{
|
||||
id: string; name: string; version: string;
|
||||
description: string; main: string; permissions: string[];
|
||||
}> = [];
|
||||
|
||||
if (!fs.existsSync(PLUGINS_DIR)) return { plugins };
|
||||
|
||||
const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const manifestPath = join(PLUGINS_DIR, entry.name, "plugin.json");
|
||||
if (!fs.existsSync(manifestPath)) continue;
|
||||
|
||||
try {
|
||||
const raw = fs.readFileSync(manifestPath, "utf-8");
|
||||
const manifest = JSON.parse(raw);
|
||||
plugins.push({
|
||||
id: manifest.id ?? entry.name,
|
||||
name: manifest.name ?? entry.name,
|
||||
version: manifest.version ?? "0.0.0",
|
||||
description: manifest.description ?? "",
|
||||
main: manifest.main ?? "index.js",
|
||||
permissions: Array.isArray(manifest.permissions) ? manifest.permissions : [],
|
||||
});
|
||||
} catch (parseErr) {
|
||||
console.error(`[plugin.discover] Bad manifest in ${entry.name}:`, parseErr);
|
||||
}
|
||||
}
|
||||
|
||||
return { plugins };
|
||||
} catch (err) {
|
||||
console.error("[plugin.discover]", err);
|
||||
return { plugins: [] };
|
||||
}
|
||||
},
|
||||
|
||||
"plugin.readFile": ({ pluginId, filePath }: { pluginId: string; filePath: string }) => {
|
||||
try {
|
||||
const pluginDir = join(PLUGINS_DIR, pluginId);
|
||||
const resolved = path.resolve(pluginDir, filePath);
|
||||
if (!resolved.startsWith(pluginDir + path.sep) && resolved !== pluginDir) {
|
||||
return { ok: false, content: "", error: "Path traversal blocked" };
|
||||
}
|
||||
if (!fs.existsSync(resolved)) {
|
||||
return { ok: false, content: "", error: "File not found" };
|
||||
}
|
||||
const content = fs.readFileSync(resolved, "utf-8");
|
||||
return { ok: true, content };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[plugin.readFile]", err);
|
||||
return { ok: false, content: "", error };
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue