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
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { appRpc } from './rpc.ts';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -19,6 +20,12 @@
|
|||
let renderedHtml = $state('');
|
||||
let loading = $state(false);
|
||||
|
||||
// Fix #1: Configure DOMPurify with safe tag whitelist
|
||||
const PURIFY_CONFIG = {
|
||||
ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'p', 'a', 'strong', 'em', 'code', 'pre', 'ul', 'li', 'br'],
|
||||
ALLOWED_ATTR: ['href', 'target', 'class'],
|
||||
};
|
||||
|
||||
function expandHome(p: string): string {
|
||||
if (p.startsWith('~/')) return p.replace('~', '/home/' + (typeof process !== 'undefined' ? process.env.USER : 'user'));
|
||||
return p;
|
||||
|
|
@ -44,19 +51,26 @@
|
|||
const res = await appRpc.request['files.read']({ path: file.path });
|
||||
if (res.error || !res.content) {
|
||||
content = '';
|
||||
renderedHtml = `<p class="doc-error">${res.error ?? 'Empty file'}</p>`;
|
||||
renderedHtml = DOMPurify.sanitize(
|
||||
`<p class="doc-error">${escapeHtml(res.error ?? 'Empty file')}</p>`,
|
||||
PURIFY_CONFIG,
|
||||
);
|
||||
} else {
|
||||
content = res.content;
|
||||
renderedHtml = renderMarkdown(content);
|
||||
renderedHtml = DOMPurify.sanitize(renderMarkdown(content), PURIFY_CONFIG);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[DocsTab] read error:', err);
|
||||
renderedHtml = '<p class="doc-error">Failed to read file</p>';
|
||||
renderedHtml = DOMPurify.sanitize('<p class="doc-error">Failed to read file</p>', PURIFY_CONFIG);
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
|
||||
/** Simple markdown-to-HTML (no external dep). Handles headers, code blocks, bold, italic, links, lists. */
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/** Simple markdown-to-HTML. All output is sanitized by DOMPurify before rendering. */
|
||||
function renderMarkdown(md: string): string {
|
||||
let html = md
|
||||
.replace(/&/g, '&')
|
||||
|
|
@ -64,8 +78,8 @@
|
|||
.replace(/>/g, '>');
|
||||
|
||||
// Code blocks
|
||||
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, lang, code) =>
|
||||
`<pre class="doc-code"><code class="lang-${lang}">${code.trim()}</code></pre>`
|
||||
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, _lang, code) =>
|
||||
`<pre class="doc-code"><code>${code.trim()}</code></pre>`
|
||||
);
|
||||
// Inline code
|
||||
html = html.replace(/`([^`]+)`/g, '<code class="doc-inline-code">$1</code>');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue