Skip to content
MUNEEB SHAFIQ
RAG / APPLIED SECURITYF03

RAMPART

Static-analysis findings grounded in a corpus of 26,681 real disclosed vulnerabilities before developers see them

  • Google Gemini
  • ChromaDB
  • ONNX Runtime
  • Semgrep
Status
Active, not archived. Private repository. 17 commits spanning 2026-07-07 to 2026-08-19, last push 2026-08-19. Three contributors. The README describes the repository as the proof-of-concept slice of the full RAMPART pipeline.
Private repository

By the numbers · 10

  • 26,681

    disclosed-vulnerability records in the grounding knowledge base

  • 12,060

    HackerOne disclosed reports harvested and classified

  • 9,313

    CrossVul before/after fix pairs, spanning 158 CWEs and 21 languages

  • 253

    distinct vulnerability-class folders across the three corpora

  • 8,012 of 9,313

    CrossVul reports filed under a class name already used by the older corpora

  • 20

    REST endpoints across eight FastAPI routers

  • 60 per scan, 12 per call

    LLM verification budget, bounding a scan to at most five Gemini requests

  • 4

    grounding exemplars retrieved per verified finding

  • 10

    backend unit tests covering the apply/revert safety net and the health endpoint

  • 26

    pinned Python dependencies, including an eleven-package OpenTelemetry pin to reconcile semgrep with chromadb

Summary

RAMPART is a desktop security scanner that treats a static-analysis hit as a hypothesis rather than a verdict. Every finding is narrowed to its enclosing function, matched against a 26,681-record corpus of real disclosed vulnerabilities held in ChromaDB, and sent to Gemini alongside those retrieved exemplars, which returns a Confirmed / Likely / Informational / False positive judgement with a confidence score. The architecture matters because grounding and LLM budget are engineered together: retrieval runs only on findings that will actually be verified, verification is batched at twelve findings per call and capped at sixty per scan, and anything past the cap is labelled Unverified rather than quietly dropped.

The problem

Security scanning tools give development teams more warnings than they can act on, and most of those warnings turn out not to matter. Engineers learn to ignore the report, which defeats the point of running it. The hard part is not finding suspicious code; it is telling a genuine, exploitable weakness apart from noise, and making that judgement at a speed and cost a small team can absorb. RAMPART attacks that filtering problem, then offers a repairable next step rather than just a longer list.

Approach

  1. Three disclosed-vulnerability corpora (HackerOne, Nuclei, CrossVul) are normalized to one markdown-per-record layout under a shared vulnerability-class taxonomy, so a single knowledge-base builder walks all three without special-casing.

  2. A pluggable scanner layer emits one normalized Finding dataclass whether Bandit or Semgrep ran; the 'auto' mode prefers Semgrep for multi-language coverage and falls back to Bandit, leaving every downstream stage scanner-agnostic.

  3. Each finding is narrowed to a code slice: the innermost enclosing function resolved through the Python AST, or a fixed line window for other languages.

  4. Retrieval queries the Chroma collections CWE-filtered first with an unfiltered semantic query as fallback, then merges and deduplicates exemplars across the three sources by best similarity.

  5. Gemini receives the finding, the code slice and the retrieved exemplars, and returns a structured verdict with a confidence score, a plain-English explanation and a remediation sentence; findings are ranked by verdict weight, then severity, then confidence.

  6. LLM output is parsed leniently: markdown fences stripped, invalid backslash escapes repaired, three different result shapes tolerated, and 429 quota errors retried on a 5/12/24-second backoff.

  7. Remediation is opt-in per finding. Applying a generated fix snapshots the entire scanned target first, re-indents the rewrite to match the original slice, and refuses to write if the region no longer matches what was scanned.

  8. Persistence is optional and anonymised: signed-in scans store CWE, severity, verdict, confidence, rule id and exemplar URLs, while the raw code slice is explicitly never sent to the database.

Architecture

rampart · flow
Target pathscanner (Semgrep, else Bandit)normalized Findingcode slice via Python AST or line windowdedupe by (file, line, CWE) and sort by severitytop 60 grounded against three Chroma corpora, CWE-filteredGemini batched verification at 12 findings per callrank by verdict, severity, confidencereport UIoptional per-finding fix generationwhole-target snapshot then applyrevert from snapshot. Anonymised scan and finding records persist to Postgres when signed in.
ComponentRole
scanner.pyPluggable static-analysis layer. Bandit and Semgrep both emit the same normalized Finding dataclass; 'auto' probes for the venv semgrep executable and falls back to Bandit, caching the probe result per process.
extract.pyNarrows a finding to its innermost enclosing function by walking the Python AST, or to a plus/minus eight line window for non-Python files.
rag.pyChroma retrieval over three MiniLM-ONNX collections. Tries a CWE-filtered query first, falls back to unfiltered semantic search, then merges and dedupes hits across sources by best cosine similarity.
gemini.pyBatched grounded verification and on-demand fix generation. Includes a lenient JSON parser that strips markdown fences and repairs invalid backslash escapes, tolerance for three response shapes, and 429 backoff.
pipeline.pyOrchestration. Dedupes by (file, line, CWE), sorts by severity, bounds LLM spend to the top sixty findings, grounds only those, then ranks the merged set by verdict weight, severity and confidence.
apply.pyLocal remediation safety net: idempotent whole-target snapshot, indentation-aware slice replacement that preserves EOL style, a content guard against stale regions, and byte-for-byte revert.
backend/app/routers/Twenty FastAPI endpoints across eight routers covering auth, scan, scan history, fix/apply/revert, folder browse, health, profile and billing, with plan quotas enforced before any scan or LLM spend.
db.py + db/schema.sqlOptional asyncpg pool over Postgres/Supabase that degrades gracefully when DATABASE_URL is unset. Three tables plus cwe_stats and code_stats aggregate views that drive the research pages without re-scanning.
frontend/srcReact 19 and TypeScript SPA: Setup, Scanning, Report, History, Profile, Pricing and Auth pages, with a diff-rendering fix panel and exemplar cards showing the retrieved real-world evidence.
frontend/src-tauriRust desktop shell. Resolves the backend command by env override, repo venv, then bundled resources, spawns it as a loopback sidecar, and terminates it on window close.

