You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ai_synth/docs/superpowers/specs/2026-03-26-audit-bugfixes-d...

4.9 KiB

Design: Audit Bug Fixes (P0 + P1)

Date: 2026-03-26 Scope: Fix 9 bugs identified by the 5-agent code audit Source: docs/audit/00-consolidated-summary.md


P0 — Fix Immediately

1. UTF-8 panic in error sanitization

Problem: &msg[..200] in synthesis.rs:1313 panics when slicing multi-byte UTF-8 characters.

Fix: Replace with safe char-boundary slicing:

let truncated: String = msg.chars().take(200).collect();

Files: backend/src/services/synthesis.rs

2. Race condition in job creation

Problem: Inside create_job(), the DashMap iteration (checking for existing active jobs by user_id) and the subsequent insert (with a new job_id key) are not atomic. Between the iteration completing and the insert, another thread can pass the same check and also insert. The DashMap is keyed by job_id (UUID), not user_id, so entry() cannot make this atomic directly.

Fix: Add a secondary DashSet<Uuid> keyed by user_id that acts as a lock. Before creating a job, attempt generating_users.insert(user_id) — if it returns false (already present), reject the request. Remove the user_id from the set when the job completes. This makes the check-and-lock atomic via DashSet's insert semantics.

Files: backend/src/services/synthesis.rs (job management section)

3. XSS via innerHTML

Problem: GenerateSynthesis.tsx renders the user's theme setting via innerHTML, allowing self-XSS.

Fix: Replace innerHTML with text interpolation. Use template literal or JSX text content instead.

Files: frontend/src/pages/GenerateSynthesis.tsx


P1 — Fix Within 30 Days

4. Expired session cleanup

Problem: sessions::delete_expired() exists in the DB module but is never called. Sessions accumulate forever.

Fix: Spawn a background tokio::spawn task at startup that calls sessions::delete_expired() periodically (every hour).

Files: backend/src/main.rs (or startup logic)

5. No pipeline timeout

Problem: A hung LLM API call blocks the generation slot indefinitely. Users cannot trigger a new generation until the stuck job times out via TTL.

Fix: Wrap the run_generation_inner call in tokio::time::timeout() with a 15-minute limit. On timeout, emit an error progress event and clean up the job.

Files: backend/src/services/synthesis.rs (the spawn that calls run_generation_inner)

6. SSRF bypass via IPv4-mapped IPv6

Problem: ::ffff:127.0.0.1 (IPv4-mapped IPv6 address) bypasses all current SSRF checks because the IPv6 branch doesn't check for mapped addresses.

Fix: In the SSRF validator, check for IPv4-mapped IPv6 addresses using to_ipv4_mapped() (or to_ipv4()) and apply IPv4 checks to the mapped address. Add tests for ::ffff:127.0.0.1, ::ffff:10.0.0.1, ::ffff:192.168.1.1.

Files: backend/src/services/scraper.rs (SSRF check function)

7. Missing SSRF check on source page fetch

Problem: source_scraper.rs fetches source page URLs directly without SSRF validation. A user could add a source pointing to http://169.254.169.254/ (AWS metadata).

Fix: Call the existing SSRF check function before fetching source pages in both extract_article_links and extract_article_links_with_llm. If the URL fails SSRF validation, log a warning and return empty results.

Files: backend/src/services/source_scraper.rs

8. Spawned generation tasks swallow panics

Problem: If the spawned generation task panics, the JoinHandle is never checked. The SSE client hangs waiting for a progress event that never comes.

Fix: After .await-ing the JoinHandle, check for Err(JoinError) (which indicates a panic). If the task panicked, emit an error progress event so the SSE client gets notified, and clean up the job entry.

Files: backend/src/handlers/generation.rs (the tokio::spawn at line ~73)

9. Missing onCleanup for setTimeout

Problem: setTimeout calls in Home.tsx, ArticleHistory.tsx, and SynthesisDetail.tsx are not cleaned up on component unmount. The timer callback fires after the component is gone, potentially causing stale state updates.

Fix: Store the timer ID returned by setTimeout, call clearTimeout(id) inside onCleanup(() => ...).

Files: frontend/src/pages/Home.tsx, frontend/src/pages/ArticleHistory.tsx, frontend/src/pages/SynthesisDetail.tsx, frontend/src/pages/GenerateSynthesis.tsx (also has uncleaned setTimeout, already touched for fix #3)


Files Summary

Backend:

  • backend/src/services/synthesis.rsfixes #1, #2, #5
  • backend/src/handlers/generation.rsfix #8
  • backend/src/services/scraper.rsfix #6
  • backend/src/services/source_scraper.rsfix #7
  • backend/src/main.rsfix #4

Frontend:

  • frontend/src/pages/GenerateSynthesis.tsxfixes #3, #9
  • frontend/src/pages/Home.tsxfix #9
  • frontend/src/pages/ArticleHistory.tsxfix #9
  • frontend/src/pages/SynthesisDetail.tsxfix #9