SentryVision is a standalone smart contract security verifier built for the Meta Hackathon. It analyzes Solidity contracts, identifies vulnerabilities, explains how an exploit would actually happen, and visualizes the attack flow.
Unlike generic scanners that output cryptic reports, SentryVision combines deterministic static analysis with Meta's Muse Spark Open (and optional Muse Wonder on repair) to produce structured, auditable, and visual security intelligence.
Sentry Vision is a triage and visualization tool, not a formal security audit. See Limitations.
- Slither is a reliable deterministic detector, but its output is hard to read.
- LLMs are easy to read, but they can hallucinate.
- Sentry Vision combines them: Slither detects, Muse Spark Open interprets and visualizes, Slither verifies the fix, and optional Muse Wonder retries a rejected patch once.
Developers and non-technical founders usually get one of two things:
- Dense static-analysis dumps (for example raw Slither output) that they cannot interpret, or
- Generic LLM answers with no deterministic grounding that they cannot trust.
Neither tells them what is actually dangerous or how an attack would really happen.
- The user pastes or uploads a
.solcontract. - Slither runs deterministic static detection (detector, impact, confidence, affected lines).
- Muse Spark Open (Meta's open-weights model) triages the findings: confirms real issues, ranks by exploitability, and explains each issue in plain language.
- The app returns structured JSON and renders a visual attack flowchart (Mermaid) for each confirmed finding.
- The model proposes a patched version, and Slither re-runs on the patch to check it. The result is labeled "Patch verified by Slither" or "Unverified patch".
- If F3 rejects the patch, the user can retry once (Muse Wonder when configured, else Spark) — still verified by Slither.
Core differentiation: not just "find bugs", but show how the hack would actually happen, and check the fix with the same deterministic tool.
| Layer | Role | Engine |
|---|---|---|
| F1 | Deterministic static signal | Slither |
| F2 | Triage, plain-language explanation, patch proposal, Mermaid exploit flow | Muse Spark Open (meta-models/muse-spark-open-1026) via Baseten |
| F3 | Patch re-verification (Slither re-check) | Slither on proposed patched_code |
| Retry (optional) | One user-triggered repair if F3 rejects the patch | Muse Wonder when LLM_RETRY_MODEL is set; otherwise Muse Spark |
flowchart TD
A["Solidity contract (.sol)"] --> B["F1: Slither static analysis"]
B --> C["Slither JSON findings"]
C --> D["F2: Muse Spark Open triage"]
A --> D
D --> E["Structured JSON findings + Mermaid flows"]
E --> G["Finding cards"]
D --> H["Patch proposal"]
H --> I["F3: Sli ther re-verification"]
I --> G
I -->|"F3 rejected (user clicks Retry once)"| J["Optional: Muse Wonder repair"]
J --> I
F1 - Deterministic Detection: Slither static analysis on the submitted .sol contract produces structured JSON findings.
F2 - Muse Spark Open Triage: Meta Muse Spark Open reads both the contract and Slither findings and returns structured JSON findings, plain-language explanations, Mermaid exploit-flow diagrams, and optional patch proposals.
F3 - Patch verification: proposed fixes are re-analyzed by Slither before they are labeled verified.
Optional retry - Muse Wonder: if F3 rejects a real patch, the user can trigger one repair pass (Muse Wonder when configured). The new patch is still subject to F3.
Presentation (alongside the pipeline): finding cards with severity, plain language, and Mermaid diagrams. Reasoning-aware parsing validates Muse JSON with Pydantic and one repair retry.
When F1 cannot run (unsupported import, compile failure, Slither unavailable, or Slither disabled), patches are not labeled verified. The UI meta line reports Pipeline: Model only — not verified.
Known OpenZeppelin imports still go through Slither via small vendored stubs: Ownable, ReentrancyGuard, Pausable (v4 security/ and v5 utils/), ERC20, IERC20, SafeERC20, Address, and Context. Other packages, relative files, and upgradeable contracts stay model-only. Ownable stub is the no-argument constructor (v4 style).
When F3 rejects a real patch (still_vulnerable, compile_failed, or regression_introduced), the UI can offer one user-triggered retry:
| Piece | Behavior |
|---|---|
| Trigger | User clicks Retry patch once (never auto) |
| Server state | meta.analysis_id — original source + findings stored in memory (~1h TTL) |
| Endpoint | POST /analyze/{analysis_id}/retry-patch |
| Cap | MAX_PATCH_RETRIES=1 enforced server-side |
| Default model | Muse Spark (same as F2) |
| Optional Wonder | Set LLM_RETRY_MODEL / LLM_WONDER_MODEL to a Muse Wonder id — pill turns green only when configured |
Retry does not run on identical patches, empty patches, or pure infra failures (timeout / slither_unavailable). Invalid retry JSON does not burn the one attempt. Verified means Slither reports no High/Medium issue and the original public functions are still there — not “safe to deploy.” Deleting an entry point is surface_removed and can be retried once.
- Muse Spark Open (
meta-models/muse-spark-open-1026) — default F2 triage / patch proposal (Baseten OpenAI-compatible API). - Muse Wonder (optional) — stronger one-shot repair on user-triggered retry when
LLM_RETRY_MODELis set. - Development: Muse tools were also used as coding assistants while building this project.
- Core AI Engine: Meta Muse Spark Open via Baseten Inference (optional Muse Wonder on self-heal retry)
- Static Analysis: Slither with
solc-select(Docker image) - Backend: Python, FastAPI, Pydantic
- Frontend: HTML / JavaScript with Mermaid.js
- Deployment: Render (Docker)
- Python 3.10+ or Docker
- Baseten API key for Muse Spark Open
git clone https://github.com/chokri29/SentryVision.git
cd SentryVision
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcp .env.example .envLLM_API_KEY=your_baseten_api_key
LLM_BASE_URL=https://inference.baseten.co/v1
LLM_MODEL=meta-models/muse-spark-open-1026
# Optional: Muse Wonder for the single self-heal retry
# LLM_RETRY_MODEL=meta-models/<muse-wonder-id>
ENABLE_SLITHER=true
ALLOWED_ORIGINS=http://localhost:8000Never commit .env. On Render, set the same variables in the service dashboard.
# Docker (recommended — includes solc + Slither)
docker build -t sentryvision .
docker run --rm -p 8000:8000 --env-file .env sentryvision
# Or local (Slither optional if installed)
uvicorn main:app --reload --port 8000- UI: http://localhost:8000
- Health:
GET /health - API:
POST /analyzewith{ "solidity_code": "..." } - Retry:
POST /analyze/{analysis_id}/retry-patch(max 1; requires prior/analyze)
- Open the live app (or
http://localhost:8000after Docker/local start). - Click Reentrancy (same source as
examples/01_reentrancy.sol). - Click Run Meta-assisted Audit.
What to look for
| UI signal | Meaning |
|---|---|
| Summary meta line | Counts + pipeline label (F1 → F2 → F3, or Model only — not verified if Slither could not run) |
| Finding severity + plain language | F2 Muse triage grounded in F1 |
| Mermaid flowchart | Visual exploit path (presentation layer) |
| Patch verified by Slither (green) | F3: no High/Medium left, and every original public/external function is still present (not a full audit) |
| Patch removed public functions | F3: Slither went quiet because an entry point was deleted — not a fix |
| Patch failed Slither re-check | F3 rejected the proposed fix — verification can say no |
| Fix proposed | Muse returned a full patched contract |
| Retry patch once (amber bar) | F3 failed → one user-triggered repair + F3 again |
Optional: try Access Control and Unchecked Call. Source for every button lives under examples/ so judges can open the same files in the repo.
| Item | Status |
|---|---|
FastAPI + Muse Spark Open via Baseten (/analyze) |
Done |
Bounded self-heal (analysis_id, max 1 retry) |
Done |
| Slither F1 + F3 in Docker | Done |
| Known OpenZeppelin stubs so F1 still runs | Done |
| pytest on push | Done |
| UI status pills + honest Wonder labeling | Done |
- Not a substitute for a professional audit.
- F3 “verified” means Slither reports no High/Medium issue and the patch kept the original public/external surface (functions, receive, fallback). It is not a proof of safety. A rewrite that keeps the function but hides the bug can still pass.
- Retry state is in-memory (lost on restart / multi-instance).
- Multi-file repos and agentic tool loops are out of scope for this demo.
MIT