Trade-offs

  • Chose

    One batched Gemini call per twelve findings, capped at sixty findings per scan

    Over

    One verification call per finding

    Free-tier Gemini quota is per-day and per-model, so batching keeps an entire scan within a handful of requests. Findings past the cap are returned honestly labelled Unverified rather than silently dropped (MAX_LLM_FINDINGS and LLM_BATCH in backend/app/config.py; the skipped-findings branch in pipeline.py).

  • Chose

    Running RAG retrieval only on the findings that will actually be verified

    Over

    Grounding every finding the scanner emits

    The embedder and Chroma client are not thread-safe so queries run sequentially, and grounding hundreds of unverified findings stalled the whole scan on a large codebase (Phase 2 comment in backend/app/services/pipeline.py).

  • Chose

    MiniLM embeddings served through ONNX Runtime

    Over

    A torch-backed gte-large index

    torch and gte-large could not be built on the development machine; the ONNX path removes the torch dependency entirely, at a documented cost to retrieval quality that a GPU-built index would recover (README limits section; module docstring in backend/app/services/rag.py).

  • Chose

    Storing only the unified fix diff plus the vulnerable lines around the patched hunk

    Over

    The full before/after source files from the upstream CrossVul dataset

    The upstream rows are 672 MB of mostly unrelated source at a 31 KB median per pair; keeping just the patch hunk holds the security signal in 27 MB (README progress log, 2026-08-19 entry).

  • Chose

    Reusing the HackerOne and Nuclei class-folder names for CrossVul wherever the CWE was already known

    Over

    A separate taxonomy per corpus

    Retrieval then sees one taxonomy rather than three: 8,012 of 9,313 CrossVul reports reuse an existing class name and 87 of 138 folders are shared, with the remainder falling back to the MITRE CWE name (README, 'One taxonomy, not three').

  • Chose

    Snapshotting the whole scanned target before the first applied fix, guarded by a content check that refuses to write when the region changed

    Over

    Editing files in place and trusting the user's version control

    A fix written into someone else's codebase needs a durable undo that does not assume git is present, and the guard also catches stale line numbers left by an earlier fix in the same file (backend/app/services/apply.py; the file_changed and revert cases in backend/tests/test_apply.py).

  • Chose

    A Tauri desktop shell that spawns the FastAPI backend as a 127.0.0.1 sidecar

    Over

    A hosted web service

    The scanner needs direct filesystem access to the user's code, and keeping the API on loopback means source never leaves the machine. The shell resolves the backend by env override, repo venv, then bundled resources, and kills the process it spawned on window close (frontend/src-tauri/src/lib.rs).

  • Chose

    Pinning the entire OpenTelemetry stack to 1.37

    Over

    Letting pip resolve transitive dependencies

    semgrep pins otel ~=1.37 while chromadb pulls 1.44, and both have to coexist in one virtual environment (header comment and eleven explicit otel pins in backend/requirements.txt).

  • Chose

    Application-level ownership checks with row-level security left off

    Over

    Supabase RLS policies

    The proof-of-concept backend writes with the service key and enforces owner_id from the JWT on every scan, fix and history query. The schema documents that RLS must be enabled first if the tables are ever exposed to Supabase's anon or authenticated roles (comment block at the top of backend/db/schema.sql).

At scale

  • 26,830 files tracked in the repository, 26,681 of them knowledge-base markdown records (git tree).

  • Backend: 30 Python files, comprising 7 services and 9 routers (git tree).

  • Frontend: 34 TypeScript/TSX files across pages, components, hooks, api clients and context providers (git tree).

  • Language byte breakdown reported by GitHub: TypeScript 105,065; Python 89,823; CSS 53,363; HTML 5,220; Rust 3,875.

  • Postgres schema defines 3 tables (users, scans, findings) and 2 aggregate views (cwe_stats, code_stats) in backend/db/schema.sql.

  • Three per-plan quota tiers enforced server-side with a 402 plan_limit response: Free 10 scans / 5 fixes, Pro 30/20, Premium 500/200 (config.PLANS).

My role

Repository owner and originator. Commit authorship under munib123 covers 2 of the 17 commits: the initial commit and the HackerOne knowledge-base corpus, in which 12,060 disclosed reports were harvested from raw form, processed, and classified into 202 vulnerability-class folders. That classification became the retrieval taxonomy the two later corpora were normalized against. The remaining 15 commits are authored by two collaborators: a collaborator (11 commits, covering most backend services, JWT auth, per-plan quotas, apply/revert and the Tauri desktop shell) and a second collaborator (4 commits, covering the Nuclei and CrossVul corpora and wiring CrossVul into retrieval). Git authorship is not a complete record of contribution, and this split must be confirmed with Muneeb before the case study is published.