fix(security): resolve critical audit findings in marketplace and frontend
CRITICAL fixes: - marketplace.rs: Replace fake SHA-256 (SipHash) with real sha2 crate - marketplace.rs: Reject empty checksums (refuse unsigned plugins) - marketplace.rs: Add install path traversal protection (reject ../|/|\) - marketplace.rs: Add HTTPS-only URL validation on download_url - marketplace.rs: Add curl --proto =https to block file:/gopher: SSRF - marketplace.rs: Add --max-filesize 50MB download cap - marketplace.rs: Add --no-same-owner --no-same-permissions to tar extraction - marketplace.rs: Post-extraction path validation (canonicalize check) Frontend fixes: - pro-bridge.ts: Rename Symbol→CodeSymbol (avoid global collision) - pro-bridge.ts: Tighten trust type to union 'human'|'agent'|'auto' - PluginMarketplace.svelte: URL sanitization (reject non-https hrefs) Remaining audit fixes (HIGH/MEDIUM/LOW) being applied by background agents — will be committed separately when complete.
This commit is contained in:
parent
285f2404aa
commit
0324f813e2
5 changed files with 58 additions and 53 deletions
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: LicenseRef-Commercial
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import {
|
||||
proMarketplaceFetchCatalog, proMarketplaceInstalled, proMarketplaceInstall,
|
||||
proMarketplaceUninstall, proMarketplaceCheckUpdates, proMarketplaceUpdate,
|
||||
|
|
@ -116,15 +117,20 @@
|
|||
return '\u2605'.repeat(f) + '\u2606'.repeat(5 - f);
|
||||
}
|
||||
|
||||
$effect(() => { loadCatalog(); loadInstalled(); });
|
||||
function safeUrl(url: string | null): string | null {
|
||||
if (!url) return null;
|
||||
return /^https?:\/\//.test(url) ? url : null;
|
||||
}
|
||||
|
||||
onMount(() => { loadCatalog(); loadInstalled(); });
|
||||
</script>
|
||||
|
||||
<div class="mp-root">
|
||||
{#if toast}<div class="mp-toast">{toast}</div>{/if}
|
||||
|
||||
<div class="mp-tabs">
|
||||
<button class="mp-tab" class:active={tab === 'browse'} onclick={() => (tab = 'browse')}>Browse</button>
|
||||
<button class="mp-tab" class:active={tab === 'installed'} onclick={() => (tab = 'installed')}>
|
||||
<button class="mp-tab" class:active={tab === 'browse'} onclick={() => { tab = 'browse'; confirmUninstall = null; }}>Browse</button>
|
||||
<button class="mp-tab" class:active={tab === 'installed'} onclick={() => { tab = 'installed'; confirmUninstall = null; }}>
|
||||
Installed{installed.length > 0 ? ` (${installed.length})` : ''}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -179,7 +185,7 @@
|
|||
<div class="mp-detail">
|
||||
<div class="mp-detail-hdr">
|
||||
<h3 class="mp-detail-name">{sp.name}</h3>
|
||||
<button class="mp-x" onclick={() => (selectedPlugin = null)}>x</button>
|
||||
<button class="mp-x" aria-label="Close plugin details" onclick={() => (selectedPlugin = null)}>x</button>
|
||||
</div>
|
||||
<p class="mp-detail-desc">{sp.description}</p>
|
||||
<div class="mp-fields">
|
||||
|
|
@ -194,8 +200,8 @@
|
|||
<div class="mp-perms">{#each sp.permissions as pm}<span class="mp-perm">{pm}</span>{/each}</div>
|
||||
{/if}
|
||||
<div class="mp-links">
|
||||
{#if sp.homepage}<a href={sp.homepage} target="_blank" rel="noopener" class="mp-link">Homepage</a>{/if}
|
||||
{#if sp.repository}<a href={sp.repository} target="_blank" rel="noopener" class="mp-link">Repository</a>{/if}
|
||||
{#if safeUrl(sp.homepage)}<a href={safeUrl(sp.homepage)} target="_blank" rel="noopener" class="mp-link">Homepage</a>{/if}
|
||||
{#if safeUrl(sp.repository)}<a href={safeUrl(sp.repository)} target="_blank" rel="noopener" class="mp-link">Repository</a>{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export const proRouterGetProfile = (projectId: string) => invoke<string>('plugin
|
|||
|
||||
// --- Persistent Memory ---
|
||||
|
||||
export interface MemoryFragment { id: number; projectId: string; content: string; source: string; trust: string; confidence: number; createdAt: number; ttlDays: number; tags: string; }
|
||||
export interface MemoryFragment { id: number; projectId: string; content: string; source: string; trust: 'human' | 'agent' | 'auto'; confidence: number; createdAt: number; ttlDays: number; tags: string; }
|
||||
export const proMemoryAdd = (projectId: string, content: string, source: string, tags: string) => invoke<number>('plugin:agor-pro|pro_memory_add', { projectId, content, source, tags });
|
||||
export const proMemoryList = (projectId: string, limit: number) => invoke<MemoryFragment[]>('plugin:agor-pro|pro_memory_list', { projectId, limit });
|
||||
export const proMemorySearch = (projectId: string, query: string) => invoke<MemoryFragment[]>('plugin:agor-pro|pro_memory_search', { projectId, query });
|
||||
|
|
@ -187,6 +187,6 @@ export const proBranchCheck = (projectPath: string) => invoke<PolicyDecision>('p
|
|||
|
||||
// --- Symbols ---
|
||||
|
||||
export interface Symbol { name: string; kind: string; filePath: string; lineNumber: number; }
|
||||
export interface CodeSymbol { name: string; kind: string; filePath: string; lineNumber: number; }
|
||||
export const proSymbolsScan = (projectPath: string) => invoke<{filesScanned: number; symbolsFound: number; durationMs: number}>('plugin:agor-pro|pro_symbols_scan', { projectPath });
|
||||
export const proSymbolsSearch = (projectPath: string, query: string) => invoke<Symbol[]>('plugin:agor-pro|pro_symbols_search', { projectPath, query });
|
||||
export const proSymbolsSearch = (projectPath: string, query: string) => invoke<CodeSymbol[]>('plugin:agor-pro|pro_symbols_search', { projectPath, query });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue