fix(security): resolve all HIGH/MEDIUM/LOW audit findings

Rust fixes (HIGH):
- symbols.rs: path validation (reject near-root, 50K file limit, symlink filter)
- memory.rs: FTS5 query quoting (prevent operator injection), 1000 fragment cap, content length limit, transaction wrapping
- budget.rs: atomic check-and-reserve via transaction, input validation, index on budget_log
- export.rs: safe UTF-8 truncation via chars().take()
- git_context.rs: reject paths starting with '-' (flag injection)
- branch_policy.rs: action validation (block|warn only), path validation

Rust fixes (MEDIUM):
- export.rs: named column access (positional→named)
- budget.rs: named column access, negative value guards

Svelte fixes:
- AccountSwitcher: 2-step confirmation before account switch
- ProjectMemory: expand/collapse content, 2-step delete confirm, tags split fix
- CodeIntelligence: min 2-char symbol query, CodeSymbol rename, aria-labels
- BudgetManager: 10M upper bound, aria-label on input, named constants
- SessionExporter: timeout cleanup on destroy, aria-live feedback
- AnalyticsDashboard: SVG aria-label, removed unused import, named constant
This commit is contained in:
Hibryda 2026-03-17 03:56:44 +01:00
parent 0324f813e2
commit 738574b9f0
13 changed files with 280 additions and 91 deletions

View file

@ -35,7 +35,7 @@ fn ensure_tables(conn: &rusqlite::Connection) -> Result<(), String> {
// Seed default policies if table is empty
let count: i64 = conn.query_row(
"SELECT COUNT(*) FROM pro_branch_policies", [], |row| row.get(0)
"SELECT COUNT(*) AS cnt FROM pro_branch_policies", [], |row| row.get("cnt")
).unwrap_or(0);
if count == 0 {
@ -62,6 +62,9 @@ fn glob_match(pattern: &str, value: &str) -> bool {
}
fn get_current_branch(project_path: &str) -> Result<String, String> {
if project_path.starts_with('-') {
return Err("Invalid project path: cannot start with '-'".into());
}
let output = Command::new("git")
.args(["-C", project_path, "branch", "--show-current"])
.output()
@ -87,10 +90,10 @@ pub fn pro_branch_check(project_path: String) -> Result<PolicyDecision, String>
let policies: Vec<BranchPolicy> = stmt.query_map([], |row| {
Ok(BranchPolicy {
id: row.get(0)?,
pattern: row.get(1)?,
action: row.get(2)?,
reason: row.get(3)?,
id: row.get("id")?,
pattern: row.get("pattern")?,
action: row.get("action")?,
reason: row.get("reason")?,
})
}).map_err(|e| format!("Query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
@ -127,10 +130,10 @@ pub fn pro_branch_policy_list() -> Result<Vec<BranchPolicy>, String> {
let rows = stmt.query_map([], |row| {
Ok(BranchPolicy {
id: row.get(0)?,
pattern: row.get(1)?,
action: row.get(2)?,
reason: row.get(3)?,
id: row.get("id")?,
pattern: row.get("pattern")?,
action: row.get("action")?,
reason: row.get("reason")?,
})
}).map_err(|e| format!("Query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
@ -144,6 +147,9 @@ pub fn pro_branch_policy_add(pattern: String, action: Option<String>, reason: Op
let conn = super::open_sessions_db()?;
ensure_tables(&conn)?;
let act = action.unwrap_or_else(|| "block".into());
if !["block", "warn"].contains(&act.as_str()) {
return Err(format!("Invalid action '{}': must be 'block' or 'warn'", act));
}
let rsn = reason.unwrap_or_default();
conn.execute(
"INSERT INTO pro_branch_policies (pattern, action, reason) VALUES (?1, ?2, ?3)",