Autonomous Solana program security auditor built on LangGraph (JS), OpenRouter, a five-level "Crystalline" cognitive memory layer, and a hybrid (Supabase + Neo4j) knowledge base.
ARES runs an audit as a graph of phases. The analysis phase fans out to several analyzers in parallel, and every audit both reads from and writes back to a growing body of security knowledge.
intake ──► recall ──┬──► analyzeOnchain ──┐
(LLM) (hybrid) ├──► analyzeStatic ──┤
├──► analyzeHeuristic ──┼──► merge ──► remember ──► report
└──► analyzeCua (opt-in)─┘ (rank) (persist) (LLM)
| Phase | What it does |
|---|---|
| INTAKE | LLM parses the request into a structured target/depth/concerns summary. |
| RECALL | Hybrid retriever pulls relevant prior knowledge (see below). |
| ANALYZE | Four analyzers run in parallel and append findings: |
· onchain — loads the program via Solana/Helius RPC and reasons over it. |
|
· static — runs Semgrep over a source path (optional binary). |
|
· heuristic — LLM reasoning over intake + recalled memory. |
|
· cua — opt-in: drives a real browser (Scrapybara Computer Use Agent) |
|
| to investigate explorers/repos/docs. See below. | |
| MERGE | Fan-in join: dedupes and severity-ranks the combined findings. |
| VERIFY | Skeptical critic pass: refines confidence/status, drops false-positives. |
| REMEMBER | LLM decides what to persist; writes crystals + runs consolidation. |
| REPORT | Synthesizes a professional audit report (severity matrix, stable finding IDs, coverage). |
Recall unions three sources and merges their scores, so a fragment surfaced by several sources ranks higher:
- Crystalline — in-process activation-based memory (working/episodic recall).
- Supabase —
hybrid_searchRPC over pgvector + full-text (RRF) for candidate retrieval. - Neo4j — a standalone lexical match over graph chunks plus 1–2 hop graph expansion / relationship-aware reranking of the Supabase candidates.
Every source degrades gracefully: with Supabase/Neo4j/embeddings unset, recall falls back to Crystalline-only and the agent still runs fully offline.
analyzeCua drives a real, Scrapybara-hosted browser to gather external
evidence about the audit target (block explorers, source repos, docs, prior
audit mentions), then turns the investigation transcript into findings.
- Opt-in and off by default. Enable with
CUA_ENABLED=trueor pass--cuafor a single run. It only activates when bothOPENAI_API_KEYandSCRAPYBARA_API_KEYare set — otherwise the node returns no findings immediately, exactly likeanalyzeStatic/analyzeOnchaindo without their inputs, so the graph and test suite stay hermetic by default. - Uses OpenAI directly, not OpenRouter.
@langchain/langgraph-cuainvokes OpenAI'scomputer-use-previewmodel itself (viaChatOpenAI, which readsOPENAI_API_KEYfrom the environment) — there's no way to route the browser-driving loop through OpenRouter. The rest of ARES is unaffected and stays on OpenRouter. - Read-only by design. The system prompt
(
cuaInvestigationSystemPromptinsrc/llm/prompts.ts) explicitly forbids authentication, form submission, and any other state-changing action — the agent may only navigate and read.
- Checkpointer:
PostgresSaverpersists per-thread graph state across runs. - Crystalline store:
InMemoryStore(session-scoped) — LangGraph JS has no Postgres store yet, so durable cross-audit knowledge lives in the Supabase + Neo4j knowledge base instead. - Knowledge writeback: REMEMBER writes each persisted fragment to Crystalline
and, when Supabase/Neo4j are configured, durably to the hybrid KB
(
src/persistence/knowledge-writer.ts) using the samedoc_id/chunk_idscheme as the seed ingester — so runtime-learned knowledge survives restarts and is recalled in later audits. Without those backends it stays Crystalline-only, exactly as before.
cp .env.example .env # fill in OPENROUTER_API_KEY (rest are optional)
npm install
npm run db:up # starts Postgres + Neo4j via docker compose
npm run db:migrate # creates checkpoint tables + Neo4j constraintsSupabase schema is applied separately (cloud or supabase CLI):
# apply db/supabase/0001_hybrid_search.sql via the Supabase SQL editor / CLISeed the knowledge base from the solsec corpus (requires Supabase and/or Neo4j credentials):
npm run ingest:solsec# On-chain program (uses Helius RPC when HELIUS_RPC_URL is set):
npm run audit -- --program <PROGRAM_ADDRESS>
# Local source with Semgrep static analysis:
npm run audit -- --source ./path/to/program
# Quick local run without Postgres:
npm run audit -- --program <ADDRESS> --ephemeral
# Opt into the CUA browser-investigation analyzer for this run
# (requires OPENAI_API_KEY + SCRAPYBARA_API_KEY):
npm run audit -- --program <ADDRESS> --cuanpm run typecheck # tsc --noEmit
npm run lint # eslint (flat config)
npm test # vitest — hermetic; no external services required
npm run build # emit to dist/The test suite runs the full graph end-to-end with a fake LLM and an in-memory store, exercising the parallel fan-out, the concat-reducer findings channel, and Crystalline persistence — with Supabase/Neo4j unset to prove graceful fallback.
.github/workflows/ci.yml runs the four commands above (typecheck, lint, build,
test) on every push and pull request to main, across Node 20 and 22. Because
the suite is hermetic, CI needs no secrets or services. Dependency updates are
grouped into weekly Dependabot PRs (.github/dependabot.yml).
The analyzers work through a structured Solana vulnerability catalog
(src/knowledge/solana-vulns.ts) — access-control, CPI, PDA, arithmetic,
lifecycle, oracle, DeFi (slippage/front-running), availability (DoS),
governance (upgrade authority), Token-2022 extensions, and business-logic
classes — grounded in the Sealevel Attacks,
Neodyme's common pitfalls,
and the solsec corpus. Every finding is
tagged with a catalog id and the set of evaluated classes is tracked as
coverage.
Findings are rated on an impact × likelihood severity matrix
(src/knowledge/severity.ts), and the REPORT phase emits a professional
assessment — executive summary with a deterministic severity table, scope &
methodology, stable finding IDs (ARES-001…), per-finding
description/impact/recommendation, and a coverage section — in the style of
firms like OtterSec, Neodyme, and Zellic.
See SECURITY.md for the vulnerability-reporting process,
ARES's read-only runtime posture, and the status of tracked dependency
advisories.
src/billing/ turns ARES into a paid service. It's opt-in — with
BILLING_ENABLED unset, nothing here runs. When enabled, every audit is
metered on its LLM token usage, priced against a per-model rate table
(pricing.ts, tracking Anthropic/OpenRouter rates incl. prompt-cache and
web-search costs), marked up, and charged in credits (default 100 credits =
$1) against a two-tier account:
- System credits — a prepaid balance (a subscription's allotment or a top-up), drawn down first.
- On-demand credits — pay-as-you-go overflow once the prepaid balance is
exhausted, settled via the Machine Payments Protocol
(HTTP-402 pay-per-request / metered sessions; hermetic local settlement
unless
MPP_ENDPOINTis set).
Profit is guaranteed by construction (profit.ts): the effective markup is
floored at 1 + BILLING_MIN_MARGIN_PCT, credits are rounded up, and every audit
is billed at least BILLING_MIN_CHARGE_CREDITS — so a run can never settle
below provider cost, even if the markup is misconfigured. Each settlement
reports cost, revenue, profit, and margin.
Payment is enforced before delivery. When billing is on, the report is
released only after settlement succeeds; if the account can't cover the charge
(InsufficientCreditsError), the report is withheld and the run exits non-zero.
A pre-flight check also warns when an account structurally can't pay (no prepaid
balance and on-demand disabled). Balances persist across runs when
BILLING_ACCOUNT_STORE_PATH is set (account-store.ts), so on-demand spend
actually accumulates instead of resetting each run. And a configured
MPP_ENDPOINT whose real HTTP-402 client isn't wired fails loudly rather
than silently settling locally — set MPP_ALLOW_LOCAL_FALLBACK=true to opt into
hermetic settlement explicitly.
All variables are documented in .env.example. Only OPENROUTER_API_KEY is
required; Solana defaults to mainnet-beta, Postgres to the docker-compose
values, and Supabase/Neo4j/embeddings/Helius are optional enhancements.
- New analyzers: add a node under
src/graph/nodes/, wire it into the ANALYZE fan-out insrc/graph/build-graph.ts, and appendFindings. - New tools: add under
src/tools/. Remote tools can be surfaced via the Model Context Protocol (MCP) with the same load/run/normalize shape. - New knowledge sources: implement the
Retrieverinterface (src/retrieval/types.ts) and add it tocreateHybridRetriever.