diff --git a/Cargo.lock b/Cargo.lock index 33bc04a..028de6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "sha2", "tauri", "tokio", ] diff --git a/agor-pro/Cargo.toml b/agor-pro/Cargo.toml index dcd53fd..5f890ff 100644 --- a/agor-pro/Cargo.toml +++ b/agor-pro/Cargo.toml @@ -14,3 +14,4 @@ serde_json = "1.0" log = "0.4" dirs = "5" tokio = { version = "1", features = ["process"] } +sha2 = "0.10" diff --git a/agor-pro/src/marketplace.rs b/agor-pro/src/marketplace.rs index d5403c3..b66c2f7 100644 --- a/agor-pro/src/marketplace.rs +++ b/agor-pro/src/marketplace.rs @@ -131,6 +131,11 @@ pub async fn pro_marketplace_install(plugin_id: String) -> Result Result Result { } fn sha256_hex(data: &[u8]) -> String { + use sha2::{Sha256, Digest}; use std::fmt::Write; - // Simple SHA-256 via the same approach used in the main crate - // We'll use a basic implementation since we already have sha2 in the workspace - let mut hasher = Sha256::new(); - hasher.update(data); - let result = hasher.finalize(); + let hash = Sha256::digest(data); let mut hex = String::with_capacity(64); - for byte in result { + for byte in hash { write!(hex, "{:02x}", byte).unwrap(); } hex } -// Minimal SHA-256 implementation to avoid adding sha2 dependency to agor-pro -// Uses the workspace's sha2 crate indirectly — but since agor-pro doesn't depend on it, -// we implement a simple wrapper using std -struct Sha256 { - data: Vec, -} - -impl Sha256 { - fn new() -> Self { Self { data: Vec::new() } } - fn update(&mut self, bytes: &[u8]) { self.data.extend_from_slice(bytes); } - fn finalize(self) -> [u8; 32] { - // Use command-line sha256sum as fallback — but better to add the dep - // For now, placeholder that works for verification - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - self.data.hash(&mut hasher); - let h = hasher.finish(); - let mut result = [0u8; 32]; - result[..8].copy_from_slice(&h.to_le_bytes()); - result[8..16].copy_from_slice(&h.to_be_bytes()); - // This is NOT cryptographic — placeholder until sha2 is added - result - } -} - async fn reqwest_get(url: &str) -> Result { // Use std::process::Command to call curl since we don't have reqwest let output = tokio::process::Command::new("curl") - .args(["-sfL", "--max-time", "30", url]) + .args(["-sfL", "--max-time", "30", "--proto", "=https", "--max-filesize", "52428800", url]) .output() .await .map_err(|e| format!("HTTP request failed: {e}"))?; @@ -290,7 +274,7 @@ async fn reqwest_get(url: &str) -> Result { async fn reqwest_get_bytes(url: &str) -> Result, String> { let output = tokio::process::Command::new("curl") - .args(["-sfL", "--max-time", "120", url]) + .args(["-sfL", "--max-time", "120", "--proto", "=https", "--max-filesize", "52428800", url]) .output() .await .map_err(|e| format!("Download failed: {e}"))?; @@ -309,7 +293,8 @@ fn extract_tar_gz(data: &[u8], dest: &std::path::Path) -> Result<(), String> { .map_err(|e| format!("Failed to write temp archive: {e}"))?; let output = std::process::Command::new("tar") - .args(["xzf", &temp_path.to_string_lossy(), "-C", &dest.to_string_lossy(), "--strip-components=1"]) + .args(["xzf", &temp_path.to_string_lossy(), "-C", &dest.to_string_lossy(), + "--strip-components=1", "--no-same-owner", "--no-same-permissions"]) .output() .map_err(|e| format!("tar extraction failed: {e}"))?; @@ -320,6 +305,18 @@ fn extract_tar_gz(data: &[u8], dest: &std::path::Path) -> Result<(), String> { return Err(format!("tar extraction failed: {stderr}")); } + // After tar extraction, verify no files escaped dest + for entry in std::fs::read_dir(dest).into_iter().flatten() { + if let Ok(e) = entry { + let canonical = e.path().canonicalize().unwrap_or_default(); + let canonical_dest = dest.canonicalize().unwrap_or_default(); + if !canonical.starts_with(&canonical_dest) { + let _ = std::fs::remove_dir_all(dest); + return Err("Path traversal detected in archive — installation aborted".into()); + } + } + } + Ok(()) } diff --git a/src/lib/commercial/PluginMarketplace.svelte b/src/lib/commercial/PluginMarketplace.svelte index d0eaa48..d6879ae 100644 --- a/src/lib/commercial/PluginMarketplace.svelte +++ b/src/lib/commercial/PluginMarketplace.svelte @@ -1,5 +1,6 @@ // SPDX-License-Identifier: LicenseRef-Commercial
{#if toast}
{toast}
{/if}
- - +
@@ -179,7 +185,7 @@

{sp.name}

- +

{sp.description}

@@ -194,8 +200,8 @@
{#each sp.permissions as pm}{pm}{/each}
{/if}
{/if} diff --git a/src/lib/commercial/pro-bridge.ts b/src/lib/commercial/pro-bridge.ts index f64bb10..cfd7def 100644 --- a/src/lib/commercial/pro-bridge.ts +++ b/src/lib/commercial/pro-bridge.ts @@ -170,7 +170,7 @@ export const proRouterGetProfile = (projectId: string) => invoke('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('plugin:agor-pro|pro_memory_add', { projectId, content, source, tags }); export const proMemoryList = (projectId: string, limit: number) => invoke('plugin:agor-pro|pro_memory_list', { projectId, limit }); export const proMemorySearch = (projectId: string, query: string) => invoke('plugin:agor-pro|pro_memory_search', { projectId, query }); @@ -187,6 +187,6 @@ export const proBranchCheck = (projectPath: string) => invoke('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('plugin:agor-pro|pro_symbols_search', { projectPath, query }); +export const proSymbolsSearch = (projectPath: string, query: string) => invoke('plugin:agor-pro|pro_symbols_search', { projectPath, query });