Skip to content
VulnForge
guides

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.

  1. Download the installer from Releases
  2. Launch — pick “Solo” at first-run
  3. 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.

bash
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 prints

Container listens on :3001. Volume vulnforge-data at /data.

Bare-metal server

VM / physical host / Windows Server. Native systemd service or Windows service.

bash
# 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.ps1

Requires 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?

Your desktop keeps its own SQLite + MCP + scanner stack even when connected to a team server. You can work through WiFi drops, planes, coffee shops. Scans that need your laptop's GPU or specific tools run locally. Sensitive data tagged 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

🔒private

Stays 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.
👥team

Syncs to the team server; every authenticated team member sees it.

use for

  • · Production findings.
  • · Shared projects.
  • · Completed reports.
  • · Any work that benefits from collaboration.
🌐pool

Opt-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

API keys you paste into your desktop. Keys stay on your machine, never sync. Examples: your personal Claude API key, a local Ollama on your GPU, OpenAI with your own billing.

Server providers

In team mode, your company's server exposes its own pool. Keys live on the server; clients invoke by name without ever seeing them. Used for cost control, privacy, and capability pooling.

provider picker looks like

text
Local: claude-opus
Local: ollama-local
Server: team-triage       ← invoked via server proxy
Server: team-deep         ← invoked via server proxy

Mix 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 categories

Category 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

The server is the source of truth for conflict ordering only, not for the data itself. Writes accumulate in each desktop's sync outbox, reconcile on reconnect. Desktop keeps its own SQLite, MCP server, plugin manager, and pipeline engine — even in team mode.

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: 600 owned by service user. Encryption key NEVER inside the DB.
  • Electron IPC open-path refuses 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.

typescript
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.