Everything you need, right here.
The essential VulnForge guides — install, first launch, privacy model, AI routing, architecture, threat model. Excerpted from the in-repo docs/ tree so you can read without leaving the site.
operator
Install
Three artifacts, one codebase. Desktop installer, Docker image, bare-metal tarball.
source: docs/operator/
Desktop (Solo)
Everything bundled — frontend, backend, MCP, SQLite, scanner. Runs offline.
- Download the installer from Releases
- Launch — pick “Solo” at first-run
- Paste a repo URL on Hunt
No network required. Data defaults to private scope.
Docker server (Team)
Easiest team install. Container + named volume. Migrations auto-apply.
git clone https://github.com/AsafMeizner/vulnforge.git
cd vulnforge
cp .env.server.example .env.server
# Set VULNFORGE_PUBLIC_URL, JWT_SECRET, BOOTSTRAP_TOKEN
docker compose -f docker-compose.server.yml \
--env-file .env.server up -d
docker logs vulnforge-server -f
# note the bootstrap token it printsContainer listens on :3001. Volume vulnforge-data at /data.
Bare-metal server
VM / physical host / Windows Server. Native systemd service or Windows service.
# Linux
wget https://.../vulnforge-server-0.1.0.tar.gz
tar xf vulnforge-server-0.1.0.tar.gz
cd vulnforge-server-0.1.0
sudo ./scripts/install-server.sh
# Windows (PowerShell admin)
.\scripts\install-server.ps1Requires Node ≥ 20, Python ≥ 3.10, git, root access.
user
First launch
When you open VulnForge for the first time, the app asks one question: "Solo or Team?"
source: docs/user/first-launch.md
Solo
Individual researcher, or kicking the tires.
- ·Everything (frontend, backend, scanner, MCP, DB) runs on your machine.
- ·No network required.
- ·All data is private by default — never leaves your device.
- ·You can switch to Team mode later.
After picking Solo you land on Hunt, ready to paste a repo URL.
Team
Your company runs a VulnForge server.
- ·Enter the public URL (e.g. https://vulnforge.acme.corp).
- ·First admin: paste the one-time bootstrap token, create an admin account.
- ·Returning members: SSO or username/password.
- ·Desktop stays local-first — you keep working through WiFi drops.
Your SQLite + MCP + scanner still runs locally. The server is for sharing, not for being the only place data lives.
Why local-first in team mode?
private never leaves your device.user
Privacy scopes
Every finding, project, note, and report has a scope that controls who can see it.
source: docs/user/privacy-scopes.md
privateStays on your desktop. Never syncs anywhere.
use for
- · Personal research notes you're not ready to share.
- · Findings in client codebases under NDA.
- · Half-baked hypotheses you'll refine first.
teamSyncs to the team server; every authenticated team member sees it.
use for
- · Production findings.
- · Shared projects.
- · Completed reports.
- · Any work that benefits from collaboration.
poolOpt-in, anonymized, shared across organizations.
use for
- · Interesting CVE patterns for the broader community.
- · Public-OSS findings where collective intelligence helps.
- · Automatic anonymization before submission.
Anonymization is automatic for pool rows
owner_user_id is stripped. URLs reduce to scheme + host + path (no query params, no auth). File paths become basenames (/home/alice/code/secret/util.c → util.c). Preview a pool submission before committing.user
AI routing
VulnForge routes AI tasks (triage, verify, deep-analysis, embed, summary) to providers per your rules.
source: docs/user/ai-providers.md
Local providers
Server providers
provider picker looks like
Local: claude-opus
Local: ollama-local
Server: team-triage ← invoked via server proxy
Server: team-deep ← invoked via server proxyMix freely: triage → Server: team-triage and deep-analysis → Local: claude-opus. Server-proxied calls go through POST /api/server/ai/invoke — the server looks up the provider by name and invokes upstream with server-side credentials. Audits record who asked.
architecture
Architecture
Multi-layer platform. Three artifacts, two operational modes, one codebase.
source: docs/architecture/
┌─────────────────────────────────────────────────────────────────┐
│ React 19 frontend · 22 pages · hash routing │
│ Hunt / Findings / Investigate / Exploits / Runtime / AI / ... │
└───────────────────────────────┬─────────────────────────────────┘
│ REST + WebSocket
┌───────────────────────────────▼─────────────────────────────────┐
│ Express backend (server/index.ts) │
│ │
│ ┌─ Auth ─────┬─ Sync ─────┬─ MCP ─────┬─ Pipeline ─┬─ Runtime ─┐│
│ │ JWT+refresh│ WS + REST │ 101 tools │ Clone→…→ │ libFuzzer, ││
│ │ bcrypt+OIDC│ conflict │ SSE+JSON- │ verify │ gdb, nmap, ││
│ │ RBAC │ resolution │ RPC │ │ Docker,QEMU││
│ └────────────┴────────────┴───────────┴───────────┴────────────┘│
│ │
│ ┌─ Plugins manager ───────┬─ Integrations ──┬─ Notes providers ─┐
│ │ Semgrep, Trivy, CodeQL │ Jira/Trello/ │ Local / Obsidian │
│ │ + custom plugins │ Slack/Linear/GH │ │
│ └─────────────────────────┴─────────────────┴───────────────────┘│
└───────────────────────────────┬─────────────────────────────────┘
│
▼
SQLite · 39 tables · 3 sync categoriesCategory A
Syncable
Rows carry 7 sync columns (sync_id, sync_scope, owner_user_id, updated_at_ms, server_updated_at_ms, tombstone, sync_status). Tables: projects, vulnerabilities, scan_findings, pipeline_runs, notes, reports, checklists…
Category B
Per-machine
Never sync. Device-only state: local plugin binaries, cache tables, OS-specific paths, windowing preferences. Stays on each machine.
Category C
Server-only
Only exists server-side: users, refresh_tokens, audit_log, oidc_providers, sync_snapshots. Desktop never stores these.
Local-first sync
security
Threat model + hardening
VulnForge holds sensitive findings, exploit code, and vendor comms. A 27-finding pre-release audit closed every CRITICAL + HIGH item before v0.1.0 shipped. Every security decision is guided by an explicit threat model.
source: docs/security/threat-model.md
Compromised user account
device-level attacker
Refresh tokens bound to device_id, rotated on every use. Replay of an old refresh token revokes the whole device session. Access token 15-minute TTL. JWT pins iss + aud so a future secret-reuse can't cause token confusion. Viewer role is read-only across /api/* by default.
Malicious insider
legitimate admin, bad faith
Audit log records every admin action (role changes, permission grants, token revocations). Granular per-role permissions — use 'researcher' for normal operators, 'admin' only for ops. Logs shippable to external SIEM.
External attacker
no credentials, public endpoint
TLS via reverse proxy (nginx). CORS refuses the literal '*' when credentials:true (CSRF vector). WebSocket upgrades gated by origin allowlist. Rate-limited login. Passwords compared via crypto.timingSafeEqual. bcrypt cost 12.
Compromised upstream
OpenAI / Jira / Slack / OIDC IdP
Every outbound URL goes through an SSRF guard that rejects RFC1918, loopback (server mode), CGNAT, cloud metadata, and IPv4-mapped-v6 bypasses with DNS resolved inside the guard. OIDC id_tokens are JWKS-signature-verified with iss / aud / exp / nonce checks. Integration tokens stay on the server.
Attacker-controlled code under analysis
a scanned repo plants malicious content
Every AI prompt (triage, verify, report, remediation, 4-stage deep-triage, root-cause clusterer, agent loop) runs each untrusted field through fenceUntrusted() inside <untrusted_*> tags, with a system-prompt preamble that tells the model to ignore any directives found inside fences. A planted '/* IGNORE PREVIOUS INSTRUCTIONS */' can't hijack the triage.
DB file leak
backup tape walks off
API keys, OIDC client secrets, and integration configs are encrypted at rest with AES-256-GCM under a vf1: envelope. Master key comes from VULNFORGE_DATA_KEY env or a chmod-600 keyfile; server-mode refuses to start without one. DB leak alone no longer hands over live credentials.
Secret handling at a glance
VULNFORGE_JWT_SECRET— 48 bytes, base64, in/var/lib/vulnforge/.env. Pins iss + aud on every issued JWT.VULNFORGE_DATA_KEY— 32 raw bytes (64 hex chars). Encrypts AI keys + OIDC client secrets + integration configs at rest. Env first, then<dataDir>/master.key(chmod 600). Server-prod refuses to start without one.VULNFORGE_BOOTSTRAP_TOKEN— 24 bytes hex, one-time, deleted from process env after first admin creation.- Password hashes — scrypt +
timingSafeEqual. Refresh tokens — bcrypt cost 6 (raw token carries 256 bits of entropy). - DB file permissions:
600owned by service user. Encryption key NEVER inside the DB. - Electron IPC
open-pathrefuses executables and paths outside the userData sandbox or prior dialog picks.
developer
Author an MCP tool
VulnForge exposes 101 tools to external AI agents via the Model Context Protocol (SSE + JSON-RPC).
source: docs/developer/mcp-tools.md
All tools live in a single file: server/mcp/tools.ts. Each tool follows the same shape — a Zod schema validates arguments, the handler calls into existing server-side code, and the return value streams back to the agent.
import { z } from "zod";
server.tool(
"tool_name",
"One-line description shown to the agent",
{
arg1: z.string(),
arg2: z.number().optional(),
},
async ({ arg1, arg2 }) => {
// Call into existing server-side code
const result = await doTheThing(arg1, arg2);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);Naming conventions
pick the right prefix
list_* → arrays (paginate big sets). get_* → single record by id. create_* / update_* / delete_* → writes. Consistency across 101 tools keeps agents predictable.
Don't duplicate logic
call the same functions the UI uses
Tools should call DB helpers, sync repo functions, pipeline methods — not re-implement them. Bug fixes and sync invariants stay in one place.