# Agent Mode Source: https://docs.vigolium.com/agentic-scan/agent-mode Vigolium ships seven agent subcommands under `vigolium agent` covering autonomous scanning, AI-guided pipelines, a unified source-audit driver, single-shot prompts, single-finding triage, and an interactive TUI. Vigolium ships **seven agent subcommands** under `vigolium agent`. They split into four families: * **Agentic scan modes**: autonomous or AI-guided vulnerability scanning: `autopilot`, `swarm` * **Source audit mode**: multi-phase AI code audit: `audit` (unified driver for the embedded vigolium-audit harness and/or the standalone piolium harness, selected with `--driver`) * **Single-shot / interactive**: `query`, `olium` * **Utility**: `session`, `triage` The parent `vigolium agent` command itself only supports `--list-templates` and `--list-agents`. All execution requires a subcommand. Every agent invocation is dispatched through the in-process **olium runtime** (`pkg/olium/`). There are no subprocess SDK or ACP backends, provider selection lives at `agent.olium.provider` in `vigolium-configs.yaml`. See [How It Works](/agentic-scan/how-it-works) for the provider list. *** ## When to use what | You want to... | Use | Why | | ------------------------------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Run a one-off prompt against code or a target (no scanning loop) | `query` | Single-shot, template-driven, returns structured findings or HTTP records | | Hand the agent the wheel for a full autonomous pentest | `autopilot` | Autonomous operator with full tools; optionally rotate context and verify candidates in durable mode | | Have the AI **drive the native scanner** on a specific target | `swarm` | Master/worker pipeline: AI plans → native modules execute → optional triage+rescan | | Audit source code (secrets, SAST triage, PoC) before or alongside a scan | `audit` | Unified driver dispatcher: drives the embedded vigolium-audit harness, piolium, or both (`--driver auto\|both\|audit\|piolium`), with per-driver child AgenticScan rows + post-pass findings dedup | | Chat with an LLM interactively in a TUI (debug, explore, ad-hoc) | `olium` | Real-time multi-turn chat; not a security scan, general-purpose agent | | Review past agent runs | `session` | Lists prior runs, shows raw output and artifacts | | Confirm or downgrade a single existing finding with AI | `triage` | Runs the AI triage flow against one finding by id and updates its verdict/severity | *** ## Mode reference ### `query`: single-shot prompt * **Use for:** code review, endpoint discovery, secret detection, ad-hoc prompts. * **Not for:** network scanning or multi-phase orchestration. * **Key flags:** `--prompt-template`, `-p/--prompt`, `--stdin`, `--source`, `--files`, `--source-label`, `--output`, `--dry-run`, `--provider`, `--model`, `--oauth-cred`, `--oauth-token`, `--llm-api-key`. ### `autopilot`: autonomous agentic scan * **Use for:** pentest-style engagements where you want the agent to be creative and decide what to do. * **How it works:** one autonomous operator runs until it calls `halt_scan` or hits limits. `legacy` keeps one conversation; `shadow`/`enforced` rotate bounded sections and persist resumable state. When `--source` is provided, vigolium-audit runs first to prepare a frozen whitebox context bundle. * **Key flags:** `[prompt]`/`--prompt`/`--prompt-file`, `-t/--target`, `--input`, `--burp-bridge-url`, `--prior-context {auto|summary|off}`, `--knowledge-base`, `--plan-file`, `--source`, `--max-duration`, `--intensity {quick|balanced|deep}`, `--audit {lite|balanced|deep|off}`, `--resume`, `--session-dir`, `--transcript`, `--triage`, `--diff`, and `--last-commits`. * **Throwaway runs:** `-S`/`--stateless` runs the whole autopilot into a throwaway temp DB (project DB untouched, like `vigolium scan -S`) and materializes `--format` outputs from it — `console`, `jsonl`, `html`, `report`, `pdf`, `sarif`, `sqlite`, `fs`, with `-o`/`--output` as the shared base (default `vigolium-result/vigolium-autopilot`). A file-producing `--format` is an error **without** `-S`; `-S` is rejected with `--db`, `--db-isolate`, and `--resume`. See [Autopilot](/agentic-scan/autopilot). ### `swarm`: AI-guided multi-phase scan * **Use for:** target-specific scanning when you want structure (planning → native scan → triage), source-aware route discovery, or verification loops. * **How it works:** 10-phase pipeline, normalize → auth (opt) → source-analysis (opt) → code-audit (opt) → discover (opt) → plan (AI) → extension → native scan → triage (opt) → rescan (opt). * **Key flags:** `[prompt]`/`--prompt`/`--prompt-file`, `-t/--target` (required with `--source`), `--input`, `--plan-file`, `--record-uuid`, `--source`, `--discover`, `--code-audit`, `--triage`, `--max-iterations`, `-m/--modules`, `--vuln-type`, `--audit {lite|balanced|deep}`, `--intensity`, `--only`/`--skip`/`--start-from`, and `--with-extensions`. `swarm` does not yet carry `-S/--stateless`. See [Swarm](/agentic-scan/swarm). ### `audit`: unified source-audit driver * **Alias:** `vigolium audit` is a top-level alias for `vigolium agent audit` (identical flags). * **Use for:** running the embedded vigolium-audit harness and/or the standalone piolium harness against a single source tree under one AgenticScan, with per-driver session subdirs (`{session}/audit/`, `{session}/piolium/`), per-driver child AgenticScan rows under one parent, and a post-pass project-wide findings dedup once both drivers exit. * **Driver values (`--driver`):** * `auto` (default) — run vigolium-audit; fall back to piolium only if the `claude`/`codex` CLI required by audit is missing. * `both` — run vigolium-audit then piolium back-to-back, unconditionally. * `audit` — run only the embedded vigolium-audit harness. * `piolium` — run only the standalone piolium harness (no audit, no fallback). * **Modes:** when the run includes both drivers (`auto` or `both`), `--mode` is restricted to the shared set: `lite`, `balanced`, `deep`, `revisit`, `confirm`, `merge`. Use `--driver=piolium` for `longshot`/`smoke` or `--driver=audit` for `mock`. `--intensity deep` expands to the chain `deep,confirm`. * **Agent selection (audit leg only):** `--provider ` resolves the agent **and** forwards that provider's BYOK auth (`anthropic-*` → claude, `openai-*` → codex). `--agent {claude|codex}` is a pure agent selector layered on top of `--provider` (rejected for invalid values; warned under `--driver=piolium`). * **Key flags:** `--driver {auto|both|audit|piolium}`, `--mode`, `--modes` (chain), `--intensity {quick|balanced|deep}`, `--source `, `--commit-depth`, `--interactive`, `--no-stream`, `--no-dedup`, `--upload-results`, `--provider`, `--agent`, plus the `--pi-*` and `--plm-*` passthroughs for the piolium leg. `--list-modes` prints the embedded vigolium-audit binary's mode graph and exits. * **Raw output (audit leg):** `--keep-raw` is **on by default** (CLI), retaining `/vigolium-results/` in the source tree; `--clean-raw` removes it after the run (the session copy is always kept). `--keep-raw`+`--clean-raw` is an error. The REST `keep_raw` default is unchanged (off). * **One-shot report:** `-S`/`--stateless` runs the audit into a throwaway temp DB (main DB untouched, like `vigolium scan -S`) and auto-writes a self-contained HTML report to `vigolium-result/vigolium-audit-report.html` (override with `-o`/`--output`; supports `gs://` and `{ts}`). Not valid with `--interactive`. * **REST equivalent:** `POST /api/agent/run/audit` with `driver: "auto"|"both"|"audit"|"piolium"` (default `"auto"`). See [Agentic Security Audit](/getting-started/agentic-security-audit). There is no standalone `vigolium agent piolium` subcommand. Piolium runs only through the audit dispatcher, `vigolium agent audit --driver piolium` (piolium alone), `--driver both` (audit then piolium), or `--driver auto` (piolium as fallback when the `claude`/`codex` CLI is missing). Piolium-only modes (`longshot`, `smoke`) and passthroughs (`--pi-*`, `--plm-*`) are exposed on `agent audit`. ### `olium`: interactive TUI chat * **Aliases:** top-level `vigolium olium` / `vigolium ol`. * **Use for:** interactive debugging, exploration, or one-shot non-interactive prompts. Provider-agnostic. * **Not for:** orchestrated scanning, there are no scan phases. * **Key flags:** `--provider`, `--model`, `--llm-api-key`, `--oauth-cred`/`--oauth-token`, `--system`, `-p/--prompt` (one-shot non-interactive), `--stdin`. ### `session`: agent run history * **Aliases:** `sessions`, `sess`. * **Use for:** auditing prior runs, debugging failed scans. * **Key flags:** `--mode {query|autopilot|swarm|audit}`, `-n/--limit`, `-o/--offset`, `--tail`, `--full`. ### `triage`: single-finding confirmation * **Use for:** confirming or downgrading one existing finding with AI, without launching a full scan. * **How it works:** `vigolium agent triage [finding-id]` loads the stored finding (and its linked request/response), runs the AI triage flow against it, and updates the finding's verdict/severity in place. * **Not for:** discovering new issues, it only re-evaluates a finding you already have. *** ## Picking between `autopilot` and `swarm` Both are agentic scan modes. The distinction: * **`autopilot`**: the agent **is** the scanner. It opens a shell, reads files, runs tools, and decides everything. Best when the target is fuzzy or you want creative, exploratory testing. * **`swarm`**: the agent **directs** the native scanner. It plans, picks modules, generates JS extensions, and the deterministic Go pipeline does the heavy traffic. Best when you want structured, repeatable results with optional verification loops. If you have **source code** and a **target URL**, both work; `swarm --source --target ... --code-audit --triage` gives you the most structured output, while `autopilot --source ...` gives the agent more freedom (and runs a vigolium-audit prep first). *** ## Cross-cutting * **Session dir:** `~/.vigolium/agent-sessions/` (override via `agent.sessions_dir` in `vigolium-configs.yaml`). * **Durable autopilot:** set `agent.olium.autopilot_mode` to `shadow` or `enforced`; `legacy` remains the default. * **Prompt templates:** `~/.vigolium/prompts/` or embedded under `public/presets/prompts/`. * **Output schemas:** `findings`, `http_records`, `attack_plan`, `triage_result`, `source_analysis`. * **Engine:** every agent run is dispatched through the in-process olium runtime (`pkg/olium/`). Provider selection lives at `agent.olium.provider` in `vigolium-configs.yaml`, see [How It Works](/agentic-scan/how-it-works) for the provider list. * **Source flag:** `--source` is the canonical source-code flag across all modes; the legacy `--repo`/`--repo-url`/`--source-url` flags have been removed. * **Task guidance:** autopilot and swarm use `--prompt` or the positional prompt. Their former CLI flags `--focus`, `--instruction`, `--instruction-file`, `--browser`, and `--credentials` are removed; put focus and login details in the prompt. Structured equivalents remain available in the REST API. *** ## REST API The server exposes the run modes plus a status/artifact surface so a controller can launch and tail runs without the CLI. | Method | Path | Purpose | | ------ | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | POST | `/api/agent/run/query` | One-shot prompt execution. | | POST | `/api/agent/run/autopilot` | Launch an autopilot scan. | | POST | `/api/agent/run/swarm` | Launch a swarm scan. | | POST | `/api/agent/run/audit` | Unified audit driver dispatcher, `driver: "auto"\|"both"\|"audit"\|"piolium"` (default `auto`). | | GET | `/api/agent/status/list` | List active and historical runs (DB + in-memory merge). | | GET | `/api/agent/status/:id` | Status of a single run. | | GET | `/api/agent/sessions` | Paginated session history (richer than `/status/list`). | | GET | `/api/agent/sessions/:id` | Full session detail incl. raw output, plan, child runs. | | GET | `/api/agent/sessions/:id/logs` | Read or tail `runtime.log` (SSE when `Accept: text/event-stream`). | | GET | `/api/agent/sessions/:id/artifacts` | List files inside the session\_dir (recursive, capped at 500 entries). | | GET | `/api/agent/sessions/:id/artifacts/{name}` | Read one file. Wildcard supports nesting (`vigolium-audit/state.json`). Optional `?max_bytes=N` cap (default 10 MiB, hard cap 100 MiB). | | POST | `/api/agent/chat/completions` | OpenAI-compatible chat completions (synchronous). | Run endpoints return `202 Accepted` with `{agentic_scan_uuid, status: "running"}` and execute in the background. Expected workflow: 1. `POST` one of the `run/*` endpoints, capture `agentic_scan_uuid`. 2. Poll `GET /api/agent/status/:id` until `status` leaves `running`. 3. Fetch the session artifacts via `/api/agent/sessions/:id/logs`, `/artifacts`, or `/artifacts/{name}` for the raw outputs (`output.md`, `swarm-plan.json`, `audit-stream.jsonl`, generated extensions, etc.). The `audit` endpoint dispatches sequentially when more than one driver runs (`auto` with fallback or `both`), and multiplexes SSE chunks with a `driver` field bracketed by `driver_start`/`driver_end` markers when `stream: true`. See [Agentic Security Audit](/getting-started/agentic-security-audit) for the unified-driver overview. Set `stream: true` on the run endpoints to opt into Server-Sent Events instead of the async response, most consumers should stick with the async flow and tail logs on demand. ### Provider overrides The CLI exposes per-invocation provider flags (`--provider`, `--model`, `--oauth-cred`, `--oauth-token`, `--llm-api-key`, `--system`). The server falls back to `agent.olium.*` in `vigolium-configs.yaml` (which keeps warm sessions and prompt caches stable across requests), but every agent run endpoint also accepts per-request BYOK credentials (`api_key`, `oauth_token`, `oauth_cred_file`, `oauth_cred_json`). The audit dispatcher additionally accepts `audit_auth` and `piolium_auth` for per-driver overrides. See [API Reference, Agent](/api-references/agent) for the full request/response schemas. # Agent Autopilot Source: https://docs.vigolium.com/agentic-scan/autopilot The autonomous operator: run in legacy mode or opt into durable context rotation, candidate verification, and resumable state. `vigolium agent autopilot` is the autonomous agentic scan. One operator decides what to investigate, drives tools (Bash, file I/O, web fetch, browser probes, and first-class Vigolium tools), and halts when it has nothing productive left to do. The default `legacy` mode keeps one growing conversation. The opt-in `shadow` and `enforced` modes split the run into bounded sections, rebuild each section from durable state, and verify finding candidates in a fresh context. There is still no master/worker phase pipeline; use [Swarm](/agentic-scan/swarm) when you want that structure. *** ## Mental model Think of it as a security analyst sitting at a terminal: * The analyst is given a target URL, optional source and reference documentation, and task guidance. * They have shell access, file-read access, web access, and the vigolium CLI. * They can mine traffic and findings already stored in the project, follow leads, record evidence, and stop when there is nothing more worth digging into. The autopilot is exactly that, except the analyst is an LLM and the durable notebook is the project database plus the run's session directory. *** ## Lifecycle (high-level) ``` ┌──────────────────────────────────────────────┐ vigolium agent ─►│ 1. CLI flag parsing │ autopilot … │ intensity preset → max-cmds, timeout, … │ │ │ │ --input → curl/HTTP/Burp/url normalize │ │ --source → resolve git URL/diff/local │ │ --knowledge-base → compact doc brief │ │ --prior-context → existing project data │ │ --provider/model → olium.ResolveProvider │ └──────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ 2. Session bootstrap │ │ EnsureSessionDir(~/.vigolium/agent-…/UUID)│ │ WriteRunPID, CleanupOrphanedProcesses │ │ create AgenticScan row (status=running) │ │ tee stdout → {session}/runtime.log │ └──────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ 3. autopilot.Run (pkg/olium/autopilot) │ │ build system prompt + initial user prompt │ │ register tools: builtins + halt_scan + │ │ finding tool + load_skill │ │ legacy: one growing conversation │ │ durable: reconstructed sections │ └──────────────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ ▼ ▼ ┌────────────────┐ ┌─────────────────┐ │ provider │ multi-turn │ tool registry │ │ (codex / │◄────────────►│ bash, read, │ │ anthropic / │ tool calls │ write, edit, │ │ openai / …) │ │ ls, grep, glob, │ └────────────────┘ │ web_fetch, │ │ load_skill, │ │ halt_scan, │ │ report_finding │ │ or propose_… │ └─────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ 4. Halt → verify → finalize │ │ halt_scan called OR ctx done OR max turns │ │ durable candidates get fresh-context │ │ verification before promotion │ │ UPDATE AgenticScan: status, duration, │ │ finding_count, error_message │ │ print summary, remove run.pid │ └──────────────────────────────────────────────┘ ``` *** ## Execution modes Set `agent.olium.autopilot_mode` in `vigolium-configs.yaml`: | Mode | Context behavior | Finding behavior | | ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | | `legacy` (default) | One growing conversation. No section state is written. | `report_finding` writes directly to the findings table. | | `shadow` | Rotates through bounded sections and persists section state. | Findings still land directly, while mirrored candidates are independently graded for comparison. | | `enforced` | Rotates through bounded sections and persists section state. | `propose_candidate` replaces `report_finding`; only verifier-confirmed candidates are promoted. | ```yaml theme={null} agent: olium: autopilot_mode: enforced ``` Durable sections rotate after 40 turns or 12 consecutive turns without progress. A new section resets the engine and reconstructs a concise brief from the scratchpad, previous closing note, candidate ledger, and recent actions. Section lifecycle events are appended to `transcript.jsonl`, and rows are stored in `agent_sections` and `agent_finding_candidates`. Use `shadow` first when you want to compare verifier decisions without changing which findings land. Use `enforced` when you want verification to gate finding creation. *** ## CLI ```bash theme={null} # Basic autonomous scan vigolium agent autopilot -t https://example.com # With source code (auto-runs vigolium-audit first) vigolium agent autopilot -t http://localhost:3000 --source ~/projects/my-app # Tighter scope with intensity preset vigolium agent autopilot -t https://example.com --intensity quick # Authenticated deep pentest (browser tooling is always available) vigolium agent autopilot -t https://app.example.com --intensity deep \ --prompt "log in as admin/admin123, then test IDOR and privilege escalation" # Focus a specific area with explicit task guidance vigolium agent autopilot -t https://api.example.com --prompt "focus on auth bypass" # Pipe a curl command — target auto-derived echo "curl -X POST https://example.com/api/login -d '{\"user\":\"admin\"}'" \ | vigolium agent autopilot # Raw HTTP / Burp pair input vigolium agent autopilot --input "POST /api/search HTTP/1.1\r\nHost: example.com\r\n\r\nq=test" # Source-aware on changed files only vigolium agent autopilot -t https://example.com --source ./app \ --diff main...feature/payments # Last 5 commits as focus vigolium agent autopilot -t https://example.com --source ./app --last-commits 5 # Use a different olium provider vigolium agent autopilot -t https://example.com \ --provider anthropic-api-key --model claude-opus-4-7 # Natural-language prompt (parsed by AI to extract target/source/focus) vigolium agent autopilot "scan VAmPI source at ~/src/VAmPI on localhost:3005" # Mine existing project traffic and findings (auto is the default) vigolium agent autopilot -t https://example.com --prior-context auto # Pull live Burp history before building the prior-context brief vigolium agent autopilot -t https://example.com \ --burp-bridge-url http://127.0.0.1:9009 # Add app documentation without flooding the operator context vigolium agent autopilot -t https://example.com --knowledge-base ./app-docs # Resume the same durable run identity and state vigolium agent autopilot --resume \ --prompt "finish the remaining authorization checks" # Pin and export debug artifacts vigolium agent autopilot -t https://example.com \ --session-dir ./debug/run-1 --transcript ./debug/run-1.jsonl # Dry-run: preview the rendered system prompt vigolium agent autopilot -t https://example.com --dry-run # CI scan with quick intensity vigolium agent autopilot -t https://staging.example.com \ --intensity quick --upload-results ``` ### Key flags | Flag | Default | Description | | -------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-t, --target` | , | Target URL (derived from `--input` if omitted) | | `--input` | , | Raw input (curl, raw HTTP, Burp XML, base64, URL). Reads stdin if piped | | `--record-uuid` | , | Use an HTTP record from the database as the seed input | | `--prompt` / positional `[prompt]` | , | Free-text task guidance. With explicit target/source flags it stays verbatim; otherwise the intent parser also derives structured fields from it. | | `--prompt-file` | , | Read the task guidance from a file — the **same** channel as `--prompt`, so a long prompt needn't be shell-escaped. Pass the guidance once: combining it with `--prompt` or the positional prompt is an error. | | `--plan-file` | , | Prose plus raw HTTP request blocks. Owns task guidance and seed input, so it cannot be combined with `--input`, `--record-uuid`, or a prompt. | | `--burp-bridge-url` | `$VIGOLIUM_BURP_BRIDGE_URL` | Import live Burp Proxy history into the project before prior context and pre-scan are built. | | `--prior-context` | `auto` | Existing project traffic/findings briefing: `auto` (bounded table), `summary` (one-line pointer), or `off`. | | `--knowledge-base` | , | Markdown/text file or directory describing auth, roles, and business logic. Adds a distilled brief plus on-demand document index. | | `--knowledge-base-raw` | false | Skip KB distillation and use the deterministic document index only. | | `--knowledge-base-no-traffic` | false | Treat every knowledge-base file as prose. By default, HTTP-traffic exports found there (HAR, Burp XML, curl, OpenAPI/Swagger, Postman, URL lists, raw HTTP) are parsed and ingested as project traffic. | | `--source` | , | Path to application source code (or git URL, auto-cloned). Auto-runs vigolium-audit. | | `--files` | , | Specific files to include (relative to `--source`) | | `--intensity` | `balanced` | Preset bundle: `quick` / `balanced` / `deep`. Sets the turn budget (quick=150, balanced=500, deep=1500), audit mode, browser, and pre-scan strategy | | `--audit` | intensity preset (`balanced` by default) | Vigolium-audit mode: `lite` / `balanced` / `deep` / `mock` / `off` | | `--diff` | , | Focus on changed code (PR URL, `main...branch`, `HEAD~N`) | | `--last-commits` | , | Shorthand for `--diff HEAD~N` | | `--max-duration` | 6h | Wall-clock cap (intensity preset sets this — quick=1h, balanced=6h, deep=12h) | | `--triage` | false | Run an AI triage pass over findings after the scan (confirm real issues vs false positives) | | `--skill` | , | Force-load these skills by name, bypassing the pre-flight selection (repeatable or comma-separated) | | `--skill-tag` | , | Force-load every skill carrying one of these tags (e.g. `xss,idor`) | | `--no-skill-filter` | false | Load the full skill set; skip the pre-flight skill selection | | `--db-isolate` | false | Scan into a private temporary database, then merge results into `--db` at the end (lets parallel runs share one DB; SQLite only) | | `-S, --stateless` | false | Run the whole autopilot into a throwaway temporary database, then materialize `--format` outputs from it. Mirrors `vigolium scan -S`. See [Throwaway runs and reports](#throwaway-runs-and-reports--s---stateless). | | `--format` | `console` | Comma-list of outputs to write from a `-S` run: `console`, `jsonl`, `html`, `report`, `pdf`, `sarif`, `sqlite`, `fs`. A file-producing format is an error without `-S`. | | `-o, --output` | `vigolium-result/vigolium-autopilot` | Shared base path for the `-S` export; each `--format` appends its own extension. Only applies with `-S`. | | `--piolium` | , | Run piolium audit instead of vigolium-audit when `--source` is set (`lite`/`balanced`/`deep`/`longshot`/…); empty auto-picks piolium when `pi` is installed | | `--no-prescan` | false | Skip the native pre-scan that seeds http\_records before the operator (target-only runs; no-op with `--source`) | | `--no-preflight-discovery` | false | Skip pre-flight discovery and OpenAPI/Swagger ingestion. | | `--no-post-halt-verify` | false | Accept `halt_scan` without the route-gap probe and possible operator re-entry. | | `--resume` | , | Resume a durable run by AgenticScan UUID; restores its run identity/state and skips audit and pre-scan preparation. | | `--session-dir` | generated | Pin the directory for `transcript.jsonl`, `runtime.log`, scratchpad, and tool results. | | `--transcript` | , | Copy the completed transcript to another path while retaining the in-session copy. | | `--headed` | false | Hidden debugging flag that shows browser windows for pre-scan, probes, and browser subprocesses. | | `--upload-results` | false | Upload session bundle to cloud storage on completion | | `--provider` | *(config)* | Olium provider override | | `--model` | *(config)* | Model id override | | `--oauth-cred` / `--oauth-token` / `--llm-api-key` | *(config)* | Provider credential overrides | | `--system-prompt` / `--system-prompt-file` | , | Fully replace the built-in autopilot system prompt; the file takes precedence. | | `--dry-run` | false | Render prompt without launching agent | | `--show-prompt` | false | Print rendered prompt to stderr before executing | | `--disable-guardrail` | false | Skip the prompt-safety classifier on the natural-language prompt (use only when a known-good prompt is refused) | | `--post-halt-gap-threshold` | 5 | New `(method, URL)` routes the post-halt probe must find before the agent is re-entered (`0` = built-in default) | The CLI no longer exposes `--focus`, `--instruction`, `--instruction-file`, `--browser`, `--credentials`, or the other auth-intent flags. Put that information in `--prompt` (or the positional prompt). Browser tooling is always available, and the intent parser extracts login details and browser requirements. The REST API keeps its structured `focus`, `instruction`, `browser`, and credential fields for programmatic callers. ### Throwaway runs and reports (`-S/--stateless`) `-S/--stateless` runs the whole autopilot into a throwaway temporary database — your project DB is left untouched — then materializes `--format` outputs from it, mirroring `vigolium scan -S`. Use it for a one-off assessment you want as files rather than as project state: ```bash theme={null} vigolium agent autopilot --stateless \ --format sqlite,html,console \ -t https://app.example.com \ --intensity deep --prompt-file prompt.md ``` `--format` accepts a comma-separated list of `console`, `jsonl`, `html`, `report`, `pdf`, `sarif`, `sqlite`, `fs` — exactly the same set `vigolium scan -S` accepts, from the same shared list. With more than one format, `-o/--output` is a shared **base path** and each format appends its own extension; with no `-o`, the base defaults to `vigolium-result/vigolium-autopilot`. The exported `.sqlite` reopens directly: ```bash theme={null} vigolium finding -S --db vigolium-result/vigolium-autopilot.sqlite --min-severity high ``` The operator's own `vigolium` subprocesses inherit the throwaway database (via `VIGOLIUM_DB_PATH`), and so do the native scans it launches — so nothing the run does leaks into your project database. A file-producing `--format` (anything but `console`) is a **hard error without `-S`**. A persisted agentic run has no live output writer, so there is nothing to materialize — export the project DB afterwards instead: ```bash theme={null} vigolium export --format html,markdown -o report --agentic-scan ``` `-S` is rejected alongside `--db`, `--db-isolate`, and `--resume` — a resumed run restores its target, scratchpad, and prior findings from the project database, which a throwaway one does not have. Under a multi-app natural-language prompt, every app runs into the same scratch database and the combined result is exported once. *** ## API ``` POST /api/agent/run/autopilot ``` ```json theme={null} { "target": "https://example.com", "source": "/home/user/src/my-app", "focus": "API injection", "intensity": "balanced", "audit": "balanced", "max_commands": 500, "max_duration": "6h", "browser": false, "diff": "main...feature/payments", "stream": true } ``` `EffectiveSourcePath()` accepts either `source` or the legacy `repo_path` JSON field. The server resolves provider/model from `agent.olium.*` by default; pass `api_key`, `oauth_token`, `oauth_cred_file`, or `oauth_cred_json` on the same request to override per call. Returns `202 Accepted` with `{agentic_scan_uuid, status}`. Set `stream: true` for an SSE response. The REST schema still exposes structured `focus`, `instruction`, `browser`, and authentication fields. CLI-only orchestration flags added in v0.3.1—`--resume`, `--knowledge-base`, `--prior-context`, `--burp-bridge-url`, `--session-dir`, and `--transcript`—are not request fields on this endpoint. *** ## What the agent actually has access to The autopilot is **not** restricted to the vigolium CLI. The engine ships a generic agentic toolset, and — once a project database is wired — a set of first-class vigolium scanner/record tools on top of it. Much of the security-specific behavior still comes from the system prompt and skills, not just the tool surface. | Tool | Notes | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `bash` | Unsandboxed shell. Catastrophic patterns (e.g. `rm -rf /`) hard-rejected. | | `read_file` | Read-only. Parallelizable. | | `write_file` | Side-effect; serial only. | | `edit_file` | Side-effect; serial only. | | `ls` | Parallelizable. | | `grep` | Parallelizable. | | `glob` | Parallelizable. | | `web_fetch` | Under autopilot the capture variant persists an `http_record` per fetch (`http` mode returns a `record_uuid`; `browser` mode captures every XHR/fetch), so it is **not** read-only and runs serial — it is not part of the parallel fan-out. | | `load_skill` | Pulls a skill body by name (skills are indexed in the system prompt). | | `halt_scan` | Autopilot-only. Sets `HaltSignal`, engine exits naturally next turn. | | `report_finding` | Legacy/shadow modes. Writes a `Finding` row scoped to the AgenticScan UUID. Accepts `record_uuids[]` linking the proving `http_records`; shadow mode also mirrors a candidate for independent grading. | | `propose_candidate` | Enforced mode only. Persists the claim and evidence for a fresh-context verifier; only confirmed candidates are promoted to findings. | | `update_plan` / `remember` | Autopilot-only. Durable scratchpad plan and notes that survive section rotation and resume. | When a project database is wired (always the case for a real autopilot run) the agent also gets **first-class vigolium tools** from `pkg/olium/vigtool/`, so it doesn't have to shell out to the CLI for common scanner actions: | Tool | Notes | | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `run_native_scan` / `run_module` / `run_extension` / `list_modules` | Launch native scans, single modules, or JS extensions; enumerate the module catalog. | | `query_records` / `inspect_record` | Explore stored `http_records` and their insertion points. | | `replay_request` | Send a mutated record; persists the exchange as an `olium-replay` `http_record` and returns `replay_record_uuid` (feed it back into `report_finding` via `record_uuids`). | | `send_raw_http` | Exact-bytes socket primitive for smuggling / desync / CRLF; scope-gated to the target. | | `attack_kit` | Read-only, non-mutating starter-payload catalog per attack class. Its Redis gopher PoC is a harmless `PING` probe (v0.2.5 changed it from a destructive `FLUSHALL`), so the agent can't wipe target data. | | `oast_poll` / `oast_mint` | Poll for and mint OAST callbacks for blind payloads. | | `list_sessions` / `get_session` / `list_findings` / `update_finding` | Query and annotate prior runs and findings. | | `list_auth_sessions` / `auth_session_lookup` / `browser_auth` | Reuse auth sessions; `browser_auth` drives interactive login (only when `agent-browser` is on `$PATH`). | | `search_burp_items` / `inspect_burp_item` | Read-only, server runs only — search / inspect the live Burp site map or Proxy history over an operator-enabled bridge listener; never modifies Burp. | The model still decides when to invoke `vigolium scan-url`, `vigolium finding`, etc. via `bash` for anything not covered by a first-class tool. See the [olium tools reference](/agentic-scan/olium#tools) for the full surface. *** ## Provider selection `olium.ResolveProvider` picks the backend in this order: 1. CLI override (`--provider`) 2. Config file (`agent.olium.provider` in `vigolium-configs.yaml`) 3. Default → `openai-compatible` with `gemma4:latest` (a local Ollama endpoint) Supported provider IDs: | Provider | Typical model | Credential | | ----------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------ | | `openai-codex-oauth` | `gpt-5.5` | OAuth cred file (`--oauth-cred` / `agent.olium.oauth_cred_path`) | | `anthropic-api-key` | `claude-opus-4-7` | `--llm-api-key` / `$ANTHROPIC_API_KEY` | | `anthropic-oauth` | `claude-opus-4-7` | Bearer token from `claude setup-token` (`--oauth-token` / `$ANTHROPIC_API_KEY`) | | `openai-api-key` | `gpt-5.5` | `--llm-api-key` / `$OPENAI_API_KEY` | | `openai-responses` | `gpt-5.5` | `--llm-api-key` / `$OPENAI_API_KEY` (public OpenAI Responses API, `/v1/responses`) | | `anthropic-cli` | `claude-opus-4-7` | The local `claude` binary on `$PATH` (alias: `anthropic-claude-cli`) | | `anthropic-claude-sdk-bridge` | Claude Code default | Logged-in Claude Code subscription via the `vigolium-audit bridge` sidecar (`--bridge-bin` override); no key | | `anthropic-compatible` | via `custom_provider.model_id` | `custom_provider.base_url` (Anthropic Messages `/v1/messages` gateway / proxy) | | `anthropic-vertex` | `claude-opus-4-6` | GCP service-account JSON + project/location | | `google-vertex` | `gemini-2.5-pro` | Same GCP creds; routes `gemini-*` models | | `openai-compatible` | `gemma4:latest` | `custom_provider.base_url` (Ollama, OpenRouter, LM Studio, vLLM, …); `api_key` optional | Prompt caching (`EnablePromptCache: true`) is set on the engine; only the Anthropic providers and the Codex OAuth provider actually emit cache markers — `openai-compatible` (including Ollama), `openai-api-key`, and `openai-responses` ignore them. *** ## Intensity presets `--intensity` bundles several settings; explicit flags always override. | Preset | `MaxCommands` | `Timeout` | `Vigolium-audit mode` | `Browser` | | -------------------- | ------------- | --------- | --------------------- | --------- | | `quick` | 150 | 1h | `lite` | on | | `balanced` (default) | 500 | 6h | `balanced` | on | | `deep` | 1500 | 12h | `deep` | on | `MaxCommands` is the autopilot's own turn cap (the agent's `DefaultAutopilotMaxTurns` is 200; intensity overrides it). When the cap is hit the run ends with an error event — the model didn't get to halt cleanly. `agent.olium.max_turns` applies to the shorter, non-autopilot engine uses (swarm phases, source analysis, query), not to autopilot. *** ## Halt conditions The autopilot exits in one of four ways: 1. **Natural halt**: model calls `halt_scan`. The current turn is allowed to finish; the engine then sees no further tool calls on the next turn and emits `EventRunDone`. `Result.Halted=true`, `HaltReason` populated. 2. **Quiet halt**: model finishes a turn with no tool calls and no `halt_scan`. Treated as a natural stop. 3. **Max turns**: turn count hits `MaxCommands`. Engine emits an `EventError`; autopilot returns a non-nil error. 4. **Context cancelled**: timeout or SIGINT/SIGTERM. Engine teardown cancels in-flight tools. A separate **finding rate-limit** lives inside `report_finding` in legacy/shadow mode: * soft warning at 50 findings (still saved) * hard cap at 200 (rejected with an `IsError` result that nudges the model toward `halt_scan`) *** ## Findings and candidate persistence In `legacy` mode, every successful `report_finding` call writes a finding directly. `shadow` keeps that behavior and mirrors a candidate. In `enforced` mode, the operator writes only a candidate; the post-run verifier promotes confirmed candidates. Promoted and directly reported findings share these fields: * `ProjectUUID`, `ScanUUID`, `AgenticScanUUID`, propagate the project/scan scope so `vigolium finding` and `vigolium agent sessions` can join back. * `ModuleID = "olium-autopilot"`, `ModuleType = "ai-agent"`, `FindingSource = "autopilot"`, distinguishes agent-originated findings from scanner-module findings. * `FindingHash`: SHA-256 over (title, severity, source\_file, url, description-fingerprint), or over an explicit `dedup_key` if the model supplies one. The DB's `ON CONFLICT` handler squashes duplicates. * `HTTPRecordUUIDs`: the `record_uuids` array the model passes to link the `http_records` that prove the finding (from `replay_request`'s `replay_record_uuid`, `web_fetch`'s `record_uuid`, or `query_records`/`inspect_record`), so the evidence exchange survives the session and `vigolium finding --with-records` can rehydrate it. Persisted findings, candidates, sections, records, and scratchpad state survive a crash or timeout. A failed run updates the parent `AgenticScan` status but does not discard the work already saved. Use `--resume` in `shadow` or `enforced` mode to continue the same run. *** ## Session artifacts For each run, autopilot creates a UUID-named directory under `agent.sessions_dir` (default `~/.vigolium/agent-sessions/`): ``` ~/.vigolium/agent-sessions/{run-uuid}/ ├── run.pid # pgid + start time; cleared on exit ├── runtime.log # human-readable run/tool log ├── transcript.jsonl # full Pi-compatible conversation + section events ├── autopilot/ │ └── scratchpad.json # durable plan, stop criteria, and notes ├── tool-results/ # large tool outputs kept out of the prompt ├── knowledge-base-brief.md # distilled/indexed operator docs, when supplied ├── transcript-verify-*.jsonl # fresh-context verifier transcripts, durable modes └── vigolium-audit/ # source-audit preparation, when --source is provided ``` The run UUID matches the `AgenticScan.uuid` row, so `vigolium agent sessions` and `vigolium log ` both work without extra plumbing. Use `--session-dir` to pin this directory and `--transcript ` to copy the transcript after the run. Stale dirs older than 48h are swept on startup; orphan PID files are cleared during process cleanup. *** ## Source-aware mode When `--source` is set, three things change: 1. **Source resolution**: accepts local paths, git URLs (cloned to a temp dir), `--diff PR-url|ref...ref|HEAD~N`, and `--last-commits N`. The agent gets a local path and (optionally) a list of changed files. 2. **Initial prompt mode hint**: the prompt switches between blackbox ("probe the live target"), whitebox ("navigate the source tree"), or a greybox blend ("read the code to find what's risky, then probe"). 3. **Skill scope**: embedded skills + `~/.vigolium/skills/` are indexed in the system prompt; scan-specific skills like `audit-auth` and `triage-finding` are loadable via `load_skill`. Vigolium-audit also runs first (foreground) when `--source` is set, freezing its findings into `vigolium-audit/` for the operator to consult. *** ## Prior project context and Burp `--prior-context auto` is the default. Before this run's pre-scan, Vigolium summarizes data already in the active project: totals, up to 20 distinct endpoints, and up to 10 open findings. Larger projects get a pointer telling the operator to use `query_records` and `list_findings` for the rest, so prompt cost stays bounded. * `auto`: include the bounded endpoint/finding tables when prior data exists. * `summary`: include totals and a one-line tool pointer. * `off`: do not front-load prior project data. Pass `--burp-bridge-url http://127.0.0.1:9009` (or set `VIGOLIUM_BURP_BRIDGE_URL`) to import live Burp Proxy history before the brief is built. That makes black-box runs start from captured operator traffic instead of a cold target. *** ## Knowledge base `--knowledge-base ` supplies application documentation such as authentication flows, roles, privilege tiers, and business rules. Vigolium indexes `.md`, `.markdown`, `.mdx`, `.txt`, `.rst`, and `.adoc` files while skipping binary/vendor trees. The default path uses one bounded, tool-less LLM call to produce a compact briefing. The opening prompt receives that summary plus an authoritative path index; full documents stay on disk for `read_file` and `grep` on demand. Distillation failure is non-fatal and falls back to the deterministic index. The result is cached as `knowledge-base-brief.md` for provenance and resume reuse. Use `--knowledge-base-raw` to skip the distillation call and include only the deterministic index. This is useful for offline or reproducible runs. **Traffic files in a knowledge base are ingested, not summarised.** If the path also holds HTTP-traffic exports — HAR, Burp XML, curl commands, OpenAPI/Swagger, Postman collections, URL lists, or raw HTTP — they are auto-detected, parsed, and written into the project database as normal traffic with `source=knowledge-base`, and a sample is folded into the brief. The operator then sees them as real records it can replay and scan, not as prose it has to interpret. Pass `--knowledge-base-no-traffic` to disable that and treat every file as documentation instead. *** ## Resume `--resume ` is available only when `agent.olium.autopilot_mode` resolves to `shadow` or `enforced`. It: * reuses the original AgenticScan UUID, project, target, source path, and session directory; * restores the scratchpad and candidate ledger; * marks any section left running by a crash as interrupted; and * skips native pre-scan, pre-flight discovery, and source-audit preparation. The original free-text prompt is not replayed. Add a new `--prompt` if the resumed pass needs extra direction. Resume requires the original database and session artifacts to remain available. *** ## Multi-app fan-out When the positional prompt parses to **multiple** apps (`vigolium agent autopilot "scan source at ~/src/A, ~/src/B"`), the package-level autopilot flags are snapshotted and reapplied per app, then `runAutopilotOlium` is invoked sequentially for each. Each app gets its own session dir, AgenticScan row, and provider session. A single-app prompt re-enters `runAgentAutopilot` directly with the parsed flags, same code path as a flag-driven invocation. *** ## REST API workflow ``` POST /api/agent/run/autopilot # async kickoff, returns run UUID GET /api/agent/status/list # list active/recent runs GET /api/agent/status/:id # poll a single run GET /api/agent/sessions/:id/logs # tail runtime.log (SSE supported) GET /api/agent/sessions/:id/artifacts # browse session directory ``` The HTTP request body covers the core target, source, focus, auth, intensity, and audit controls, but it does not expose every CLI-only preparation/debugging flag. The handler resolves provider/source, enters `autopilot.Run` on a goroutine, and returns the run UUID immediately. *** ## TL;DR Autopilot is one autonomous operator bounded by `MaxCommands` and wall-clock time. Legacy mode keeps one conversation and reports findings directly. Durable modes rotate context through persisted sections, support `--resume`, and can require a fresh-context verifier before a candidate becomes a finding. Use `--prior-context`, `--burp-bridge-url`, and `--knowledge-base` to start the operator with the evidence and application intent it would otherwise need to rediscover. # How Agent Mode Works Source: https://docs.vigolium.com/agentic-scan/how-it-works Architecture of Vigolium's agent runtime, the in-process olium engine, prompt orchestration, swarm/autopilot pipelines, and provider model. Vigolium Agentic Scan Vigolium's agent mode runs AI-driven security scans on top of the native scanner. This document explains the architecture, the moving parts, and the flow of a typical agent run. > **Recent shift:** the previous subprocess-based SDK and ACP backends (`claudesdk`, `codexsdk`, `opencodesdk`, ACP bridges) have been removed. All AI dispatch now goes through an in-process Go runtime called **olium** (`pkg/olium/`). One unified provider interface, one conversation state, one place to reason about timeouts and retries. *** ## 1. Subcommand surface `vigolium agent` is a parent command with informational flags only (`--list-templates`, `--list-agents`). Real work happens in subcommands. | Subcommand | Purpose | | ----------- | -------------------------------------------------------------------------------------------------------------- | | `query` | Single-shot prompt (template or inline). Code review, secret hunt, endpoint discovery. | | `autopilot` | Agentic scan: autonomous operator with full tool access. | | `swarm` | Agentic scan: 10-phase guided pipeline (plan → extension → scan → triage). | | `audit` | Unified source-audit dispatcher (drives the embedded vigolium-audit harness, piolium, or both via `--driver`). | | `olium` | Interactive olium TUI or one-shot non-interactive prompt. | | `session` | List or inspect past agent runs (sessions list / detail view). | | `triage` | AI triage of a single existing finding (`triage [finding-id]`) — confirm or downgrade it. | `query` and `triage` are one-shot modes that do **not** orchestrate a scan (`query` runs a prompt with optional source-code context; `triage` re-evaluates one existing finding). `autopilot` and `swarm` are the two **agentic scan** modes. *** ## 2. Architecture layers ``` ┌─────────────────────────────────────────────────────────────────┐ │ CLI pkg/cli/agent_*.go │ │ query · autopilot · swarm · audit · olium · session · triage │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Orchestrators pkg/agent/ │ │ │ │ SwarmRunner swarm.go + swarm_pipeline.go │ │ normalize → auth → source-analysis → code-audit → │ │ discovery → plan → extension → scan → triage → finalize │ │ │ │ AutopilotPipelineRunner autopilot_pipeline.go │ │ audit (optional, foreground) → autonomous operator │ │ │ │ Audit driver dispatcher audit_drivers.go + audit_chain.go │ │ driver=auto|both|audit|piolium · per-driver child rows │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Engine pkg/agent/engine.go │ │ Preflight → buildPrompt → enrichContext → run on olium → │ │ parse → ingest (findings / http_records / plans / triage) │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Olium runtime pkg/olium/{engine,tool,skill} │ │ Multi-turn agent loop · tool registry · event stream │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Providers pkg/olium/provider/ │ │ anthropic (API/OAuth/CLI/SDK-bridge/compatible/Vertex) · │ │ openai (API/Responses/codex-OAuth) · │ │ google-vertex · openai-compatible (HTTP) │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.1 Engine (`pkg/agent/engine.go`) The engine is the seam between orchestrators and the olium runtime. Its job is to: 1. **Preflight**: validate provider/model selection. 2. **Build the prompt**: load template, parse frontmatter, render with `TemplateData`. 3. **Enrich context**: pull DB context (previous findings, discovered endpoints, high-risk endpoints, module list, scan stats) through a thread-safe LRU cache that lives for one swarm/autopilot run. 4. **Dispatch**: call the olium engine. 5. **Retry**: exponential backoff on transient errors (default 2 retries, 2-30s backoff with jitter). 6. **Parse**: schema-aware JSON extraction tolerating fences, prose, and type coercion (string ↔ int, object ↔ string body). 7. **Ingest**: write parsed findings/HTTP records to the DB repository. Key entry points: * `Engine.Run(ctx, opts)`: one-shot prompt execution; creates a fresh olium engine. * `Engine.RunOnOliumEngine(ctx, opts, eng)`: runs against a **shared engine instance**, preserving conversation prefix for prompt-cache hits across phases. * `Engine.RunSourceAnalysisParallel(ctx, cfg)`: fan-out source analysis (single explore call → parallel format/extension sub-calls on the same engine). A global semaphore caps in-flight provider calls; `agent.olium.max_concurrent` uses a positive value as the cap, `0`/unset as the default of 4, and a negative value for unbounded concurrency. ### 2.2 Olium runtime (`pkg/olium/`) Native, in-process replacement for the old subprocess pool. * **`pkg/olium/engine`**: `Engine.Run(ctx, prompt) <-chan Event` returns a stream of events: `EventTextDelta`, `EventThinkingDelta`, `EventToolCall`, `EventTurnDone` (with token usage), `EventError`. Conversation state (system prompt, tool definitions, prior turns) lives on the engine and is reused across calls when phases share an engine. * **`pkg/olium/tool`**: registry of built-in tools (bash, file ops, grep, fetch, …). Autopilot exposes the full set; swarm uses a smaller set per phase. * **`pkg/olium/skill`**: optional skill files (Markdown SKILL.md packages) that augment the system prompt; loaded from embedded assets and `~/.vigolium/skills/`. * **`pkg/olium/provider`**: provider dispatch (eleven drivers): | Provider | Auth source | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `openai-codex-oauth` | `oauth_cred_path` (JSON from `codex login`) | | `anthropic-api-key` | `llm_api_key` or `$ANTHROPIC_API_KEY` | | `anthropic-oauth` | `oauth_token` (from `claude setup-token`); falls back to `$ANTHROPIC_API_KEY` | | `openai-api-key` | `llm_api_key` or `$OPENAI_API_KEY` | | `openai-responses` | `llm_api_key` or `$OPENAI_API_KEY` (public OpenAI Responses API, `/v1/responses`) | | `anthropic-cli` | shells out to the local `claude` binary (alias: `anthropic-claude-cli`) | | `anthropic-claude-sdk-bridge` | logged-in Claude Code subscription via the `vigolium-audit bridge` sidecar; `bridge_binary` / `--bridge-bin` (embedded blob, then `PATH`) | | `anthropic-compatible` | `custom_provider.base_url` (Anthropic Messages `/v1/messages`), `custom_provider.api_key`, `custom_provider.model_id`, `custom_provider.extra_headers` | | `anthropic-vertex` | `oauth_cred_path` (GCP SA JSON or `$GOOGLE_APPLICATION_CREDENTIALS`) + `google_cloud_project` / `google_cloud_location` | | `google-vertex` | same GCP creds; routes `gemini-*` models | | `openai-compatible` | `custom_provider.base_url` (required), `custom_provider.api_key` (optional), `custom_provider.model_id`, `custom_provider.extra_headers` (Ollama / OpenRouter / LM Studio / vLLM / Groq / Together / LocalAI / custom proxies) | Default provider when nothing is configured is `openai-compatible` with `gemma4:latest` (a local Ollama endpoint). Configured under `agent.olium` in `vigolium-configs.yaml`. Per-call deadline defaults to 10 minutes (`call_timeout_sec`). ### 2.3 Prompt templates Markdown files with YAML frontmatter, loaded from (in order): 1. `agent.templates_dir` (config dir) 2. `~/.vigolium/prompts/` 3. Embedded (`public/presets/prompts/` baked into the binary) Frontmatter declares the **output schema** the agent is expected to produce: | Schema | Used by | Parsed into | | ---------------------------- | ----------------------------- | ------------------------ | | `findings` | code review, triage, audit | `[]AgentFinding` → DB | | `http_records` | endpoint discovery | `[]AgentHTTPRecord` → DB | | `source_analysis` | swarm source-analysis phase | `SourceAnalysisResult` | | `attack_plan` / `swarm_plan` | swarm plan + extension phases | `SwarmPlan` | | `triage_result` | swarm triage phase | `TriageResult` | Templates render against `TemplateData`, which carries: source code snippets, directory tree, target URL, hostname, previous findings (DB), discovered endpoints (DB), module list/tags, scan stats, and a free-form `Extra` map for orchestrator-injected hints. *** ## 3. Swarm pipeline `vigolium agent swarm --target ... [--source ...]` runs a state-machine pipeline. Each step implements `swarmPhaseStep.Run(ctx, *swarmPipelineState)`. ``` ┌────────────────────────────────────────────────────┐ │ agent swarm │ └────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────┐ │ native-normalize │ parse curl/raw HTTP/Burp/URL → records └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ auth (optional) │ browser-based login (--browser-auth; browser always on) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ source-analysis (AI)│ if --source: parallel explore + format │ │ emits routes, session-config, source extensions └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ code-audit (AI) │ optional code-level audit (--code-audit) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ native-discover │ optional crawl/spidering (--discover or deep) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ plan (AI) │ master agent picks modules + extensions │ │ batched (5 records/batch) for large input sets └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ native-extension │ compile/validate JS extensions (Sobek) │ │ LLM repair on syntax errors (max 5 in parallel) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ native-scan │ ScanFunc: native modules + custom extensions └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ triage (AI) ────────┼──→ rescan? loop up to MaxIterations └──────────┬──────────┘ (re-runs native-scan with targeted modules) ▼ ┌─────────────────────┐ │ finalize │ aggregate results, token usage, DB update └─────────────────────┘ ``` Phase names prefixed `native-` are pure-Go (no LLM). The pipeline is gated by: * `--only` / `--skip` / `--start-from` flags (with legacy aliases via `NormalizeSwarmPhase`) * intensity preset (`SwarmPresets[Quick|Balanced|Deep]`) * `cfg.SourcePath`: empty source skips source-analysis and code-audit * `cfg.Discover`, `cfg.CodeAudit`, `cfg.Triage` toggles * checkpoint state for observability; `--start-from` creates a new run with earlier phases marked complete A parallel **vigolium-audit** subprocess can run in the background (`cfg.Audit != ""`) when source is provided, contributing source-code audit findings without blocking the swarm. Swarm uses the embedded vigolium-audit harness directly; the multi-driver `agent audit` command layers piolium support on top. ### Plan & extension phases The master agent receives input records (chunked into `MasterBatchSize`, default 5) and returns a `SwarmPlan`: ```go theme={null} SwarmPlan { ModuleTags / ModuleIDs // which native modules to enable Extensions []GeneratedExtension // custom JS extensions (full source) QuickChecks []QuickCheck // shorthand → expanded JS by extensions/quickcheck_gen.go FocusAreas / Snippets / Hints // free-form guidance } ``` The extension phase compiles every JS extension through the Sobek engine. Syntax errors trigger an LLM repair pass (max 5 parallel). Surviving extensions are written to `/extensions/` with sanitized filenames. ### Triage loop After the native scan, if findings exist and triage is enabled, the triage agent receives a fixture (truncated by detail tiers, 15 full-detail / 40 table-with-top-10 / etc.) and emits: ```go theme={null} TriageResult { Confirmed []Finding FalsePositive []Finding FollowUpScans []FollowUpScan // optional rescans (modules + URLs) } ``` If `FollowUpScans` is non-empty and rescan is enabled, the pipeline loops back to `native-scan` with targeted modules. Loop bounded by `MaxIterations` (default 3); early-exits when all findings have "certain" confidence. *** ## 4. Autopilot pipeline `vigolium agent autopilot --target ... [--source ...]` is simpler, no plan/extension phases. The agent itself decides what to run. ``` ┌─────────────────────────────────────────────┐ │ vigolium-audit (optional, foreground) │ │ if --source: run vigolium-audit, freeze │ │ findings into vigolium-audit/ directory │ └─────────────────┬───────────────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ Context preparation │ │ - load frozen vigolium-audit findings │ │ - import Burp + summarize prior DB data │ │ - distill/index --knowledge-base docs │ │ - prepareAutopilotAuth → AuthHeaders │ │ - buildAutopilotContextBundle │ │ (routes, auth flows, browser decision) │ │ - buildAutopilotPlan │ │ (budgets, tasks, stop criteria) │ └─────────────────┬───────────────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ Autonomous operator (AI) │ │ olium engine with full tool access: │ │ Bash, Read, Grep, Glob, Edit, Write, │ │ vigolium scan-url, vigolium finding, │ │ vigolium traffic, etc. │ │ bounded by MaxCommands + Timeout │ │ legacy: growing conversation │ │ shadow/enforced: rotating durable sections│ └─────────────────┬───────────────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ Verification │ │ durable candidates → fresh-context grade │ │ enforced: promote confirmed candidates │ └─────────────────────────────────────────────┘ ``` The default `agent.olium.autopilot_mode: legacy` preserves direct `report_finding` behavior. `shadow` rotates context while mirroring candidates; `enforced` replaces direct reports with `propose_candidate` and promotes only verifier-confirmed claims. Durable runs can continue with `autopilot --resume `. ### Intensity presets (autopilot) | Intensity | MaxCommands | Timeout | Vigolium-audit mode | Browser | | --------- | ----------- | ------- | ------------------- | ------- | | quick | 150 | 1h | lite | on | | balanced | 500 | 6h | balanced | on | | deep | 1500 | 12h | deep | on | *** ## 5. Session directories Every swarm and autopilot run writes a session dir under `agent.sessions_dir` (default `~/.vigolium/agent-sessions//`). Layout: ``` / ├── checkpoint.json # swarm: completed phases, record stats, last triage round ├── swarm-plan.json # serialized SwarmPlan ├── session-config.json # auth session definitions (login flows, token rules) ├── extensions/ # compiled JS extensions (sanitized filenames) ├── vigolium-audit/ # vigolium-audit subprocess output (audit-state.json + findings) ├── piolium/ # piolium subprocess output (when audit dispatcher runs piolium) ├── master-output.md # rendered prompt sent to master agent (debug) ├── source-analysis-output.md ├── code-audit-output.md ├── runtime.log # human-readable runtime/tool log ├── transcript.jsonl # autopilot: conversation + section events ├── transcript-*.jsonl # swarm/query: per-phase conversations ├── transcript-verify-*.jsonl # durable candidate verifier transcripts ├── autopilot/scratchpad.json # durable plan, stop criteria, and notes ├── knowledge-base-brief.md # when --knowledge-base is supplied ├── tool-results/ # spilled large tool outputs ├── audit-stream.jsonl # `agent audit` streaming agent events (NDJSON) ├── inputs.json # normalized input records └── skills/ # copied embedded skills (vigolium-scanner) ``` `EnsureSessionDir(baseDir, agenticScanUUID)` in `pkg/agent/pipeline_types.go` is the canonical creator. *** ## 6. Configuration All agent settings live under `agent` in `vigolium-configs.yaml`: ```yaml theme={null} agent: default_agent: olium templates_dir: ~/.vigolium/prompts sessions_dir: ~/.vigolium/agent-sessions context_limits: max_findings: 50 max_endpoints: 100 max_high_risk: 20 min_risk_score: 50 olium: provider: openai-compatible # openai-codex-oauth | anthropic-api-key | openai-api-key | openai-responses | anthropic-oauth | anthropic-cli | anthropic-claude-sdk-bridge | anthropic-compatible | anthropic-vertex | google-vertex | openai-compatible model: gemma4:latest # provider default if empty oauth_cred_path: ~/.codex/auth.json llm_api_key: ${ANTHROPIC_API_KEY} reasoning_effort: medium max_tokens: 1000000 max_turns: 32 # short non-autopilot uses; autopilot uses its own cap (DefaultAutopilotMaxTurns=200) max_concurrent: 4 # 0/unset = default 4; negative = unbounded autopilot_mode: legacy # legacy | shadow | enforced call_timeout_sec: 600 # per-call deadline; -1 = no timeout custom_provider: # only used when provider == openai-compatible base_url: http://localhost:11434/v1 # Ollama; OpenRouter / LM Studio / vLLM also work model_id: gemma4:latest api_key: "" audit: enable: false mode: lite browser: enable: true binary_path: agent-browser ``` CLI flags (`--provider`, `--model`, `--oauth-cred`, `--oauth-token`, `--llm-api-key`, `--bridge-bin`) override the config at runtime. The REST API also accepts per-request BYOK credentials. *** ## 7. Where things live | What | Where | | -------------------------------- | ---------------------------------------------- | | Subcommand wiring | `pkg/cli/agent*.go` | | Swarm orchestrator | `pkg/agent/swarm.go`, `swarm_pipeline.go` | | Autopilot orchestrator | `pkg/agent/autopilot_pipeline.go` | | Audit driver dispatcher | `pkg/agent/audit_drivers.go`, `audit_chain.go` | | Vigolium-audit / piolium runners | `pkg/agent/audit_agent.go`, `pkg/piolium/` | | Engine (prompt → dispatch) | `pkg/agent/engine.go` | | Prompt templates / rendering | `pkg/agent/prompt/`, `public/presets/prompts/` | | Output parsers (JSON-tolerant) | `pkg/agent/parsing/` | | Olium runtime | `pkg/olium/engine`, `tool`, `skill` | | Olium providers | `pkg/olium/provider/` | | Phase constants & presets | `pkg/agent/agenttypes/constants.go` | | Core types | `pkg/agent/agenttypes/types.go` | | Public aliases | `pkg/agent/aliases.go` | | Config schema | `internal/config/agent.go` | *** ## 8. Quick mental model * **Engine** turns a prompt template + DB context into a structured result. One LLM call. * **Orchestrator** sequences many engine calls plus native steps (discovery, scan), checkpoints state, and writes a session directory. * **Olium** is the agent runtime, it holds conversation state and dispatches to a provider. One olium engine can serve many engine calls cheaply (prompt cache hits). * **Swarm phases** are controlled by `--only`, `--skip`, and `--start-from`; swarm has no public `--resume` flag. * **Autopilot resume** takes an AgenticScan UUID and restores durable scratchpad/candidate state; it is not phase-based. * **Intensity** is a single knob that hydrates a bundle of toggles (commands, timeout, vigolium-audit mode, discover/audit/triage flags, browser/auth). # Olium Agent Source: https://docs.vigolium.com/agentic-scan/olium The in-process AI agent runtime that powers every agentic feature in Vigolium, interactive TUI, headless prompts, and the engine library used by autopilot, swarm, query, and vigolium-audit. `olium` is the in-process AI agent runtime that powers every agentic feature in Vigolium. It ships as both: * **A user-facing command**: `vigolium agent olium` (aliases: `vigolium olium`, `vigolium ol`), for interactive chat in a TUI or scripted one-shot prompts. * **A library**: `pkg/olium/`, that the autopilot, swarm, query, vigolium-audit-prep, and source-analysis paths all dispatch through. There are **no subprocess SDK or ACP backends**; every AI call in vigolium goes through this engine. For a higher-level comparison against the other agent subcommands, see [Agent Mode](/agentic-scan/agent-mode). *** ## What it is A turn-based, tool-using LLM agent written in Go. Components: | Layer | Lives in | Responsibility | | ------------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Engine** | `pkg/olium/engine/` | Multi-turn loop: provider stream → tool dispatch → history append → repeat | | **Provider** | `pkg/olium/provider/` | LLM backend, eleven drivers (openai-codex-oauth, anthropic-api-key, anthropic-oauth, openai-api-key, openai-responses, anthropic-cli, anthropic-claude-sdk-bridge, anthropic-compatible, anthropic-vertex, google-vertex, openai-compatible) | | **Tools** | `pkg/olium/tool/` | The eight built-in primitives the model can call (bash, file ops, search, web fetch) | | **Skills** | `pkg/olium/skill/` | SKILL.md workflow files (agentskills.io format) discovered from project, user, and embedded scopes | | **TUI** | `pkg/olium/tui/` | Bubble Tea front-end (inline scrollback, slash commands, live tool cards) | | **Headless** | `pkg/olium/headless.go` | Non-interactive single-prompt runner for scripts and smoke tests | | **Autopilot** | `pkg/olium/autopilot/` | Autonomous scan loop with budgets and a halt signal; durable modes rotate context and can verify candidates before promotion | | **Vigolium tools** | `pkg/olium/vigtool/` | Scanner- and record-aware extensions: `run_native_scan`, `run_module`, `query_records`, `replay_request`, `attack_kit`, `list_findings`, etc. | | **Auth** | `pkg/olium/auth/` | Codex OAuth credential loading and refresh (handles `~/.codex/auth.json`) | *** ## What it does Each invocation runs one **multi-turn loop**: 1. Append the user prompt to history. 2. Stream a single provider response (text deltas, thinking deltas, tool calls). 3. Append the assistant turn to history; emit `EventTurnDone` with token usage. 4. If there are no tool calls → emit `EventRunDone` and exit. 5. Otherwise dispatch the tool calls. If **all** calls are read-only the engine fans them out in parallel (cap = 8); otherwise it runs them strictly serially so writes can't race reads. Tool results are appended to history in the model's original order regardless. 6. Loop back to step 2, capped by `MaxTurns` (default **32** for chat / headless, **200** for autopilot). Surrounding behavior: * **Tool result truncation / spill**: results larger than `MaxToolResultBytes` (default 16 KiB) get head+tail truncation with an elision marker. If `SpillDir` is set (autopilot does this), the full payload spills to `/tool-results/` and the model gets a head excerpt plus an on-disk path it can `read_file`. * **Per-tool timeout**: each tool invocation gets its own deadline (default 5 minutes). A runaway `bash curl` can't hang the whole session. * **Prompt caching**: opt-in via `EnablePromptCache`. Autopilot turns it on; the Anthropic providers write `cache_control: ephemeral` markers and the Codex OAuth provider writes `prompt_cache_key` headers, cutting repeated-prefix tokens by \~90 % across long runs. `openai-api-key`, `openai-responses`, `openai-compatible` (Ollama / OpenRouter / LM Studio / vLLM / Groq / …), `anthropic-compatible`, and `google-vertex` do not emit cache markers, so the flag is silently ignored for them. (`anthropic-claude-sdk-bridge` manages caching inside the Claude Code Agent SDK.) * **Skills**: when a registry is loaded the engine injects an `` block into the system prompt at construction, and registers a `load_skill` tool the model can call to fetch a skill body on demand. *** ## Modes ### Interactive TUI (default) ```bash theme={null} vigolium olium # chat vigolium ol # alias vigolium agent olium # full path vigolium ol "audit this repo" # auto-submitted first prompt echo "summarise" | vigolium ol # stdin auto-detected when piped ``` Bubble Tea inline mode (no alt-screen, output appends to scrollback as it streams). Live partial line, fenced code-block highlighting via chroma, and a one-line "tool exec" card while a tool runs. Slash chooser opens on `/`: * `/clear`: clear conversation history. * `/skill: [args]`: inline expansion of a loaded skill; the body is pasted into the prompt so the model doesn't have to spend a tool call to `load_skill`. The model id, provider, and reasoning effort are shown in the banner header. ### One-shot non-interactive Passing `-p` / `--prompt` runs a single prompt non-interactively and streams to stdout, the TUI is skipped automatically. ```bash theme={null} vigolium ol -p "list every route in this repo" ``` Prints assistant text to **stdout**; thinking deltas, tool start/end cards, and per-turn `[turn done in= out= cached=]` summaries go to **stderr**. Exits non-zero on engine error. ### Library use (autopilot, swarm, query) `pkg/agent/olium_adapter.go` is the single dispatch path every other agent feature funnels through: * `runOliumPrompt(ctx, cfg, prompt, streamWriter, sourcePath)`: fresh engine per call. * `runOliumOnEngine(ctx, cfg, eng, prompt, streamWriter)`: reuses an engine so the conversation prefix stays warm (used by source-analysis to fork an explore phase into 3 parallel format calls). * `acquireProviderSlot(ctx, cfg)`: global semaphore (size = `agent.olium.max_concurrent`, default 4) that bounds in-flight provider calls process-wide so swarm phase fan-out can't trigger 429s on tier-1 plans. * `EffectiveCallTimeout()`: default 10 min per provider call; 0 → default, negative → no timeout. *** ## Providers Eleven drivers in `pkg/olium/provider/`. The provider ID is vendor-first so it's obvious which credential field applies: | Provider | Auth | Default / typical model | Source of credential | | ------------------------------- | -------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `openai-codex-oauth` | OAuth credential file | `gpt-5.5` | `--oauth-cred` → `agent.olium.oauth_cred_path` → `~/.codex/auth.json` (produced by `codex login`) | | `anthropic-api-key` | `x-api-key` header | `claude-opus-4-7` | `--llm-api-key` → `agent.olium.llm_api_key` → `$ANTHROPIC_API_KEY` | | `anthropic-oauth` | Bearer token (Claude Code OAuth) | `claude-opus-4-7` | `--oauth-token` → `agent.olium.oauth_token` → `$ANTHROPIC_API_KEY` (produced by `claude setup-token`) | | `openai-api-key` | `x-api-key` header | `gpt-5.5` | `--llm-api-key` → `agent.olium.llm_api_key` → `$OPENAI_API_KEY` | | `openai-responses` | `x-api-key` header | `gpt-5.5` | `--llm-api-key` → `agent.olium.llm_api_key` → `$OPENAI_API_KEY`; public OpenAI Responses API (`/v1/responses`) instead of Chat Completions | | `anthropic-cli` | (none, subprocess) | `claude-opus-4-7` | `--claude-bin` (default `claude` on `$PATH`); alias `anthropic-claude-cli` | | `anthropic-claude-sdk-bridge` | (none, subscription) | Claude Code default | Drives Claude Code via the `vigolium-audit bridge` sidecar using your logged-in subscription; `--bridge-bin` → `agent.olium.bridge_binary` → embedded blob → `vigolium-audit` on `$PATH` | | `anthropic-compatible` | optional bearer | via `custom_provider.model_id` | `custom_provider.base_url` (Anthropic Messages `/v1/messages` gateway or proxy), `custom_provider.api_key`, `custom_provider.model_id`, `custom_provider.extra_headers` | | `anthropic-vertex` | GCP SA JSON | `claude-opus-4-6` | `agent.olium.oauth_cred_path` (or `$GOOGLE_APPLICATION_CREDENTIALS`) + `google_cloud_project` / `google_cloud_location`; routes `claude-*` models to publishers/anthropic | | `google-vertex` | GCP SA JSON | `gemini-2.5-pro` | Same GCP creds as `anthropic-vertex`; routes `gemini-*` models to publishers/google | | `openai-compatible` *(default)* | optional bearer | `gemma4:latest` (Ollama) | `custom_provider.base_url` (required), `custom_provider.api_key` (optional), `custom_provider.model_id`, `custom_provider.extra_headers` (Ollama / OpenRouter / LM Studio / vLLM / Together / Groq / LocalAI / custom proxies) | With no `--provider` flag and no YAML override, vigolium defaults to **`openai-compatible`** with `gemma4:latest` against a local Ollama endpoint, so a freshly initialized config works out of the box without any cloud credentials. The `anthropic-oauth` provider also prepends a Claude Code preamble to the system prompt and adds the `oauth-2025-04-20` beta header so it's accepted on the same endpoint as `anthropic-api-key`. Codex auth refreshes itself: it parses the JWT, checks expiry with a 60 s skew, and posts to `/oauth/token` with the stored refresh token, rewriting `~/.codex/auth.json` (mode `0o600`). > **Note:** the REST API falls back to `agent.olium.*` in `vigolium-configs.yaml` (which keeps warm sessions and prompt caches stable across requests), but every agent run endpoint also accepts per-request BYOK credentials (`api_key`, `oauth_token`, `oauth_cred_file`, `oauth_cred_json`). The audit dispatcher additionally accepts `audit_auth` / `piolium_auth` for per-driver overrides. *** ## Tools Built-in tool registry, eight tools registered in this order: | Name | Read-only? | What it does | | ------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `bash` | no | `bash -lc ` with hard-rejects for catastrophic patterns (`rm -rf /`, `dd` to block devices, fork bombs, `mkfs` against real devices). Default timeout = engine `ToolTimeout` (5 min). | | `read_file` | yes | Read file with line-number prefix. Params: `path`, `offset`, `limit` (default 2000). | | `write_file` | no | Create or overwrite a file. | | `edit_file` | no | Find-and-replace edit on a file. | | `ls` | yes | List a directory. | | `grep` | yes | Regex search, uses ripgrep when available, else native Go regex. Params: `pattern`, `path`, `glob`, `max_matches` (200), `ignore_case`. | | `glob` | yes | Glob pattern → paths. | | `web_fetch` | yes\* | Fetch a URL. Two modes: `http` (default, fast) and `browser` (headless Chromium for SPA / JS-heavy pages). Params: `url`, `method`, `headers`, `body`, `max_bytes`, `mode`, `wait_selector`, `wait_ms`. | \* `web_fetch` is only read-only in the **no-capture** variant (TUI chat / query, no DB wired). Under autopilot the capture-enabled variant persists every fetch as an `http_record` — `http` mode returns a `record_uuid`, `browser` mode captures every XHR/fetch during render (many records per call) — so it can issue state-changing methods, is **not** read-only, and does **not** join the parallel read-only fan-out (concurrent capturing fetches would race the shared record store). The `IsReadOnly()` flag is what the engine uses to decide whether to fan out a turn's tool calls in parallel. `bash` runs **without an approval prompt** (yolo mode), only the catastrophic-pattern guard prevents disasters. ### Autopilot adds more When the engine runs under `vigolium agent autopilot`, the registry also gets: * `halt_scan`: model-driven exit. Sets a halt signal; the run loop exits after the current turn. * `report_finding` (`legacy`/`shadow`): persists a finding to the database and accepts `record_uuids` linking its proving `http_records`. Shadow mode also mirrors a candidate for independent grading. It soft-warns at 50 saved findings and hard-caps at 200. * `propose_candidate` (`enforced`): persists a claim and its evidence for a fresh-context skeptic verifier. Only confirmed candidates are promoted to findings. * `load_skill`: fetch a skill body by name (registered whenever the skill registry is non-empty). * `update_plan` / `remember`: durable scratchpad plan + notes that survive section rotation and resume (registered unconditionally, seeded from the pipeline's frozen plan when present). * **Vigtool** (registered when `Repo` is non-nil): scanner- and record-aware tools — * Scan / modules: `run_native_scan`, `run_module`, `run_extension`, `list_modules`. * Record surface: `query_records`, `inspect_record`, `replay_request`, `send_raw_http`. * OAST: `oast_poll`, `oast_mint`. * Sessions / findings: `list_sessions`, `get_session`, `list_findings`, `update_finding`. * Auth: `list_auth_sessions`, `auth_session_lookup`, `browser_auth` (only when `agent-browser` is on `$PATH`). * Payloads: `attack_kit`. * Live Burp bridge (server runs, operator-enabled listener): `search_burp_items`, `inspect_burp_item`. * Capture-enabled `web_fetch` + `browser_probe` replace their built-in variants (need `Repo` + `ProjectUUID`), persisting every fetch as an `http_record`. A few of these are worth calling out: * **`attack_kit`** returns a curated starter-payload set per attack class (`xss`, `sqli`, `ssrf`, `cmd-injection`, `path-traversal`, `ssti`, `xxe`, `open-redirect`, `crlf`) — read-only and non-mutating. Its SSRF Redis gopher PoC is a harmless `PING` probe (v0.2.5 changed it from a destructive `FLUSHALL`), so an autonomous agent can't wipe a target's data; intrusive commands are left to explicit operator authorization. Pair it with `replay_request` to actually send payloads. * **`replay_request`** sends a mutated stored record and persists the exact sent request + received response as an `olium-replay` `http_record`, returning its UUID as `replay_record_uuid` — pass that to `report_finding` or `propose_candidate` via `record_uuids` as reproducible proof. * **`search_burp_items` / `inspect_burp_item`** are read-only tools (server runs only) that search / inspect the live Burp Target site map or Proxy history over an operator-enabled read-only bridge listener; they never modify Burp. *** ## Skills Skills are Markdown workflow files with YAML frontmatter, following the [agentskills.io](https://agentskills.io) convention so files written for Claude Code or pi work in olium verbatim. Format: ```markdown theme={null} --- name: triage-finding description: Walk a candidate finding from suspicious response → root cause → PoC. license: optional allowed-tools: optional list --- # Body Instructional prose the model reads after calling load_skill. ``` `name` must match `[a-z0-9-]+` (≤64 chars); `description` ≤1024 chars. ### Discovery The skill registry walks four scopes, first-found-by-name wins: 1. **Project**: `.agent/skills/` and `.claude/skills/` in the working directory and every ancestor, closest first. 2. **User**: `~/.vigolium/skills/` (only when `IncludeUserSkills=true`). 3. **Embedded**: shipped in the binary under `public/presets/skills/` via `go:embed`. Two on-disk layouts are accepted: `//SKILL.md` (directory skill, the agentskills.io standard) or `/.md` (single-file shorthand; frontmatter `name` must match the filename stem). Generic chat (`vigolium agent olium`, headless) loads scopes 1 + 3 only. Autopilot and swarm load all three so security-specific workflows in `~/.vigolium/skills/` don't pollute casual chat. ### Use The engine writes an `` block into the system prompt listing every skill's name + description + location. The model fetches bodies on demand via the `load_skill` tool, progressive disclosure, so unused skills don't burn tokens. In the TUI, type `/skill: [args]` to inline-expand a skill body into your prompt directly, no tool call needed. *** ## CLI flags ```text theme={null} --provider openai-codex-oauth | anthropic-api-key | anthropic-oauth | openai-api-key | openai-responses | anthropic-cli | anthropic-claude-sdk-bridge | anthropic-compatible | anthropic-vertex | google-vertex | openai-compatible --model provider-specific (empty = provider default) --oauth-cred OAuth credential file (openai-codex-oauth; default ~/.codex/auth.json). Also the GCP SA JSON for the Vertex providers. --oauth-token Claude Code OAuth bearer token (anthropic-oauth) --llm-api-key API key for anthropic-api-key / openai-api-key / openai-responses --claude-bin Path to the `claude` binary (anthropic-cli) --bridge-bin Path to the vigolium-audit binary hosting the SDK bridge (anthropic-claude-sdk-bridge; empty = embedded blob, then PATH) --base-url Override custom_provider.base_url (openai-compatible / anthropic-compatible) --system Override the built-in system prompt -p, --prompt Initial prompt (alternative to a positional arg). Forces non-interactive mode. --stdin Force reading the prompt from stdin ``` Precedence for the initial prompt: positional args → `-p/--prompt` → stdin (auto-detected when piped, or forced with `--stdin`). Values flow CLI → YAML → env: every CLI flag falls back to its `agent.olium.*` YAML field, which in turn falls back to the documented default or env var. *** ## Configuration The full `agent.olium` block: ```yaml theme={null} agent: olium: provider: openai-compatible # openai-codex-oauth | anthropic-api-key | anthropic-oauth | openai-api-key | openai-responses | anthropic-cli | anthropic-claude-sdk-bridge | anthropic-compatible | anthropic-vertex | google-vertex | openai-compatible model: gemma4:latest # empty = provider default oauth_cred_path: ~/.codex/auth.json # also GCP SA JSON for anthropic-vertex / google-vertex bridge_binary: "" # anthropic-claude-sdk-bridge; empty = embedded blob, then PATH oauth_token: "" # anthropic-oauth; supports ${ENV_VAR}; falls back to $ANTHROPIC_API_KEY llm_api_key: "" # supports ${ENV_VAR}; falls back to $ANTHROPIC_API_KEY / $OPENAI_API_KEY google_cloud_project: "" # Vertex providers; $GOOGLE_CLOUD_PROJECT wins google_cloud_location: "" # Vertex providers; default us-central1 reasoning_effort: medium # minimal|low|medium|high|xhigh (codex) system_prompt: "" # empty = built-in olium prompt custom_provider: # used when provider == openai-compatible or anthropic-compatible base_url: http://localhost:11434/v1 # full chat-completions URL also works model_id: gemma4:latest # fallback for olium.model api_key: "" # optional extra_headers: [] # curl-style "Key: Value" entries max_tokens: 1000000 temperature: 0.0 max_turns: 32 # short non-autopilot uses; autopilot uses its own cap (DefaultAutopilotMaxTurns=200) cache_size: 1024 # LRU; 0 disables max_concurrent: 4 # default 4 (0/unset); negative = unbounded call_timeout_sec: 600 # per-call deadline; negative = no timeout (parent ctx only) autopilot_mode: legacy # legacy | shadow | enforced ``` `autopilot_mode` is `legacy` by default. `shadow` and `enforced` enable durable section rotation; `enforced` additionally gates finding promotion through a fresh-context verifier. A negative `max_concurrent` removes the provider-call cap, while `0` keeps the default cap of 4. Adjacent config blocks worth knowing: * `agent.sessions_dir`: where per-run session directories go. Default `~/.vigolium/agent-sessions/`. * `agent.browser`: toggles `agent-browser` integration (the binary `web_fetch` shells out to in `mode: browser`). * `agent.audit`: controls the optional vigolium-audit / piolium prep step that autopilot/swarm can stack ahead of the olium loop. *** ## Sessions and on-disk state Every agent run gets a session directory under `agent.sessions_dir` (default `~/.vigolium/agent-sessions//`). Bare `vigolium agent olium` chat doesn't write a session, it's only autopilot/swarm/query that materialise one. Inside a session dir you may find: * `runtime.log`: per-turn event log (text deltas, tool start/end, turn-done summaries). * `tool-results/-.txt`: spilled oversized tool outputs (when the engine's `SpillDir` is set). * `session-config.json`: run metadata (project / scan UUIDs, options). * `swarm-plan.json`, `master-output.md`, `audit-stream.jsonl`, `checkpoint.json`, produced by the higher-level modes that wrap olium (swarm, vigolium-audit, autopilot). Browse past runs with `vigolium agent session list` / `--full` / `--tail`. *** ## Stream events The engine emits a unified `Event` channel regardless of provider: | Event | Carries | | ------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `EventTextDelta` | `Delta`, assistant text increment | | `EventThinkingDelta` | `Delta`, reasoning content (Anthropic thinking, codex reasoning) | | `EventToolCallStart` | `ToolName`, `ToolArgs`, the model decided to call a tool | | `EventToolExecStart` / `EventToolExecProgress` / `EventToolExecEnd` | tool invocation lifecycle, `ToolResult`, `ToolIsErr` | | `EventTurnDone` | `StopReason`, `Usage` (input / output / cache-read / cache-write tokens) | | `EventRunDone` | terminal usage | | `EventError` | `Err`, provider failure, ctx cancellation, max-turns exceeded | Token counts on `EventTurnDone` are accumulated by every higher-level caller (autopilot for budget enforcement, the adapter for `agenttypes.TokenUsage`, the swarm for cost reporting). *** ## When to use what | You want to... | Use | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Chat / debug / explore interactively | `vigolium ol` | | Run one prompt from a script and parse stdout | `vigolium ol -p "..."` | | Hand the agent the wheel for an autonomous pentest | `vigolium agent autopilot` (uses olium with budgets plus direct reporting or verified candidates, depending on `autopilot_mode`) | | AI-direct the native scanner (plan → modules → triage) | `vigolium agent swarm` | | Single-shot template-driven prompt with structured output | `vigolium agent query` | Olium itself is the **general-purpose** chat / dev surface and the engine every other mode reuses, it is not a security scan on its own. *** ## See also * [Agent Mode](/agentic-scan/agent-mode), the full agent subcommand map. * [Autopilot](/agentic-scan/autopilot), autonomous scan mode built on the olium engine. * [Swarm](/agentic-scan/swarm), AI-guided multi-phase scan that drives the native scanner. * [How It Works](/agentic-scan/how-it-works), provider list and the high-level dispatch story. # Agent Swarm Source: https://docs.vigolium.com/agentic-scan/swarm AI-guided multi-phase scan: master agent reads requests, picks modules, generates JS extensions, runs the native scanner, and optionally triages findings. `vigolium agent swarm` is the AI-guided agentic scan mode. The master agent reads the target's request/response surface (and optionally its source code), picks the right scanner modules, generates custom JavaScript extensions when needed, runs the native scanner, and optionally triages the results in a verify-and-rescan loop. It sits between two extremes: it is more **directed** than `autopilot` (which gives the agent free reign with full tool access) and more **flexible** than a hand-tuned `vigolium scan` (modules and extensions are chosen by the model, not the user). This document covers what the pipeline looks like, how data flows between phases, and where the AI / native boundary sits. *** ## When to use swarm | Scenario | Use | | ------------------------------------------------------------------------------------------------------ | ----------------------- | | You have a target URL, raw request, or HTTP record and want the agent to pick modules + write payloads | `swarm` | | You also have the application source, want routes + auth + extensions inferred from code | `swarm --source ` | | You want false-positive verification and targeted re-scans on the model's say-so | `swarm --triage` | | You'd rather hand the agent a shell + the codebase and let it decide everything | `autopilot` | | You want a one-shot prompt with no scanning | `agent query` | Swarm is the right mode when you want the AI to **drive the native scanner**, not replace it. *** ## Pipeline at a glance Ten phases run in strict order. Each phase is either **native** (deterministic Go) or **AI** (LLM call via the olium engine). Most phases are conditional, they only fire when their inputs are present or the user opts in. ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ swarm pipeline │ │ │ │ [N] native-normalize ─────────────────────────────── always │ │ │ │ │ [A] auth ─────────────────────────── if --browser-auth │ │ │ │ │ [A] source-analysis ─────────────── if --source (4-call wave) │ │ [A] code-audit ──────────────────── if --code-audit and --source │ │ │ │ │ [N] native-discover ─────────────── if --discover │ │ │ │ │ [A] plan ────────────────── always (master agent, may batch) │ │ [N] native-extension ────────────── if plan declared extensions │ │ [N] native-scan ────────────────── always (hands off to scanner) │ │ │ │ │ [A] triage ────────────────── if --triage │ │ [N] native-rescan ───────────────── per round, when triage requests it │ │ │ │ │ [N] finalize ────────────────── always │ └─────────────────────────────────────────────────────────────────────────────┘ [A] = AI call [N] = native Go ``` *** ## Data flow The pipeline is driven by a single `swarmPipelineState` that all phases read from and write to. The two main payloads that move through it are **records** (HTTP request/response pairs) and the **plan** (module selection + extensions spec). ``` ┌────────────────────┐ inputs ──────►│ normalize │──► []*HttpRequestResponse (curl/HTTP/ └────────────────────┘ │ burp/url/ │ record uuid/ ▼ stdin) ┌────────────────────┐ │ source-analysis │ if --source │ (4-call wave) │──► +routes ┌────────────────┤ │ +session_cfg │ │ │ +extensions │ └────────────────────┘ │ │ │ ▼ │ ┌────────────────────┐ │ │ native-discover │ if --discover │ └────────────────────┘ │ │ │ ▼ merged []records │ ┌────────────────────┐ │ │ plan (master) │ │ │ - select modules │ │ │ - focus areas │ │ │ - extensions spec │ │ └────────────────────┘ │ │ │ ▼ SwarmPlan │ ┌────────────────────┐ │ │ extension │ │ │ validate + persist │ │ └────────────────────┘ │ │ │ ▼ extensions/*.js │ ┌────────────────────┐ └───────────────►│ native-scan │ ScanFunc │ runner.RunNative… │ (callback) └────────────────────┘ │ ▼ findings → DB ┌────────────────────┐ │ triage (loop) │ if --triage │ verdict per finding│ │ + rescan request │ └────────────────────┘ │ ▼ ┌────────────────────┐ │ finalize │ │ aggregate results │ └────────────────────┘ ``` Records grow as the pipeline runs: source analysis appends discovered routes, and native discovery merges crawl/spider results before planning. The plan, once produced, is the single source of truth for what the native scanner will run. *** ## Phase reference | # | Phase | Type | Purpose | Trigger | | -- | ------------------ | --------- | ------------------------------------------------------------ | ------------------------------------------------------ | | 1 | `native-normalize` | Native | Parse `--input`/stdin/record-uuid into `HttpRequestResponse` | Always | | 2 | `auth` | AI/Native | Browser-driven login, writes auth headers/cookies | `--browser-auth` (browser tooling is always available) | | 3 | `source-analysis` | AI | 4-call wave: explore → routes / session / extensions | `--source` | | 4 | `code-audit` | AI | Code-level security audit, findings → DB | `--code-audit` (auto with `--source` at balanced/deep) | | 5 | `native-discover` | Native | Crawl/spider/JS-scan to discover endpoints | `--discover` | | 6 | `plan` | AI | Master agent: pick modules, focus areas, extensions spec | Always | | 7 | `native-extension` | Native | Validate generated JS, write to `extensions/` | Plan declared extensions | | 8 | `native-scan` | Native | Hand off to `runner.RunNativeScan()` | Always | | 9 | `triage` | AI | Verify findings; mark confirmed / FP / rescan | `--triage` | | 10 | `native-rescan` | Native | Targeted rescan on triage request (loops) | Triage verdict = "rescan" | Phase control is available through `--skip` and `--start-from`. `--start-from` creates a synthetic checkpoint for a new run, marking earlier phases complete; it does not reopen an existing session. *** ## Source-aware mode: the 4-call wave When `--source ` is given, source analysis runs as a single explore call followed by three parallel format calls. ``` ┌──────────────────────────────────┐ │ Call 1 swarm-source-explore │ │ reads source once → notes: │ │ • routes notes │ │ • auth notes │ └────────────────┬─────────────────┘ │ session history reused │ (provider-cached, not resent in full) ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────────────┐ │ Call 2a │ │ Call 2b │ │ Call 3 │ │ format-routes │ │ format-session │ │ source-extensions │ │ notes → JSONL │ │ notes → │ │ notes → JS files │ │ http_records │ │ session_config │ │ │ └────────────────────┘ └────────────────────┘ └──────────────────────┘ │ │ │ └─────────────┬─────┴───────┬───────────┘ ▼ ▼ appended to written to ps.records auth-config.yaml ``` Why split it this way: explore output is large (capped at 64 KB), per-topic format calls only see a 48 KB slice. Provider session caching keeps the explore context cheap to reuse across the three follow-ups instead of re-paying for it. The discovered session config can come back malformed; the engine round-trips invalid entries through the LLM for repair before hydration into auth headers and persistence to `auth-config.yaml`. *** ## Master agent and batching The `plan` phase is two sub-calls: 1. **Plan agent**: analyses the records, returns a `SwarmPlan` with module tags/IDs, focus areas, and an extensions spec. Markdown-section output, retried up to `MaxMasterRetries` (default 3) on parse or transient errors. 2. **Extension agent** (conditional), only fires if the plan declared extensions. Generates JS scanner code. If this call fails, the plan from step 1 is still valid (graceful degradation). When `len(records) > MasterBatchSize` (default 5), planning fans out: ``` records ─► partition into batches of MasterBatchSize │ ├─► batch 1 ┐ ├─► batch 2 │ parallel, up to BatchConcurrency (default 3) ├─► batch 3 │ goroutines via errgroup └─► batch N ┘ ▼ plan_1, plan_2, … plan_N ▼ merge once at the end • module tags/IDs: set union • focus areas: deduplicated • extensions: merged by filename; collisions with different code → renamed • provenance: which batch contributed what ▼ SwarmPlan ``` The first batch error cancels the rest; partial-success merge is only attempted if the caller chooses to continue. When records are filtered for the prompt, a compact summary table of all endpoints is appended so the agent still sees the full surface even if only the top-N have full headers/bodies. *** ## Triage and rescan loop Triage follows the intensity preset: it is off for `quick` and on for `balanced` (the default) and `deep`. Use `--triage=false` to disable it explicitly, or `--triage` to enable it when another setting would leave it off. ``` for round in 1..MaxIterations: ┌───────────────────────────────────┐ │ query findings from DB │ filter by severity / module │ (resume from last_finding_id) │ └─────────────┬─────────────────────┘ ▼ ┌───────────────────────────────────┐ │ triage agent (AI) │ per finding: │ │ confirmed | false-positive | rescan └─────────────┬─────────────────────┘ ▼ verdict == rescan? │ │ │ yes │ done / no-rescan ▼ ▼ ┌──────────────┐ break │ native-rescan│ │ OnlyPhase = │ │ dynamic- │ │ assessment │ └──────┬───────┘ ▼ checkpoint round, continue ``` `MaxIterations` defaults to 1 (`quick`), 3 (`balanced`), 5 (`deep`). Triage processes findings in batches of 25 per round. Rescans set `IsRescan=true` on the `ScanRequest`, which forces `OnlyPhase = "dynamic-assessment"` and `SkipIngestion = true` so only the targeted modules execute. *** ## Native scanner handoff The swarm runner does not call modules itself, it hands off via callbacks the CLI installs on `SwarmConfig`: | Callback | Set when | What it does | | ------------------------ | ------------ | ------------------------------------------------------------------- | | `ScanFunc` | always | Runs `runner.RunNativeScan()` with modules/extensions from the plan | | `DiscoverFunc` | `--discover` | Crawl/spider/JS-scan; merges discovered records before planning | | `SourceAnalysisCallback` | `--source` | Writes `auth-config.yaml` from session config | `ScanFunc` is built like: ```go theme={null} opts.Modules = ResolveModulesFromPlan(req.ModuleTags, req.ModuleIDs) opts.AuthConfigs = []string{generatedAuthConfigYAML} // from source analysis opts.AuthConfigBestEffort = true // tolerate partial AI output if req.IsRescan { opts.OnlyPhase = "dynamic-assessment" opts.SkipIngestion = true } runner := runner.New(opts) return runner.RunNativeScan() ``` This is the AI / native boundary: everything above is AI-shaped (prompt, plan, JS code, verdicts), everything below this call is the standard executor running the registered modules. *** ## Session artifacts and checkpoints A swarm run creates a session directory (default `~/.vigolium/agent-sessions//`): ``` / ├── swarm-plan.json ← merged SwarmPlan from master agent ├── checkpoint.json ← phase progress, plan, triage round ├── source-analysis-prompt.md ├── source-analysis-output.md ├── source-analysis-sections.json ├── source-extensions.json ├── auth-config.yaml ← hydrated from session_config ├── code-audit-{prompt,output}.md ├── master-{prompt,output}.md ├── auth-{prompt,output}.md ├── extensions/*.js ← validated, persisted ├── triage/triage-round-N-{prompt,output}.md ├── transcript-*.jsonl ← per-phase Pi-compatible conversations └── runtime.log ← human-readable tool/runtime log ``` `checkpoint.json` is rewritten after every phase for observability and internal recovery. The public CLI has no swarm `--resume` flag. `--start-from ` begins a new run with earlier phases marked complete, so make sure the required database/source inputs already exist. *** ## CLI cheat sheet ```bash theme={null} # Minimum: target only — model picks modules from probed responses vigolium agent swarm --target https://app.example.com # Source-aware: 4-call source analysis + code audit vigolium agent swarm --target https://app.example.com --source ./repo # With triage and rescans (verifies findings, retries) vigolium agent swarm --target https://app.example.com --triage --max-iterations 3 # Full-scope sweep — discovery + planning + scanning vigolium agent swarm --target https://app.example.com --discover # Use a preset vigolium agent swarm --target https://app.example.com --intensity deep # Start a new run at planning, using traffic already in the project DB vigolium agent swarm --target https://app.example.com --start-from plan # Pipe a curl command — input auto-detected echo "curl -X POST https://app.example.com/api/login -d '{\"u\":\"a\"}'" | vigolium agent swarm # Force extension generation even when the master returns none vigolium agent swarm -t https://app.example.com --with-extensions # Add verbatim task guidance (also accepts a positional prompt) vigolium agent swarm -t https://app.example.com \ --prompt "log in as admin/admin123 and focus on payment authorization" # Render prompts without calling the LLM vigolium agent swarm --target https://app.example.com --dry-run --show-prompt # Use a different olium provider for this run vigolium agent swarm -t https://app.example.com \ --provider anthropic-api-key --model claude-opus-4-7 ``` ### Important flags | Flag | Effect | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-t, --target` | Target URL for dynamic phases | | `--input` | Raw input (curl, HTTP, Burp XML, base64, URL), auto-detected | | `--record-uuid` | Use an HTTP record from the database as the seed input | | `--prompt` / positional `[prompt]` | Free-text task guidance; credentials and browser intent are extracted from it | | `--prompt-file` | Read the task guidance from a file — the same channel as `--prompt`, so a long prompt needn't be shell-escaped. Pass it once: combining with `--prompt` or the positional prompt is an error | | `--plan-file` | Prose plus raw HTTP request blocks; cannot be combined with `--input` or a prompt | | `--source` | Source-code path (or git URL); enables source analysis + code-audit | | `--code-audit` | Force code audit (auto-on with `--source` for balanced/deep) | | `--discover` | Run native discovery before planning | | `--all-records` | Plan from every HTTP record already in the active project | | `--records-from` | Filter project records by spec — `host=`, `path=`, `method=`, `status=`, `source=`, `since=`, `until=` (comma-separated). `since`/`until` take the shared date grammar (`2d`, `today`, `2026-08-01`, RFC3339) | | `--max-plan-records` | Cap on records sent to the plan agent, one slot per URL prefix (intensity sets it: quick 10, balanced 25, deep 50; `0` = no limit) | | `--triage` | Enable verify-and-rescan loop | | `--skill` | Force-load these skills by name into triage, bypassing planner selection (repeatable or comma-separated) | | `--skill-tag` | Force-load every skill carrying one of these tags into triage (e.g. `xss,idor`) | | `--no-skill-filter` | Load the full skill set into triage; ignore planner selection | | `--db-isolate` | Scan into a private temp DB, then merge results into `--db` at the end (SQLite only) | | `--max-iterations` | Triage rounds (preset-driven default) | | `--master-batch-size` | Records per master-agent batch (default 5) | | `--batch-concurrency` | Parallel master batches (`0` = auto, scaled to CPU count) | | `--intensity` | `quick` / `balanced` / `deep` preset bundle | | `--audit` | Run a parallel source audit in `lite`, `balanced`, or `deep` mode (requires `--source`) | | `--skip`, `--start-from` | Skip named phases or start a new run from a validated phase | | `--source-analysis-only` | Stop after source analysis | | `--with-extensions` | Force the extension agent even when the plan returned none | | `-m, --modules` | Explicit module IDs to merge with the agent's selection | | `--vuln-type` | Vulnerability focus (e.g. `sqli`, `xss`, `ssrf`) | | `--browser-auth` | Run browser-based authentication before discovery; put credentials in the prompt | | `--cookie`, `--header`, `--login-curl`, `--auth-config` | Direct authentication/session injection alternatives | | `--headed` | Hidden debugging flag that shows browser windows | | `--upload-results` | Upload session bundle to cloud storage on completion | | `--provider`, `--model`, `--oauth-cred`, `--oauth-token`, `--llm-api-key` | Olium provider overrides | | `--gcp-project`, `--gcp-location` | GCP project / region for the Vertex providers (else `$GOOGLE_CLOUD_PROJECT` / `$GOOGLE_CLOUD_LOCATION`, then config, then `us-central1`) | | `--probe-concurrency`, `--probe-timeout`, `--max-probe-body` | Probing stage limits (defaults: 10 parallel, 10s per request, 2 MB body) | | `--sub-agent-concurrency` | Parallel source-analysis sub-agents — routes, auth, extensions (default 3) | | `--max-master-retries` | Master-agent retries on a parse failure (default 3) | | `--disable-guardrail` | Skip the prompt-safety classifier on the natural-language prompt | The CLI no longer exposes `--focus`, `--instruction`, `--instruction-file`, `--browser`, or `--credentials`. Use `--prompt` (or the positional prompt) for broad guidance and login details. Browser tooling is always enabled for swarm. The REST schema retains its structured focus, instruction, browser, and credential fields. *** ## API ``` POST /api/agent/run/swarm ``` ```json theme={null} { "input": "curl -X POST https://example.com/api/login -d '{\"user\":\"admin\"}'", "vuln_type": "sqli", "focus": "auth bypass", "module_names": ["sqli-error-based"], "discover": true, "code_audit": true, "triage": true, "max_iterations": 3, "intensity": "balanced", "audit": "lite", "stream": true, "timeout": "12h" } ``` At least one of `input`, `inputs`, or `http_request_base64` is required (`url` is an optional hint). `timeout` defaults to **12h** when omitted. See [API Reference, Agent](/api-references/agent) for the full schema. *** ## Where things live | Concern | File | | ------------------------------ | --------------------------------------------------- | | CLI flags + callback wiring | `pkg/cli/agent_swarm.go` | | Phase dispatch + state | `pkg/agent/swarm_pipeline.go` | | Master agent, batching, triage | `pkg/agent/swarm.go` | | 4-call source analysis | `pkg/agent/engine.go` (`RunSourceAnalysisParallel`) | | Phase constants + presets | `pkg/agent/agenttypes/constants.go` | | Native scanner entry | `pkg/core/executor.go`, `internal/runner/runner.go` | For the broader architecture (olium runtime, providers, common engine), see [How It Works](/agentic-scan/how-it-works). # Using the Vigolium Scanner Skill in Claude Code & Codex Source: https://docs.vigolium.com/agentic-scan/using-vigolium-in-your-agent Install and use the vigolium-scanner skill with AI coding agents for web vulnerability scanning and extension authoring. This guide explains how to install and use the `vigolium-scanner` skill with AI coding agents, Claude Code and OpenAI Codex, to operate the Vigolium CLI for web vulnerability scanning, security testing, and custom extension authoring. **Skill repository:** [github.com/vigolium/skills](https://github.com/vigolium/skills) — or install the copy **embedded in your `vigolium` binary**, which always matches your installed version. ### Quick Install ```bash theme={null} # Install the embedded skill into the current project (recommended — always matches your binary) vigolium skills install --agent claude --scope project ``` `vigolium skills install` (added in v0.2.1) writes the `vigolium-scanner` bundle straight from the binary, so it can never drift from the CLI you're driving. Pick the agent with `--agent claude|codex|agents` and the location with `--scope project` (current folder) or `--scope global` (home dir). Run `vigolium skills` to list what's bundled and `vigolium skills get vigolium-scanner` to print a bundle without installing it. Prefer to pull the latest from the repo instead? Use the external `skills` installer: ```bash theme={null} bunx skills add vigolium/skills --skill vigolium-scanner --agent --yes ``` Replace `` with your agent (e.g., `claude-code`, `codex`). *** ## The three machine contracts Whether or not you install the skill, these are the interfaces a program drives Vigolium through. They are additive — default human output is unchanged. | Contract | What it is | When to use it | | ------------------------------------ | --------------------------------------------------------------- | -------------------------------------------------- | | `-j` / `--json` | **One compact envelope** per command, with token-bounded bodies | Triage: survey findings, pull one in detail | | `--format jsonl` / `vigolium export` | **Bulk, full-fidelity stream**, one object per line | Archival, handing a whole run to another tool | | `--events ndjson` | **A live stream while a scan runs** | Watching progress, reacting to a WAF block mid-run | ### `--events ndjson`: watch a scan while it runs Don't poll the database and don't invent a patience message — ask for the stream. One JSON object per line on **stdout**, flushed per event, while the human console stays on stderr: ```bash theme={null} vigolium scan -t https://target.example --events ndjson 2>/dev/null | jq -c . ``` Available on `scan`, `run`, `scan-url`, `scan-request`. Types: `scan.started`, `phase.started|progress|finished`, `waf.block`, `waf.pacing`, `finding.new`, `error`, `scan.finished`. The three you should actually branch on: * **`waf.block`** — the edge started filtering. Results for that host are **incomplete**; do not read a thin surface as a clean target. * **`error`** — a phase failed and the scan **carried on**. Non-fatal by construction, so this is never a reason to abandon a run that is still producing findings. A failure that ends the run rides on `scan.finished{status:"failed"}` instead. * **`scan.finished`** — always last, including `status:"interrupted"` on SIGINT/SIGTERM. Its *absence* means the process was killed outright (SIGKILL can't be caught), so treat a stream that stops without it as a hard kill, not a completed scan. Every line carries `scan_uuid` (a sweep is several invocations) and `v`, the event-schema version. This replaces scraping `vigolium log` for `[waf-block-detected]` markers. Full event table: [CLI References](/others/cli-references#machine-event-stream). ### `-j`: one envelope, every command ```jsonc theme={null} { "schema_version": 1, "command": "traffic", "project_uuid": "…", "db_path": "/home/me/.vigolium/database-vgnm.sqlite", "total": 39, "offset": 0, "limit": 100, "items": [ … ], "query": "vigolium replay -u ", "generated_at": "2026-09-04T10:11:12.345Z", "generated_at_ms": 1788453072345 } ``` Parse `items` — it is canonical. Each command also writes its historical row key (`records`, `findings`, `scans`, `rows`, `stats`) as a *deprecated alias* pointing at the same slice; don't write key fallbacks. **Assert `db_path`**: the database open order ends at one shared default file, so a fall-through silently mixes engagements. Run the `query` field rather than composing your own follow-up. Gate on the contract once at startup with `vigolium version --json`, which reports `schema_version` and `db_schema_version`. ### Exit codes `0` success · `1` error · `2` usage error · `3` `fuzz --fail-on-match` · `4` `--fail-on` gate tripped. **`4` is not a failure.** The scan ran to completion, wrote its output, and found something at or above the threshold — the opposite outcome from `1`, where it never got that far. See the [full table](/others/cli-references#exit-codes). *** ## What the Skill Does The skill teaches the AI agent how to: 1. **Pick the right vigolium command** for any security testing task 2. **Construct correct flag combinations** with proper syntax 3. **Follow scanning workflows** end-to-end (ingest -> scan -> triage -> export) 4. **Write custom JavaScript extensions** using the `vigolium.*` API 5. **Operate AI agent modes** (query, autopilot, swarm) 6. **Manage data**: browse traffic, filter findings, export reports, clean databases The skill uses lazy-loaded references: the main `SKILL.md` stays small, and detailed docs are loaded on demand when the agent needs deep flag information or extension authoring guidance. *** ## Skill Structure ``` public/skills/vigolium-scanner/ ├── SKILL.md # Main skill (decision tree, recipes, routing) └── references/ ├── scanning.md # scan, scan-url, scan-request, run ├── agent-modes.md # agent query/autopilot/swarm/audit/olium/triage/session ├── agent-loop.md # the -j JSON contracts, triage, replay, exit-code gating ├── fuzzing.md # vigolium fuzz: positions, payloads, matchers, anomaly ├── burp.md # Burp bridge: send-via-burp, Repeater/Organizer staging ├── auth.md # --auth-file / --auth, YAML format, extract rules ├── data.md # db, finding, traffic, module, ext, js, config, export ├── server.md # vigolium server: REST API, recording/MITM proxy, live mirror ├── ingest.md # vigolium ingest: local/remote, per-format input examples ├── kit.md # vigolium kit: the standalone stateless primitives ├── extensions.md # writing JS scanner modules against vigolium.* └── flags.generated.md # generated from the command tree — grep by flag name ``` These filenames changed in v0.3.7–v0.3.9: the references were renamed to shorter stems, `flags-reference.md` became the generated `flags.generated.md`, `server-and-ingestion.md` split into `server.md` + `ingest.md`, and fuzzing and the Burp bridge moved out of `agent-loop.md` into their own files. A separate `agent-browser` skill was dropped. Re-run `vigolium skills install` to pick up the current layout. *** ## Installation **Option A: `vigolium skills install` (recommended — embedded, version-matched)** ```bash theme={null} # Default: install the vigolium-scanner bundle for Claude into the current project vigolium skills install # Choose the agent and scope explicitly vigolium skills install --agent codex --scope global # Install every bundled skill, or override the destination directory vigolium skills install --all vigolium skills install --dir ./.claude/skills ``` The bundle ships **inside** the `vigolium` binary, so `vigolium skills install` can never install a stale copy — it always matches the CLI version you're driving. `--agent` accepts `claude`, `codex`, or `agents` (an [agentskills.io](https://agentskills.io)-compatible layout); `--scope` is `project` (current folder) or `global` (home dir). Inspect what's bundled first with `vigolium skills` (list) and `vigolium skills get vigolium-scanner` (print). **Option B: Install via npx / bunx (pulls the latest from the repo)** ```bash theme={null} bunx skills add vigolium/skills --skill vigolium-scanner --agent --yes ``` or with `npx`: ```bash theme={null} npx skills add vigolium/skills --skill vigolium-scanner --agent --yes ``` Replace `` with your agent (e.g., `claude-code`, `codex`). This fetches the skill from the [vigolium/skills](https://github.com/vigolium/skills) repository and registers it automatically. **Option C: Clone and copy manually** ```bash theme={null} git clone https://github.com/vigolium/skills.git cd skills ``` Then copy the skill folder to your agent's configuration directory: ```bash theme={null} # For Claude Code cp -R vigolium-scanner ~/.claude # For other agents cp -R vigolium-scanner ~/.agents ``` Once installed, the skill **auto-triggers** when you mention keywords like `scan`, `vigolium`, `agent autopilot`, `vulnerability scanner`, `openapi scan`, etc. In Claude Code, you can also invoke it explicitly with `/vigolium-scanner`. *** ## Usage Examples by Category ### 1. Scanning **Basic scan against a single target:** ``` > Scan https://example.com for vulnerabilities ``` ```bash theme={null} vigolium scan -t https://example.com ``` **Multiple targets:** ``` > Scan both https://example.com and https://api.example.com ``` ```bash theme={null} vigolium scan -t https://example.com -t https://api.example.com ``` **Targets from a file:** ``` > I have a list of URLs in targets.txt, scan all of them ``` ```bash theme={null} vigolium scan -T targets.txt ``` **Scan with a specific strategy:** ``` > Do a deep scan of https://example.com with discovery and spidering ``` ```bash theme={null} vigolium scan -t https://example.com --strategy deep ``` **Scan a single URL with custom method, headers, and body:** ``` > Test the login endpoint for vulnerabilities: POST to https://api.example.com/login with JSON credentials ``` ```bash theme={null} vigolium scan-url https://api.example.com/login \ --method POST \ --body '{"username":"admin","password":"test123"}' \ -H "Content-Type: application/json" ``` **Scan a raw HTTP request from a file:** ``` > I captured a raw request in request.txt, scan it ``` ```bash theme={null} vigolium scan-request -i request.txt ``` **Scan a raw request from stdin:** ``` > Scan this raw request from the terminal ``` ```bash theme={null} echo -e "GET /api/users?id=1 HTTP/1.1\r\nHost: example.com\r\nAuthorization: Bearer tok123\r\n" | vigolium scan-request ``` **Scan with a proxy (e.g., Burp Suite):** ``` > Scan https://example.com and route traffic through Burp ``` ```bash theme={null} vigolium scan -t https://example.com --proxy http://127.0.0.1:8080 ``` **High-speed scan with tuned concurrency:** ``` > Scan fast — 100 workers, 200 req/s, max 5 per host ``` ```bash theme={null} vigolium scan -t https://example.com -c 100 --rate-limit 200 --max-per-host 5 ``` **Scan and output results as JSONL:** ``` > Scan and save results as JSON lines ``` ```bash theme={null} vigolium scan -t https://example.com --format jsonl -o results.jsonl ``` **Scan and generate an HTML report:** ``` > Scan and produce an interactive HTML report ``` ```bash theme={null} vigolium scan -t https://example.com --format html -o report.html ``` **Scan to a standalone SQLite file:** ``` > Run a one-off scan and give me a self-contained .sqlite I can reopen later ``` ```bash theme={null} vigolium scan -S -t https://example.com --format sqlite -o scan ``` `--format sqlite` (aliases `sqlite3`, `db`) requires `-S/--stateless` + `-o`; reopen it with `vigolium finding -S --db scan.sqlite`. **Scan to a browsable filesystem tree (no DB needed):** ``` > Scan and give me the traffic + findings as files I can grep ``` ```bash theme={null} vigolium scan -t https://example.com --format fs -o run ``` `--format fs` writes two sibling dirs off the `-o` base — `run-traffic/` and `run-findings/` — so you can investigate with plain `ls`/`grep`/`jq`. Each `/.req` is the raw, replayable request (strip the leading `@target` line), `.resp.headers`/`.resp.body` hold the gzip-decoded response, and `.md` under `-findings/` is the finding cross-linked to its `.req`. Start from `index.json` in each dir — `jq` it to map every id to its url/status/severity. Works with or without `-S`; honors `--omit-response`. See [Output and Reporting](/getting-started/output-and-reporting#filesystem-fs) for the full layout. **Scan to SARIF (for GitHub code scanning / DefectDojo):** ``` > Scan and produce a SARIF file I can upload to code scanning ``` ```bash theme={null} vigolium scan -S -t https://example.com --format sarif -o results.sarif ``` `--format sarif` writes a SARIF 2.1.0 log; it also works on `vigolium export --format sarif` and `vigolium import --format sarif` (the path for publishing an agentic source audit). Code findings anchor to `file:line` so a consumer can annotate a diff; HTTP findings anchor to their URL and carry the evidence exchange in `webRequest`/`webResponse`. See [Output and Reporting](/getting-started/output-and-reporting#sarif). **Scan with custom scanning profile:** ``` > Use the aggressive scanning profile ``` ```bash theme={null} vigolium scan -t https://example.com --scanning-profile aggressive ``` **Scan with strict origin scope:** ``` > Only scan URLs on the exact same origin ``` ```bash theme={null} vigolium scan -t https://example.com --scope-origin strict ``` *** ### 2. Input Formats **OpenAPI 3.x spec with explicit base URL:** ``` > Scan my API using the OpenAPI spec ``` ```bash theme={null} vigolium scan -I openapi -i api-spec.yaml -t https://api.example.com ``` **OpenAPI spec using servers from the spec:** ``` > Use the server URLs defined in the spec itself ``` ```bash theme={null} vigolium scan -I openapi -i api-spec.yaml --spec-url ``` **OpenAPI with auth header and parameter values:** ``` > Scan the spec with bearer auth and set the user_id parameter to 42 ``` ```bash theme={null} vigolium scan -I openapi -i spec.yaml -t https://api.example.com \ --spec-header "Authorization: Bearer eyJ..." \ --spec-var "user_id=42" ``` **Swagger 2.0 spec:** ``` > Import a Swagger 2.0 spec and scan ``` ```bash theme={null} vigolium scan -I swagger -i swagger.json -t https://api.example.com ``` **Burp Suite XML export:** ``` > I exported traffic from Burp, scan it ``` ```bash theme={null} vigolium scan -I burp -i burp-export.xml -t https://example.com ``` **HAR (HTTP Archive) file:** ``` > Scan my browser-recorded HAR file ``` ```bash theme={null} vigolium scan -I har -i traffic.har ``` **cURL commands file:** ``` > I have a file of curl commands, scan them all ``` ```bash theme={null} vigolium scan -I curl -i curl-commands.txt ``` **Postman collection:** ``` > Import and scan my Postman collection ``` ```bash theme={null} vigolium scan -I postman -i collection.json -t https://api.example.com ``` **Nuclei templates:** ``` > Run these Nuclei templates against the target ``` ```bash theme={null} vigolium scan -I nuclei -i templates/ -t https://example.com ``` **Piped URLs from stdin:** ``` > Pipe a list of URLs into the scanner ``` ```bash theme={null} cat urls.txt | vigolium scan -i - ``` *** ### 3. Phase Control **Run only discovery (content enumeration):** ``` > Just run content discovery against the target ``` ```bash theme={null} vigolium run discover -t https://example.com # or vigolium scan -t https://example.com --only discovery ``` **Run only spidering (headless browser crawling):** ``` > Spider the target with a headless browser ``` ```bash theme={null} vigolium run spidering -t https://example.com ``` **Run only audit (vulnerability scanning):** ``` > Skip discovery, just run the vulnerability modules ``` ```bash theme={null} vigolium run audit -t https://example.com # or vigolium scan -t https://example.com --only audit ``` **Run only the Nuclei known-issue scan (critical/high only):** ``` > Run Nuclei-based known-issue checks, only critical and high severity ``` ```bash theme={null} vigolium run known-issue-scan -t https://example.com --known-issue-scan-severities critical,high ``` **Run a whitebox source-code audit (SAST):** ``` > Run a security audit on my Go app's source ``` ```bash theme={null} vigolium agent audit --source /path/to/app ``` **Run only external harvest (Wayback, Common Crawl, OTX):** ``` > Gather URLs from external intelligence sources ``` ```bash theme={null} vigolium run external-harvest -t https://example.com ``` **Skip specific phases:** ``` > Scan but skip discovery and spidering ``` ```bash theme={null} vigolium scan -t https://example.com --skip discovery,spidering ``` **Run only JavaScript extensions:** ``` > Run only my custom extension, skip built-in modules ``` ```bash theme={null} vigolium scan -t https://example.com --only extension --ext ./custom-check.js # or vigolium run ext -t https://example.com --ext ./custom-check.js ``` **Phase aliases reference:** | Alias | Resolves To | | ----------------------------- | -------------------- | | `deparos`, `discover` | `discovery` | | `spitolas` | `spidering` | | `ext` | `extension` | | `audit`, `dast`, `assessment` | `dynamic-assessment` | | `cve`, `kis`, `known-issues` | `known-issue-scan` | *** ### 4. Module Filtering **List all available scanner modules:** ``` > Show me all scanner modules ``` ```bash theme={null} vigolium module ls # or vigolium scan -M ``` **Filter modules by keyword:** ``` > Show me all XSS-related modules ``` ```bash theme={null} vigolium module ls xss ``` **List only active modules with verbose details:** ``` > Show active modules with descriptions ``` ```bash theme={null} vigolium module ls --type active -v ``` **Scan with specific modules only:** ``` > Only run reflected XSS and error-based SQL injection modules ``` ```bash theme={null} vigolium scan -t https://example.com -m xss-light-url-params,sqli-error-based ``` **Filter modules by tags (OR logic):** ``` > Scan with modules tagged 'spring' or 'injection' ``` ```bash theme={null} vigolium scan -t https://example.com --module-tag spring --module-tag injection ``` **Combine module IDs and tags:** ``` > Run sqli-error plus all XSS-tagged modules ``` ```bash theme={null} vigolium scan -t https://example.com -m sqli-error --module-tag xss ``` **Enable/disable modules persistently:** ``` > Disable all SQL injection modules, enable all XSS modules ``` ```bash theme={null} vigolium module disable sqli vigolium module enable xss ``` **Enable by exact module ID:** ``` > Enable only the reflected XSS module ``` ```bash theme={null} vigolium module enable xss-light-url-params --id ``` *** ### 5. Server & Ingestion **Start the API server (default port 9002):** ``` > Start the vigolium server ``` ```bash theme={null} vigolium server ``` **Start server on custom port without auth:** ``` > Start the server on port 8443 with no authentication ``` ```bash theme={null} vigolium server --service-port 8443 --no-auth ``` **Start server with scan-on-receive (auto-scan ingested traffic):** ``` > Start the server and auto-scan every request that comes in ``` ```bash theme={null} vigolium server -t https://example.com --scan-on-receive ``` **Start server with transparent proxy for recording:** ``` > Start server with a recording proxy on port 8080 ``` ```bash theme={null} vigolium server --ingest-proxy-port 8080 ``` **Mirror ingested traffic to files in real time:** ``` > Run the server and mirror everything it ingests into ./mirror so I can grep it live ``` ```bash theme={null} vigolium server --mirror-fs ./mirror ``` Every saved HTTP record and finding is written to `./mirror/traffic//…` and `./mirror/findings//…` as it lands in the DB — the same layout as `--format fs`, except the indexes are append-only `index.jsonl` (one object per line — `tail`/`grep` it live) and per-host ids resume across restarts. Point your agent at `./mirror` to read ingested Burp/proxy traffic as files while it streams in. **High-concurrency server:** ``` > Start a high-throughput server with 200 workers ``` ```bash theme={null} vigolium server -c 200 --mem-buffer 50000 ``` **Ingest an OpenAPI spec locally:** ``` > Import the spec into the database without scanning ``` ```bash theme={null} vigolium ingest -t https://api.example.com -I openapi -i spec.yaml ``` **Ingest and auto-scan:** ``` > Import the spec and scan immediately ``` ```bash theme={null} vigolium ingest -t https://api.example.com -I openapi -i spec.yaml -S ``` **Ingest Burp export:** ``` > Import Burp traffic into the database ``` ```bash theme={null} vigolium ingest -t https://example.com -I burp -i export.xml ``` **Remote ingest to a running server:** ``` > Send traffic to the vigolium server running on localhost ``` ```bash theme={null} vigolium ingest -s http://localhost:9002 -I openapi -i spec.yaml ``` **Ingest without fetching responses:** ``` > Store request-only records, don't make network requests ``` ```bash theme={null} vigolium ingest -t https://example.com -I burp -i export.xml --disable-fetch-response ``` *** ### 6. AI Agent Modes #### Agent (Template-Based) **Security code review:** ``` > Review my source code for security vulnerabilities ``` ```bash theme={null} vigolium agent query --prompt-template security-code-review --source ./src ``` **Endpoint discovery from source:** ``` > Find all API endpoints in my source code ``` ```bash theme={null} vigolium agent query --prompt-template endpoint-discovery --source ./src ``` **Review specific files only:** ``` > Review only auth.go and middleware.go for security issues ``` ```bash theme={null} vigolium agent query --prompt-template security-code-review --source ./src \ --files "src/auth.go,src/middleware.go" ``` **Append extra instructions to a template:** ``` > Code review, but focus on authentication and authorization ``` ```bash theme={null} vigolium agent query --prompt-template security-code-review --source ./src \ --append "Focus specifically on authentication and authorization vulnerabilities" ``` **Use a custom prompt file:** ``` > Run the agent with my own prompt template ``` ```bash theme={null} vigolium agent query --prompt-file custom-prompt.md --source ./src ``` **Select a specific olium provider:** ``` > Use Anthropic for code review ``` ```bash theme={null} vigolium agent query --provider anthropic-api-key --prompt-template security-code-review --source ./src ``` **Dry-run to preview the rendered prompt:** ``` > Show me what prompt would be sent to the agent ``` ```bash theme={null} vigolium agent query --prompt-template security-code-review --source ./src --dry-run ``` **Save agent output to a file:** ``` > Save the review results to a JSON file ``` ```bash theme={null} vigolium agent query --prompt-template security-code-review --source ./src \ --output review-results.json ``` **List available templates and providers:** ``` > What prompt templates and olium providers are available? ``` ```bash theme={null} vigolium agent --list-templates vigolium agent --list-agents ``` **Built-in templates include:** * `security-code-review`: Comprehensive security review * `injection-sinks`: Find injection sinks * `auth-bypass`: Auth bypass vectors * `secret-detection`: Hardcoded secrets * `endpoint-discovery`: API endpoints from source * `api-input-gen`: Generate test inputs * `curl-command-gen`: Generate cURL commands * `attack-surface-mapper`: Map attack surface * `nextjs-security-audit`: Next.js security review * `react-xss-audit`: React XSS audit * `cors-csrf-review`: CORS/CSRF config audit #### Agent Query (Freeform Prompt) **Inline prompt:** ``` > Ask the agent to review code for vulnerabilities ``` ```bash theme={null} vigolium agent query 'review this code for SQL injection vulnerabilities' ``` **Named prompt flag:** ``` > Analyze the authentication flow ``` ```bash theme={null} vigolium agent query --prompt 'analyze the authentication flow for bypass vectors' ``` **Pipe prompt from stdin:** ``` > Pipe a prompt to the agent ``` ```bash theme={null} echo "check for SSRF in the URL-fetching handler" | vigolium agent query --stdin ``` **Custom prompt file with a specific provider:** ``` > Run a custom prompt file through Anthropic ``` ```bash theme={null} vigolium agent query --provider anthropic-api-key --prompt-file custom-prompt.md ``` **Pick a different model:** ``` > Run the review on a frontier model ``` ```bash theme={null} vigolium agent query --provider anthropic-api-key --model claude-opus-4-7 \ 'comprehensive security review of all handlers' ``` #### Agent Autopilot (Autonomous Scanning) **Basic autonomous scan:** ``` > Let the AI autonomously scan the target ``` ```bash theme={null} vigolium agent autopilot -t https://example.com ``` **With source code context and focus area:** ``` > Autonomous scan focused on auth bypass, with source code for context ``` ```bash theme={null} vigolium agent autopilot -t https://api.example.com --source ./src \ --prompt "focus on auth bypass" ``` **Custom limits (lighter intensity, shorter wall-clock):** ``` > Run a quick scan capped at 15 minutes ``` ```bash theme={null} vigolium agent autopilot -t https://example.com --intensity quick --max-duration 15m ``` **Preview the system prompt (dry run):** ``` > Show me what system prompt the autopilot agent would receive ``` ```bash theme={null} vigolium agent autopilot -t https://example.com --dry-run ``` **Custom system prompt:** ``` > Use my own system prompt for autopilot ``` ```bash theme={null} vigolium agent autopilot -t https://example.com --system-prompt-file my-system-prompt.md ``` **Start from project traffic, Burp history, and app docs:** ``` > Use the traffic we already captured and the application's role documentation ``` ```bash theme={null} vigolium agent autopilot -t https://example.com \ --burp-bridge-url http://127.0.0.1:9009 \ --prior-context auto --knowledge-base ./app-docs ``` **Resume a durable run:** ```bash theme={null} vigolium agent autopilot --resume \ --prompt "finish the remaining privilege-boundary checks" ``` **Use a different olium provider:** ``` > Run autopilot through Google Vertex ``` ```bash theme={null} vigolium agent autopilot -t https://example.com --provider google-vertex --model gemini-2.5-pro ``` **Autopilot security model:** * Yolo-mode Bash — autopilot runs the model's `bash` calls without an approval prompt. Common scanner, record, replay, finding, OAST, auth-session, and Burp operations are also first-class tools; the model can still shell out for uncovered CLI operations. * The engine only hard-rejects catastrophic patterns (`rm -rf /`, `dd` to block devices, fork bombs, `mkfs` against real devices). There is no `vigolium`-only command allowlist. * Per-tool timeout: 5 minutes (`ToolTimeout`). * Intensity-driven defaults: quick=150 / balanced=500 / deep=1500 commands (`MaxCommands`), browser enabled at every intensity. * In legacy/shadow mode, `report_finding` soft-warns at 50 findings and hard-caps at 200. In enforced mode, `propose_candidate` routes claims through fresh-context verification before promotion. * Run autopilot in a disposable container or VM scoped to in-scope targets — see [Security Warning](/others/security-warning). #### Agent Swarm (AI-Guided Multi-Phase Scan) **Basic swarm scan with discovery (all phases):** ``` > Run the full AI swarm scan ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com ``` The swarm runs ten phases in order (most are conditional — see [Swarm](/agentic-scan/swarm) for full detail): 1. **native-normalize** — parse `--input`/stdin/record-uuid into HTTP records 2. **auth** *(optional)* — browser-driven login, writes auth headers/cookies 3. **source-analysis** *(if `--source`)* — 4-call wave that emits routes, session-config, and source extensions 4. **code-audit** *(if `--code-audit`)* — code-level security audit, findings → DB 5. **native-discover** *(if `--discover`)* — crawl/spider/JS-scan 6. **plan** — master agent picks modules and writes an extensions spec 7. **native-extension** — compile/validate generated JS extensions (Sobek) 8. **native-scan** — `runner.RunNativeScan()` with the planned modules + extensions 9. **triage** *(if `--triage`)* — AI verifies findings, may request targeted rescans 10. **native-rescan** — targeted rescan loop, bounded by `--max-iterations` **Swarm with focus area and source code:** ``` > Swarm scan focused on SQL injection, with source code ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com \ --prompt "focus on SQL injection" --source ./src ``` **Control rescan iterations:** ``` > Allow up to 3 triage->rescan iterations ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com --max-iterations 3 ``` **Skip discovery and start from planning (use existing DB data):** ``` > I already have traffic in the database, start from planning ``` ```bash theme={null} vigolium agent swarm -t https://example.com --start-from plan ``` **Skip triage (just discover -> plan -> scan):** ``` > Run swarm but skip triage and rescan ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com --skip triage --skip rescan ``` **Use a scanning profile:** ``` > Run swarm with the deep scanning profile ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com --profile deep ``` **Preview agent prompts (dry run):** ``` > Show me the prompts without executing ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com --dry-run ``` **Specific source files for agent context:** ``` > Only include routes.go and handlers.go as context ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com --source ./src \ --files "routes.go,handlers.go" ``` **Use a different agent backend:** ``` > Run swarm with Gemini ``` ```bash theme={null} vigolium agent swarm --discover -t https://example.com \ --provider google-vertex --model gemini-2.5-pro ``` *** ### 7. Traffic & Results Browsing **Browse all stored HTTP traffic:** ``` > Show me the HTTP traffic in the database ``` ```bash theme={null} vigolium traffic ``` **Fuzzy search traffic:** ``` > Show traffic related to login ``` ```bash theme={null} vigolium traffic login ``` **Tree view (hierarchical URL structure):** ``` > Show traffic as a directory tree ``` ```bash theme={null} vigolium traffic --tree ``` **Burp-style colored output:** ``` > Show traffic in Burp Suite style ``` ```bash theme={null} vigolium traffic --burp ``` **Filter by host, method, status:** ``` > Show POST and PUT requests to api.example.com that returned 200 ``` ```bash theme={null} vigolium traffic --host api.example.com --method POST,PUT --status 200 ``` **Filter by date range:** ``` > Show traffic from January 2024 ``` ```bash theme={null} vigolium traffic --from 2024-01-01 --to 2024-01-31 ``` **Search in request/response body:** ``` > Find traffic containing "password" in the body ``` ```bash theme={null} vigolium traffic --body password ``` **Search in headers:** ``` > Find traffic with JWT tokens in headers ``` ```bash theme={null} vigolium traffic --header "Bearer" ``` **Custom columns:** ``` > Show host, method, path, status, and auth columns ``` ```bash theme={null} vigolium traffic --columns HOST,METHOD,PATH,STATUS,AUTH ``` **Watch mode (auto-refresh):** ``` > Monitor traffic in real-time, refresh every 5 seconds ``` ```bash theme={null} vigolium traffic --watch 5s ``` **View raw HTTP request/response:** ``` > Show raw traffic for the last 5 records ``` ```bash theme={null} vigolium traffic --raw --limit 5 ``` **Browse findings:** ``` > Show all vulnerability findings ``` ```bash theme={null} vigolium finding ``` **Filter findings by severity:** ``` > Show only high and critical findings ``` ```bash theme={null} vigolium finding --severity high,critical ``` **Search findings:** ``` > Find SQL injection findings ``` ```bash theme={null} vigolium finding --search "sql injection" ``` **Watch findings in real-time:** ``` > Monitor findings as they come in ``` ```bash theme={null} vigolium finding --watch 5s ``` **Render a finding as Markdown (evidence + request/response):** ``` > Give me finding 42 as Markdown I can paste into a report ``` ```bash theme={null} vigolium finding --id 42 --markdown > finding-42.md ``` **Read a standalone export without a project DB:** ``` > Browse the high-severity findings in this scan.jsonl export ``` ```bash theme={null} vigolium finding -S --db ./scan.jsonl --min-severity high vigolium traffic -S --db ./scan.sqlite --status 500 -n 20 ``` `-S/--stateless` + `--db` reads a `--format jsonl` export or a standalone `.sqlite` directly with project scoping off, writing nothing to your project DB. **Replay stored traffic (re-send requests):** ``` > Replay login-related requests and compare responses ``` ```bash theme={null} vigolium replay --search login ``` **Replay and replace stored responses:** ``` > Replay requests to api.example.com and update stored responses ``` ```bash theme={null} vigolium replay --host api.example.com --in-replace ``` *** ### 8. Data Management **Database statistics:** ``` > Show me database stats ``` ```bash theme={null} vigolium db stats ``` **Detailed stats with host breakdown:** ``` > Show detailed stats broken down by host ``` ```bash theme={null} vigolium db stats --detailed ``` **Stats for a specific host:** ``` > Stats for example.com only ``` ```bash theme={null} vigolium db stats --host example.com ``` **Live-updating stats:** ``` > Watch database stats, refresh every 10 seconds ``` ```bash theme={null} vigolium db stats --watch 10s ``` **List database records with filters:** ``` > Show findings table, critical and high severity ``` ```bash theme={null} vigolium db ls --table findings --severity critical,high ``` **List available tables and columns:** ``` > What tables are in the database? What columns does findings have? ``` ```bash theme={null} vigolium db ls --list-tables vigolium db ls --list-columns --table findings ``` **Clean records by hostname:** ``` > Delete all records for old-target.com ``` ```bash theme={null} vigolium db clean --host old-target.com --force ``` **Clean old records with dry-run preview:** ``` > Preview what would be deleted before January 2024 ``` ```bash theme={null} vigolium db clean --before 2024-01-01 --dry-run ``` **Clean only findings (keep HTTP records):** ``` > Delete info-severity findings but keep the HTTP records ``` ```bash theme={null} vigolium db clean --findings-only --severity info --force ``` **Clean orphaned findings:** ``` > Remove findings without associated HTTP records ``` ```bash theme={null} vigolium db clean --orphans ``` **Reset entire database:** ``` > Wipe the entire database and start fresh ``` ```bash theme={null} vigolium db reset --force ``` **Reclaim disk space after deletion:** ``` > Delete last month's records and reclaim the space ``` ```bash theme={null} vigolium db clean --before 2026-07-01 # VACUUM runs automatically after a delete ``` *** ### 9. Export & Reports **Full JSONL export:** ``` > Export everything from the database as JSONL ``` ```bash theme={null} vigolium export --format jsonl -o full-export.jsonl ``` **Export only findings:** ``` > Export just the findings ``` ```bash theme={null} vigolium export --format jsonl --only findings -o findings.jsonl ``` **Export findings and HTTP records:** ``` > Export findings and associated HTTP traffic ``` ```bash theme={null} vigolium export --format jsonl --only findings,http -o results.jsonl ``` **HTML report:** ``` > Generate an interactive HTML report ``` ```bash theme={null} vigolium export --format html -o report.html ``` **Lightweight export (omit raw HTTP request/response bytes):** ``` > Export URLs only, without raw request/response data ``` ```bash theme={null} vigolium export --omit-response --only http -o urls.jsonl ``` **Export with search filter:** ``` > Export only records matching example.com ``` ```bash theme={null} vigolium export --search "example.com" -o filtered.jsonl ``` **Database-level export as CSV:** ``` > Export HTTP records as CSV ``` ```bash theme={null} vigolium db export -f csv -o records.csv ``` **Export as Markdown:** ``` > Export records as a Markdown report ``` ```bash theme={null} vigolium db export -f markdown -o report.md ``` **Export raw requests only:** ``` > Export just the raw HTTP requests ``` ```bash theme={null} vigolium db export -f raw --request-only -o requests.txt ``` **Export filtered by host and date:** ``` > Export records for example.com from 2024 onwards ``` ```bash theme={null} vigolium db export -f csv -o records.csv --host example.com --from 2024-01-01 ``` **Export a single record by UUID:** ``` > Export record abc12345 ``` ```bash theme={null} vigolium db export --uuid abc12345 ``` **Export module registry:** ``` > Export all available scanner modules ``` ```bash theme={null} vigolium export --only modules ``` *** ### 10. Whitebox / Source-Aware Scanning Source-aware scanning runs through the agent modes via the `--source` flag — the native `vigolium scan` does not take source code. Use `agent swarm` for an AI-driven source-aware DAST scan, or `agent audit` for a source-only (SAST) review. **Source-aware scan with local source code:** ``` > Whitebox scan with source code in ./src ``` ```bash theme={null} vigolium agent swarm --target https://example.com --source ./src ``` `--target` is required whenever `--source` is given. The source-analysis and code-audit phases run automatically when `--source` is present. **Scan with source cloned from Git:** ``` > Clone the repo and run a source-aware scan ``` ```bash theme={null} # Clone first, then point --source at the local checkout git clone https://github.com/org/repo /tmp/repo vigolium agent swarm --target https://example.com --source /tmp/repo ``` **Run a source-code audit (SAST) only:** ``` > Run a security audit on the source code ``` ```bash theme={null} vigolium agent audit --source /path/to/app ``` **Deeper multi-phase audit:** ``` > Run a thorough deep-mode source audit ``` ```bash theme={null} vigolium agent audit --source /path/to/app --mode deep ``` *** ### 11. JavaScript Extensions **Install preset examples:** ``` > Install the example extension scripts ``` ```bash theme={null} vigolium ext preset ``` **View the extension API reference:** ``` > Show me the extension API docs ``` ```bash theme={null} vigolium ext docs vigolium ext docs --example # with code examples vigolium ext docs http # filter by namespace ``` **List loaded extensions:** ``` > Show currently loaded extensions ``` ```bash theme={null} vigolium ext ls vigolium ext ls --type active # active extensions only ``` **Quick-test JS code inline:** ``` > Test a JS expression ``` ```bash theme={null} vigolium ext eval 'vigolium.log.info("hello from extension")' vigolium ext eval 'vigolium.utils.md5("password")' ``` **Evaluate a JS file:** ``` > Run a JS script file ``` ```bash theme={null} vigolium ext eval --ext-file script.js ``` **Run a custom extension against a target:** ``` > Run my custom scanner extension ``` ```bash theme={null} vigolium run extension -t https://example.com --ext custom-check.js # or vigolium run ext -t https://example.com --ext custom-check.js ``` **Run extension alongside built-in modules:** ``` > Run built-in modules plus my custom extension ``` ```bash theme={null} vigolium scan -t https://example.com --ext custom-check.js ``` **Run only extensions (skip built-in modules):** ``` > Run only my custom extensions ``` ```bash theme={null} vigolium scan -t https://example.com --only extension --ext custom-check.js ``` **Load multiple extensions:** ``` > Run three extensions together ``` ```bash theme={null} vigolium scan -t https://example.com --ext check1.js --ext check2.js --ext check3.js ``` **Load all extensions from a directory:** ``` > Run all extensions in my extensions folder ``` ```bash theme={null} vigolium scan -t https://example.com --ext-dir ./my-extensions/ ``` **Ask the agent to write an extension:** ``` > Write me a passive extension that checks for missing security headers ``` The agent will generate a JS file like: ```javascript theme={null} module.exports = { id: "missing-security-headers", name: "Missing Security Headers", type: "passive", severity: "low", confidence: "certain", scope: "response", tags: ["headers", "misconfiguration", "light"], scanTypes: ["per_request"], scanPerRequest: function(ctx) { if (!ctx.response) return null; var headers = ctx.response.headers; var missing = []; if (!headers["strict-transport-security"]) missing.push("HSTS"); if (!headers["x-content-type-options"]) missing.push("X-Content-Type-Options"); if (!headers["x-frame-options"] && !headers["content-security-policy"]) { missing.push("X-Frame-Options/CSP"); } if (missing.length === 0) return null; return { url: ctx.request.url, name: "Missing Security Headers: " + missing.join(", "), severity: "low", description: "Response is missing: " + missing.join(", ") }; } }; ``` **Ask the agent to write an AI-augmented extension:** ``` > Write an active extension that uses AI to generate XSS payloads ``` The agent will generate a JS file using `vigolium.agent.generatePayloads()` and `vigolium.agent.analyzeResponse()`. **YAML extension (simple pattern matching):** ``` > Write a YAML extension that detects stack traces and SQL errors ``` ```yaml theme={null} id: error-pattern-detector name: Verbose Error Pattern Detector type: passive severity: suspect confidence: tentative scope: response tags: [error, information-disclosure, light] scanTypes: [per_request] patterns: - name: "Stack Trace Detected" regex: "(?:at\\s+[\\w.$]+\\(|Traceback \\(most recent|Exception in thread)" severity: suspect - name: "SQL Error Message" regex: "(?:mysql_|pg_|sqlite_|ORA-\\d{5}|SQLSTATE\\[)" severity: medium ``` *** ### 12. Configuration & Projects **View all configuration:** ``` > Show the current vigolium config ``` ```bash theme={null} vigolium config ls ``` **View a specific config section:** ``` > Show scope configuration ``` ```bash theme={null} vigolium config ls scope vigolium config ls scanning_pace vigolium config ls server ``` **Set configuration values:** ``` > Set the default strategy to deep ``` ```bash theme={null} vigolium config set scanning_strategy.default_strategy deep ``` **Set scope mode:** ``` > Set origin scope to strict ``` ```bash theme={null} vigolium config set scope.origin.mode strict ``` **Enable extensions globally:** ``` > Enable extensions in audit ``` ```bash theme={null} vigolium config set audit.extensions.enabled true ``` **View scope rules:** ``` > Show current scope rules ``` ```bash theme={null} vigolium scope view vigolium scope view host ``` **View scanning strategies:** ``` > Show available strategies and their phases ``` ```bash theme={null} vigolium strategy ``` **Create and manage projects:** ``` > Create a project, then switch to it ``` ```bash theme={null} vigolium project create my-project vigolium project list vigolium project use my-project ``` **Scope CLI operations to a project:** ``` > Scan within a specific project ``` ```bash theme={null} vigolium scan -t https://example.com --project-name my-project ``` **Project-scoped database access:** ``` > Show stats for my-project ``` ```bash theme={null} VIGOLIUM_PROJECT_UUID=a1b2c3d4-... vigolium db stats ``` *** ## Natural Language Examples These are examples of natural language prompts you can give to Claude Code or Codex with the skill installed. The agent will translate them into the correct vigolium commands. | You Say | Agent Runs | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | "Scan example.com" | `vigolium scan -t https://example.com` | | "Deep scan with spidering" | `vigolium scan -t --strategy deep` | | "Import my Burp export and scan it" | `vigolium scan -I burp -i export.xml` | | "Scan my OpenAPI spec with auth" | `vigolium scan -I openapi -i spec.yaml -t --spec-header "Authorization: Bearer ..."` | | "Only run XSS modules" | `vigolium scan -t --module-tag xss` | | "Review my code for security issues" | `vigolium agent query --prompt-template security-code-review --source ./src` | | "Autonomous scan focused on injection" | `vigolium agent autopilot -t --prompt "focus on injection"` | | "Run the full AI pipeline" | `vigolium agent swarm --discover -t ` | | "Show me all critical findings" | `vigolium finding --severity critical` | | "Export results as HTML report" | `vigolium export --format html -o report.html` | | "What traffic is in the database?" | `vigolium traffic` | | "Write me an extension that checks for exposed .env files" | Generates a JS extension file | | "Start the server with auto-scan" | `vigolium server -t --scan-on-receive` | | "Whitebox scan with my source code" | `vigolium scan -t --source ./src --strategy whitebox` | | "Clean up old scan data" | `vigolium db clean --before --force` | *** ## Tips & Best Practices 1. **Start with `scan -t`**: It's the most common command. Add flags incrementally. 2. **Use strategies**: `lite` for quick checks, `balanced` for most cases, `deep` for full coverage, `whitebox` when you have source code. 3. **Phase isolation**: Use `--only` or `vigolium run ` to iterate on a single phase without re-running the entire pipeline. 4. **Module tags**: Filter modules by technology (`spring`, `nodejs`) or vulnerability class (`xss`, `injection`) to reduce noise. 5. **Watch mode**: Add `--watch 5s` to `traffic`, `finding`, or `db stats` for real-time monitoring during long scans. 6. **Dry-run agents**: Always `--dry-run` first for agent commands to preview prompts before spending AI tokens. 7. **Swarm over autopilot**: Use `agent swarm --discover` for structured scans (lower cost, reproducible). Use `agent autopilot` for exploratory, creative scanning. 8. **Extensions for custom logic**: Write JS extensions instead of modifying core modules. They run alongside built-in modules with `--ext`. 9. **Projects for isolation**: Use `vigolium project create` to keep scan data separate across engagements. 10. **Export early**: Run `vigolium export --format html -o report.html` to share results as interactive reports. # Vigolium API Reference Source: https://docs.vigolium.com/api-overview Base URL: http://localhost:9002 (default). For detailed documentation on each endpoint category, see the individual reference pages below. ## Endpoint Index ### [Overview](/api-references/overview) Server startup, authentication, and general endpoints. The following routes are public (no auth): | Method | Endpoint | Description | | ------ | ----------------- | -------------------------------------------------------------- | | GET | `/` | Static UI | | GET | `/health` | Health check | | GET | `/server-info` | Server info (uptime, queue depth, totals) | | GET | `/swagger/*` | Swagger UI | | GET | `/metrics` | Prometheus metrics (response gated by `server.enable_metrics`) | | POST | `/api/auth/login` | File-based login → bearer token | | GET | `/api/info` | App info / build metadata | | GET | `/api/user/info` | Current user | ### [HTTP Records](/api-references/http-records) | Method | Endpoint | Description | | ------ | ------------------------- | ----------------------------------------- | | GET | `/api/http-records` | List HTTP records (paginated, filterable) | | GET | `/api/http-records/:uuid` | Get HTTP record detail | | DELETE | `/api/http-records/:uuid` | Delete HTTP record | ### [Findings](/api-references/findings) | Method | Endpoint | Description | | ------ | -------------------------- | ------------------------------------------- | | GET | `/api/findings` | List findings (paginated, filterable) | | GET | `/api/findings/:id` | Get finding detail | | PATCH | `/api/findings/:id/status` | Update finding status (open/triaged/closed) | | DELETE | `/api/findings/:id` | Delete finding | ### [Ingestion](/api-references/ingestion) | Method | Endpoint | Description | | ------ | ------------------ | --------------------------------------------------------------------------- | | POST | `/api/ingest-http` | Ingest HTTP data (URL, curl, OpenAPI, Burp, Postman, HAR, Nuclei) | | POST | `/api/import` | Bulk import scans/records/findings (incl. `gs://...` and uploaded archives) | ### [Scan](/api-references/scan) | Method | Endpoint | Description | | ------ | ------------------------- | ----------------------------------- | | POST | `/api/scan-url` | Scan a single URL | | POST | `/api/scan-request` | Scan a raw HTTP request | | POST | `/api/scans/run` | Trigger a target scan | | POST | `/api/scan-records` | Scan specific HTTP records by UUID | | POST | `/api/scan-all-records` | Scan filtered records | | GET | `/api/scan/status` | Current scan status (latest/global) | | GET | `/api/scans` | List scan history | | GET | `/api/scans/:uuid` | Get scan detail | | GET | `/api/scans/:uuid/logs` | Scan logs (SSE supported) | | DELETE | `/api/scans/:uuid` | Delete scan | | POST | `/api/scans/:uuid/stop` | Stop a running scan | | POST | `/api/scans/:uuid/pause` | Pause a scan | | POST | `/api/scans/:uuid/resume` | Resume a scan | | POST | `/api/scans/:uuid/update` | Adjust scan parameters mid-run | ### [Stats](/api-references/stats) | Method | Endpoint | Description | | ------ | ------------ | -------------------------- | | GET | `/api/stats` | Aggregated scan statistics | ### [Scope](/api-references/scope) | Method | Endpoint | Description | | ------ | ------------ | ------------------- | | GET | `/api/scope` | View scope config | | POST | `/api/scope` | Update scope config | ### [Config](/api-references/config) | Method | Endpoint | Description | | ------ | ------------- | -------------------- | | GET | `/api/config` | View configuration | | POST | `/api/config` | Update configuration | ### [Modules](/api-references/modules) | Method | Endpoint | Description | | ------ | -------------- | -------------------- | | GET | `/api/modules` | List scanner modules | ### [Storage](/api-references/storage) | Method | Endpoint | Description | | ------ | --------------------------------- | ------------------------------------------- | | POST | `/api/storage/upload-source` | Upload source code archive to cloud storage | | GET | `/api/storage/source/:key` | Download a previously uploaded source file | | GET | `/api/storage/results/:scan-uuid` | Download a scan result bundle (.tar.gz) | | POST | `/api/storage/presign` | Generate a presigned upload/download URL | ### [Diagnostics](/api-references/diagnostics) | Method | Endpoint | Description | | ------ | ------------------ | -------------------------------------------------------- | | GET | `/api/diagnostics` | System readiness check (DB, agent provider, tools, dirs) | ### [OAST Interactions](/api-references/oast-interactions) | Method | Endpoint | Description | | ------ | ---------------------------- | --------------------------- | | GET | `/api/oast-interactions` | List OAST interactions | | GET | `/api/oast-interactions/:id` | Get OAST interaction detail | | DELETE | `/api/oast-interactions/:id` | Delete OAST interaction | ### [Extensions](/api-references/extensions) | Method | Endpoint | Description | | ------ | ----------------------- | -------------------------------- | | GET | `/api/extensions` | List extensions | | GET | `/api/extensions/:name` | Get extension (with raw content) | | PUT | `/api/extensions/:name` | Edit extension | | GET | `/api/extensions/docs` | List JS API functions | ### [Projects](/api-references/projects) | Method | Endpoint | Description | | ------ | --------------------- | ------------------------------------------ | | GET | `/api/projects` | List projects + aggregate stats | | POST | `/api/projects` | Create project | | GET | `/api/projects/:uuid` | Get project | | PUT | `/api/projects/:uuid` | Update project | | DELETE | `/api/projects/:uuid` | Delete project (reassigns data to default) | ### [Database](/api-references/database) | Method | Endpoint | Description | | ------ | ----------------------------------- | ---------------------- | | GET | `/api/db/tables` | List tables | | GET | `/api/db/tables/:table/columns` | Table column metadata | | GET | `/api/db/tables/:table/records` | Generic record listing | | GET | `/api/db/tables/:table/records/:id` | Get record | | POST | `/api/db/tables/:table/records` | Insert record (admin) | | PUT | `/api/db/tables/:table/records/:id` | Update record (admin) | | DELETE | `/api/db/tables/:table/records/:id` | Delete record (admin) | ### [Agent](/api-references/agent) | Method | Endpoint | Description | | ------ | ------------------------------------------ | --------------------------------------------------------------------- | | POST | `/api/agent/run/query` | Single-shot agent prompt execution | | POST | `/api/agent/run/autopilot` | Autonomous AI-driven scanning session | | POST | `/api/agent/run/swarm` | AI-guided multi-phase vulnerability swarm | | POST | `/api/agent/run/audit` | Source-audit driver dispatcher (`driver: auto\|both\|audit\|piolium`) | | GET | `/api/agent/status/list` | List agent runs | | GET | `/api/agent/status/:id` | Agent run status | | GET | `/api/agent/sessions` | Paginated session history | | GET | `/api/agent/sessions/:id` | Full session detail | | GET | `/api/agent/sessions/:id/logs` | Read or tail `runtime.log` (SSE supported) | | GET | `/api/agent/sessions/:id/artifacts` | List session artifact files | | GET | `/api/agent/sessions/:id/artifacts/{name}` | Read a specific artifact | | POST | `/api/agent/chat/completions` | OpenAI-compatible chat completions | # Vigolium API Reference, Agent Source: https://docs.vigolium.com/api-references/agent AI agent API with query, autopilot, swarm, and audit run modes, SSE streaming, session history, and OpenAI-compatible chat. ## Overview The agent API provides four run modes that mirror the `vigolium agent` CLI subcommands, plus session history and status endpoints: | Endpoint | CLI Equivalent | Description | | ---------------------------------------------- | -------------------------- | ------------------------------------------------------- | | `POST /api/agent/run/query` | `vigolium agent query` | Single-shot prompt execution | | `POST /api/agent/run/autopilot` | `vigolium agent autopilot` | Autonomous AI-driven scanning session | | `POST /api/agent/run/swarm` | `vigolium agent swarm` | AI-guided multi-phase vulnerability swarm | | `POST /api/agent/run/audit` | `vigolium agent audit` | Source-audit dispatcher (vigolium-audit and/or piolium) | | `GET /api/agent/status/list` | , | List runs with in-memory status | | `GET /api/agent/status/:id` | , | Get run status by ID | | `POST /api/agent/scans/:uuid/cancel` | , | Cancel an in-flight agent run | | `GET /api/agent/sessions` | `vigolium agent sessions` | Paginated session history from DB | | `GET /api/agent/sessions/:id` | , | Full session detail with debug fields | | `GET /api/agent/sessions/:id/logs` | , | Raw `runtime.log` (plain text or SSE tail) | | `GET /api/agent/sessions/:id/artifacts` | , | List session artifact files | | `GET /api/agent/sessions/:id/artifacts/{name}` | , | Read a specific artifact (10 MiB default cap) | | `POST /api/agent/chat/completions` | , | OpenAI-compatible chat completions | Concurrency is bounded by two pools (`server.agent_heavy_max` for `autopilot`/`swarm`/`audit`, `server.agent_light_max` for `query` and chat completions) plus a per-project heavy cap. New heavy runs return `409 Conflict` once the cap is hit; light runs queue until `server.agent_queue_timeout` elapses and then return `429 Too Many Requests`. > **Server-default provider, BYOK per request.** The server resolves the olium provider from `agent.olium.*` in `vigolium-configs.yaml` (which keeps prompt caches stable across requests), but every agent run endpoint also accepts per-request BYOK credentials. Provide any subset of `api_key`, `oauth_token`, `oauth_cred_file`, or `oauth_cred_json` on the same request to override per call. The audit dispatcher additionally accepts `audit_auth` and `piolium_auth` objects to apply different credentials to each driver. *** ## POST /api/agent/run/query, Single-Shot Agent Run Starts an AI agent run with a prompt template, file, or inline prompt. Returns `202 Accepted` (async) or an SSE stream when `stream: true`. **Request body:** | Field | Type | Required | Description | | ----------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------- | | `agent` | string | No | Optional descriptive label persisted to the run record (provider is server-side from `agent.olium.*`) | | `prompt_template` | string | No\* | Name of a prompt template (from `~/.vigolium/prompts/`) | | `prompt_file` | string | No\* | Path to a prompt file on disk | | `prompt` | string | No\* | Inline prompt text | | `source` | string | No | Path to source code for context. Legacy alias: `repo_path`. | | `files` | string\[] | No | Specific files to include as context | | `append` | string | No | Additional text appended to the prompt | | `instruction` | string | No | Custom instruction appended to the prompt | | `source_label` | string | No | Source label for findings ingested from agent output | | `scan_uuid` | string | No | Link results to a specific scan UUID | | `stream` | bool | No | If `true`, returns an SSE stream instead of 202 async response | \* At least one of `prompt_template`, `prompt_file`, or `prompt` is required. ```bash theme={null} # Run with a prompt template curl -s -X POST http://localhost:9002/api/agent/run/query \ -H "Content-Type: application/json" \ -d '{ "prompt_template": "security-code-review", "source": "/home/user/src/my-app" }' | jq . # Run with an inline prompt curl -s -X POST http://localhost:9002/api/agent/run/query \ -H "Content-Type: application/json" \ -d '{ "prompt": "Analyze the authentication flow for vulnerabilities", "source": "/home/user/src/my-app", "files": ["src/auth/login.py", "src/auth/session.py"] }' | jq . ``` **Response (202):** ```json theme={null} { "agentic_scan_uuid": "agt-550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "query run started" } ``` *** ## POST /api/agent/run/autopilot, Autonomous Scanning Session Launches an AI agent that autonomously discovers, scans, and triages vulnerabilities using vigolium CLI commands. When `source` is provided, vigolium-audit runs first, native context and planning artifacts are prepared, and then the autonomous operator session starts. The HTTP schema intentionally keeps structured `focus`, `instruction`, browser, and credential fields. v0.3.1 CLI preparation/debugging flags such as `--resume`, `--prior-context`, `--knowledge-base`, `--burp-bridge-url`, `--session-dir`, and `--transcript` are not fields on this endpoint. **Request body:** | Field | Type | Required | Description | | ------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prompt` | string | No | Natural language scan prompt (parsed into target/source/focus when explicit fields are empty) | | `intensity` | string | No | Scan intensity preset: `"quick"`, `"balanced"` (default), or `"deep"`. Bundles `max_commands`, `timeout`, `audit_mode`, and `browser` settings | | `target` | string | No\* | Target URL to scan (derived from `input` if not set) | | `input` | string | No | Raw input (curl, raw HTTP, Burp XML, URL), target extracted automatically | | `agent` | string | No | Optional descriptive label persisted to the run record | | `source` | string | No | Path to source code, git URL (with optional OAuth token), or archive file (`.zip`, `.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`). Legacy alias: `repo_path`. | | `files` | string\[] | No | Specific files to include (relative to `source`). Auto-populated from `diff` when not set | | `diff` | string | No | Focus on changed code: GitHub PR URL (`github.com/.../pull/123`), git ref range (`main...branch`), or `HEAD~N` | | `last_commits` | int | No | Focus on last N commits (shorthand for `diff: "HEAD~N"`) | | `focus` | string | No | Focus area hint (e.g. `"API injection"`, `"auth bypass"`) | | `instruction` | string | No | Custom instruction appended to the prompt | | `timeout` | string | No | Go duration string (default `"6h"`) | | `max_commands` | int | No | Max LLM turns the agent can take. Intensity preset overrides default: quick=`150`, balanced=`500`, deep=`1500` | | `browser` | bool | No | Enable agent-browser for browser-based interactions | | `credentials` | string | No | Credentials for auth preflight (e.g. `"admin/admin123"`) | | `credential_sets` | object\[] | No | Multiple credential sets keyed by role/persona | | `auth_required` | bool | No | Require auth/session preparation before the operator starts | | `requires_browser` | bool | No | Require browser-assisted auth instead of HTTP-only preflight | | `browser_start_url` | string | No | Explicit browser/login start URL | | `focus_routes` | string\[] | No | Protected/browser-focused routes to prioritize | | `dry_run` | bool | No | Render the prompt without executing the agent | | `stream` | bool | No | If `true`, returns an SSE stream | | `scan_uuid` | string | No | Link results to a specific scan UUID | | `project_uuid` | string | No | Scope results to a project (falls back to `X-Project-UUID` header) | | `audit_mode` | string | No | Vigolium-audit mode: `"lite"` (default, 3-phase), `"balanced"` (9-phase), `"deep"` (12-phase), `"mock"` | | `no_audit` | bool | No | Disable automatic vigolium-audit (enabled by default when `source` is set) | | `audit` | string | No | Shorthand for `audit_mode`/`no_audit`: `"lite"`, `"balanced"`, `"deep"`, `"mock"`, or `"off"` | | `piolium` | string | No | Run a piolium audit in parallel with the autopilot prep. Same value set as `audit` | | `triage` | bool | No | Enable an AI triage pass over the autopilot's findings on completion | | `upload_results` | bool | No | Upload session bundle to cloud storage on completion (requires `storage` config) | | `api_key` | string | No | BYOK API key for this request (overrides server config) | | `oauth_token` | string | No | BYOK OAuth token for this request | | `oauth_cred_file` | string | No | Path to an OAuth credential JSON file readable by the server | | `oauth_cred_json` | string | No | Inline OAuth credential JSON | \* At least one of `target`, `input`, `source`, `diff`, or `prompt` is required. **Source resolution:** The `source` field accepts local paths, git URLs (HTTPS or SSH), git URLs with embedded OAuth tokens (`https://oauth2:TOKEN@github.com/...`), and archive files. Git repos are cloned with `--depth 1` and archives are extracted into the session directory. OAuth tokens are stripped from logs. **Diff resolution:** When `diff` is set, the changed file list auto-populates `files` and the patch content is included in the agent prompt. For PR URLs without `source`, the repo is auto-cloned. GitHub PRs use the GitHub REST API directly (no `gh` CLI required). The `GITHUB_TOKEN` env var is used as a fallback. **Quick scan (CI/PR review):** ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/autopilot \ -H "Content-Type: application/json" \ -d '{ "target": "http://localhost:3000", "source": "/home/user/src/my-app", "intensity": "quick", "diff": "https://github.com/org/repo/pull/42" }' | jq . ``` **Balanced scan (default):** ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/autopilot \ -H "Content-Type: application/json" \ -d '{ "target": "http://localhost:3000", "source": "/home/user/src/my-app", "focus": "authentication bypass", "stream": true }' ``` **Deep scan:** ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/autopilot \ -H "Content-Type: application/json" \ -d '{ "target": "http://localhost:3000", "source": "/home/user/src/my-app", "intensity": "deep", "instruction": "Test all API endpoints. Focus on IDOR, auth bypass, and injection.", "stream": true }' ``` **Response (202):** ```json theme={null} { "agentic_scan_uuid": "agt-550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "autopilot run started" } ``` *** ## POST /api/agent/run/swarm, AI-Guided Vulnerability Swarm Launches an AI-guided multi-phase vulnerability swarm. The master agent analyzes inputs, selects scanner modules, generates custom JS extensions, executes scans, and optionally triages results. The swarm phases are: 1. **Normalize**: Parse and normalize inputs (native, no AI) 2. **Auth**: Browser-based login (native, optional; the API uses `auth: true`, and browser tooling is enabled by the intensity/config path) 3. **Source Analysis**: AI agents extract routes, auth flows, and extensions from source code *(conditional, requires `source`)* 4. **Code Audit**: AI security code audit *(conditional, requires `code_audit: true`)* 5. **Discovery**: Content discovery and spidering *(conditional, requires `discover` flag)* 6. **Plan**: Master agent analyzes targets, selects modules, generates quick checks and extensions 7. **Extension**: Validate, merge, and write JS extensions to disk (native) 8. **Scan**: Execute scanner modules with agent-selected filters and extensions (native) 9. **Triage**: AI agent reviews findings, confirms or marks as false positive *(optional, requires `triage: true`)* 10. **Rescan**: Targeted re-scanning based on triage follow-ups *(conditional, triggered by triage)* AI agents are called at phases 3, 4, 6, and 9. When inputs exceed `master_batch_size` records, the master agent runs in parallel batches (default 5 records per batch) with plan merging. **Request body, Inputs:** | Field | Type | Required | Description | | ---------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `prompt` | string | No | Natural language scan prompt | | `intensity` | string | No | `"quick"`, `"balanced"` (default), `"deep"`, bundles `discover`, `triage`, `code_audit`, `max_iterations`, `audit`, concurrency, and duration settings | | `input` | string | No\* | Single input (URL, curl command, raw HTTP, Burp XML) | | `inputs` | string\[] | No\* | Multiple inputs | | `http_request_base64` | string | No\* | Base64-encoded raw HTTP request | | `http_response_base64` | string | No | Base64-encoded raw HTTP response (attached to the request above) | | `url` | string | No | URL hint for parsing the base64 request | \* At least one of `input`, `inputs`, `http_request_base64`, `source`, `diff`, or `prompt` is required. **Source analysis:** | Field | Type | Required | Description | | ---------------------- | --------- | -------- | --------------------------------------------------------------------------- | | `source` | string | No | Path to source code, git URL, or archive file. Legacy alias: `source_path`. | | `files` | string\[] | No | Specific source files to include (relative to `source`) | | `diff` | string | No | Focus on changed code: PR URL, git ref range, or `HEAD~N` | | `last_commits` | int | No | Focus on last N commits | | `source_analysis_only` | bool | No | Run only the source analysis phase and exit | **Scanning parameters:** | Field | Type | Required | Description | | ------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------- | | `vuln_type` | string | No | Vulnerability type focus (e.g. `"sqli"`, `"xss"`) | | `focus` | string | No | Broad focus area hint | | `instruction` | string | No | Custom instruction appended to agent prompts | | `module_names` | string\[] | No | Explicit module IDs to use | | `only_phase` | string | No | Isolate a single phase | | `skip_phases` | string\[] | No | Skip specific phases | | `start_from` | string | No | Start a new run at a specific phase, marking earlier phases complete | | `max_iterations` | int | No | Max triage→rescan rounds (default depends on intensity) | | `discover` | bool | No | Run discovery+spidering before master agent planning | | `code_audit` | bool | No | Enable AI security code audit phase (requires `source`) | | `triage` | bool | No | Enable AI triage and rescan phases | | `force_extensions` | bool | No | Force extension agent to run even when the master returned no extensions | | `profile` | string | No | Scanning profile name (e.g. `"light"`, `"thorough"`) | | `audit` | string | No | Background vigolium-audit: `"lite"`, `"balanced"`, `"deep"`, `"mock"`, `"off"`. Requires `source`. | | `piolium` | string | No | Background piolium audit (run in parallel with vigolium-audit). Same value set as `audit`. | | `auth` | object | No | Per-host auth configuration (browser-driven login + token rules) | | `auth_required` | bool | No | Require auth/session preparation before the master agent starts | | `requires_browser` | bool | No | Require browser-assisted auth | | `browser_start_url` | string | No | Explicit browser/login start URL | | `focus_routes` | string\[] | No | Protected/browser-focused routes to prioritize | | `credentials` | string | No | Credentials for auth preflight (e.g. `"admin/admin123"`) | | `credential_sets` | object\[] | No | Multiple credential sets keyed by role/persona | **Concurrency tuning:** | Field | Type | Required | Description | | -------------------- | ------ | -------- | ----------------------------------------------------------------- | | `batch_concurrency` | int | No | Max parallel master agent batches (0 = auto) | | `max_master_retries` | int | No | Max master agent retries on parse failure (default `3`) | | `sa_max_concurrency` | int | No | Max parallel source analysis sub-agents (default `3`) | | `max_plan_records` | int | No | Max records sent to plan agent (0 = default `10`) | | `master_batch_size` | int | No | Max records per master agent batch (0 = default `5`) | | `probe_concurrency` | int | No | Max parallel probe requests (0 = default `10`) | | `probe_timeout` | string | No | Per-request probe timeout as Go duration | | `max_probe_body` | int | No | Max response body size in bytes during probing (0 = default 2 MB) | **Output / scoping:** | Field | Type | Required | Description | | ---------------- | ------ | -------- | ---------------------------------------------------- | | `agent` | string | No | Optional descriptive label | | `dry_run` | bool | No | Render prompts without executing agents | | `show_prompt` | bool | No | Include rendered prompts in output | | `stream` | bool | No | If `true`, returns an SSE stream with phase events | | `timeout` | string | No | Go duration string (default `"12h"`) | | `project_uuid` | string | No | Scope results to a project | | `scan_uuid` | string | No | Link results to a specific scan UUID | | `upload_results` | bool | No | Upload session bundle to cloud storage on completion | **Examples:** ```bash theme={null} # Swarm a single URL curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -d '{ "input": "https://example.com/api/search?q=test" }' | jq . # Swarm a curl command (auto-detected) curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -d '{ "input": "curl -X POST -H '\''Content-Type: application/json'\'' -d '\''{\"user\":\"admin\"}'\'' https://example.com/api/login" }' | jq . # Source-aware with code audit + discovery — full pipeline curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -d '{ "input": "https://example.com", "source": "/home/user/src/my-app", "discover": true, "code_audit": true, "triage": true, "instruction": "Focus on business logic flaws in the payment flow", "profile": "thorough" }' | jq . # Source-aware with background vigolium-audit curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -d '{ "input": "https://example.com", "source": "/home/user/src/my-app", "discover": true, "audit": "lite" }' | jq . # Diff-focused on a GitHub PR curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -d '{ "input": "http://localhost:3000", "source": "/home/user/src/my-app", "diff": "https://github.com/org/repo/pull/42" }' | jq . # SSE streaming with project scoping curl -N -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -H "X-Project-UUID: proj-123" \ -d '{ "input": "https://example.com", "stream": true, "timeout": "30m", "intensity": "deep" }' ``` **Response (202):** ```json theme={null} { "agentic_scan_uuid": "agt-550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "swarm run started" } ``` *** ## POST /api/agent/run/audit, Source-Audit Driver Dispatcher Launches a multi-phase source-code audit driven by the embedded vigolium-audit harness and/or the standalone piolium harness against a single source tree under one AgenticScan. Per-driver child rows hang off the parent run; the dispatcher runs a project-wide findings dedup pass once its drivers exit. **Request body, core fields:** | Field | Type | Required | Description | | ---------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | string | Yes | Path to source code, git URL (HTTPS or SSH), or archive file. Legacy alias: `repo_path`. | | `driver` | string | No | `"auto"` (default — run audit; fall back to piolium only if `claude`/`codex` CLI is missing), `"both"` (run audit then piolium unconditionally), `"audit"`, or `"piolium"` | | `mode` | string | No | Harness mode — see `--list-modes` on `vigolium agent audit` / `vigolium agent audit`. Under `auto`/`both`, restricted to shared modes (`lite`, `balanced`, `deep`, `revisit`, `confirm`, `merge`). | | `modes` | string\[] | No | Chain modes back-to-back (e.g. `["deep","confirm"]`). Audit runs the chain natively in one subprocess; piolium chains via sequential runs collapsed into one aggregated child row. | | `intensity` | string | No | `"quick"` → `lite`, `"balanced"` → `balanced`, `"deep"` → chain `[deep, confirm]` | | `agent` | string | No | Audit-leg agent selector: `"claude"` or `"codex"`. Layered on top of `provider`. | | `provider` | string | No | Olium provider name (e.g. `anthropic-api-key`). Resolves the audit-leg agent and forwards BYOK auth (`anthropic-*` → claude, `openai-*` → codex). | | `commit_depth` | int | No | Shallow-clone depth (0 = full history) | | `interactive` | bool | No | Drop into the audit harness interactively (audit-only). Skips NDJSON streaming and findings auto-import. | | `no_stream` | bool | No | Disable streaming progress to the client | | `no_dedup` | bool | No | Skip the post-pass project-wide findings dedup | | `no_preflight` | bool | No | Skip per-driver preflight checks | | `upload_results` | bool | No | Upload session bundle on completion (requires `storage` config) | | `stream` | bool | No | If `true`, returns an SSE stream with `driver`/`chunk`/`phase`/`done`/`error` events | | `timeout` | string | No | Go duration string | | `project_uuid` | string | No | Scope results to a project | | `scan_uuid` | string | No | Link results to a specific scan UUID | **Per-driver BYOK overrides** apply different credentials to each leg under `auto`/`both`: | Field | Type | Description | | -------------- | ------ | -------------------------------------------------------------------------------------------- | | `audit_auth` | object | `{api_key, oauth_token, oauth_cred_file, oauth_cred_json}` applied to the vigolium-audit leg | | `piolium_auth` | object | Same shape, applied to the piolium leg | **Piolium-specific flags:** | Field | Type | Description | | ------------- | ------ | ---------------------------------------------- | | `pi_provider` | string | Override Pi's provider for the piolium leg | | `pi_model` | string | Override Pi's model for the piolium leg | | `plm_*` | varied | Pass-through flags to piolium's session config | **Examples:** ```bash theme={null} # Default: auto — vigolium-audit, fall back to piolium if the CLI is missing curl -s -X POST http://localhost:9002/api/agent/run/audit \ -H "Content-Type: application/json" \ -d '{"source": "/home/user/src/my-app"}' | jq . # Both drivers, deep intensity (expands to mode chain [deep, confirm]) curl -s -X POST http://localhost:9002/api/agent/run/audit \ -H "Content-Type: application/json" \ -d '{ "source": "/home/user/src/my-app", "driver": "both", "intensity": "deep" }' | jq . # Per-driver BYOK curl -s -X POST http://localhost:9002/api/agent/run/audit \ -H "Content-Type: application/json" \ -d '{ "source": "/home/user/src/my-app", "driver": "both", "audit_auth": {"api_key": "sk-ant-..."}, "piolium_auth": {"api_key": "sk-..."} }' | jq . # Streamed multi-driver run (SSE) curl -N -X POST http://localhost:9002/api/agent/run/audit \ -H "Content-Type: application/json" \ -d '{ "source": "/home/user/src/my-app", "driver": "both", "stream": true }' ``` **SSE events for audit:** Each `chunk` event includes a `driver` field (`"audit"` or `"piolium"`). The dispatcher brackets each driver's stream with `driver_start` and `driver_end` events so a UI can group log lines by driver. The `done` event arrives once after both drivers (and the dedup pass) finish. *** ## Natural Language Prompts The autopilot and swarm endpoints accept a `prompt` field for natural language scan requests. When `prompt` is provided and no explicit input fields are set (`target`, `input`, `source`), the prompt is parsed by an AI intent extractor that returns structured parameters. The intent extractor recognizes: target URLs, source code paths, vulnerability focus areas, custom instructions, discovery mode, code audit mode, and vigolium-audit level. | Intent Field | Maps To (Autopilot) | Maps To (Swarm) | Description | | ------------- | ------------------- | --------------- | ----------------------------------------------------------------- | | `target` | `target` | `input` | Target URL | | `source_path` | `source` | `source` | Filesystem path to source code | | `focus` | `focus` | `focus` | Vulnerability focus area | | `instruction` | `instruction` | `instruction` | Remaining guidance | | `discover` | , | `discover` | Inferred when both target and source are present | | `code_audit` | , | `code_audit` | Inferred when source-only (no target) | | `audit` | `audit_mode` | `audit` | `"lite"`, `"balanced"`, or `"deep"` when audit agent is mentioned | ```bash theme={null} # Swarm with a natural language prompt curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -d '{ "prompt": "scan source at ~/src/VAmPI on localhost:3005 with audit agent" }' | jq . # Dry run — preview extracted intent without launching a scan curl -s -X POST http://localhost:9002/api/agent/run/autopilot \ -H "Content-Type: application/json" \ -d '{ "prompt": "scan VAmPI source at ~/src/VAmPI on localhost:3005 with full audit agent", "dry_run": true }' | jq . ``` > Explicit fields always take precedence. If you pass both `prompt` and `target`/`input`/`source`, the prompt is ignored. *** ## SSE Streaming All run endpoints support `"stream": true`, which returns a `text/event-stream` response. Each event is a JSON object on a `data:` line. | Type | Description | Modes | | -------------- | -------------------------------------------------------------------- | -------------------- | | `chunk` | Incremental text output from the agent (audit adds a `driver` field) | All | | `phase` | Phase transition (includes `phase` field) | Swarm, audit | | `driver_start` | Multi-driver dispatcher began streaming a driver | Audit (multi-driver) | | `driver_end` | Multi-driver dispatcher finished streaming a driver | Audit (multi-driver) | | `done` | Final event with the complete result object | All | | `error` | Agent run failed; includes `error` message | All | **Example SSE stream (swarm):** ``` data: {"type":"phase","phase":"native-normalize"} data: {"type":"phase","phase":"plan"} data: {"type":"chunk","text":"Analyzing targets for attack strategy..."} data: {"type":"phase","phase":"native-extension"} data: {"type":"phase","phase":"native-scan"} data: {"type":"phase","phase":"triage"} data: {"type":"done","swarm_result":{"total_findings":5,"confirmed":3,"false_positives":2,"iterations":2,"severity_counts":{"high":2,"medium":3}}} ``` *** ## GET /api/agent/status/list, List Agent Runs Returns all agent runs with their current status. ```bash theme={null} curl -s http://localhost:9002/api/agent/status/list | jq . ``` ```json theme={null} [ { "agentic_scan_uuid": "agt-550e8400-e29b-41d4-a716-446655440000", "mode": "query", "status": "completed", "agent_name": "olium", "template_id": "security-code-review", "finding_count": 3, "saved_count": 3, "completed_at": "2026-04-01T15:10:00Z" }, { "agentic_scan_uuid": "agt-661f9511-f3ac-52e5-b827-557766551111", "mode": "swarm", "status": "running", "current_phase": "native-scan", "phases_run": ["native-normalize", "plan", "native-extension"] } ] ``` *** ## GET /api/agent/status/:id, Agent Run Status | Field | Type | Description | | ------------------- | --------- | --------------------------------------------------------------- | | `agentic_scan_uuid` | string | Unique run identifier | | `mode` | string | `"query"`, `"autopilot"`, `"swarm"`, `"audit"`, or `"pipeline"` | | `status` | string | `"running"`, `"completed"`, `"failed"`, or `"cancelled"` | | `agent_name` | string | Agent label recorded for the run | | `template_id` | string | Prompt template ID (query mode) | | `finding_count` | int | Number of findings produced | | `record_count` | int | Number of HTTP records produced (query/autopilot) | | `saved_count` | int | Number of records saved to DB (query/autopilot) | | `error` | string | Error message (failed runs only) | | `completed_at` | string | ISO 8601 completion timestamp | | `result` | object | Full agent result (query/autopilot, completed runs) | | `current_phase` | string | Currently executing phase (swarm, running) | | `phases_run` | string\[] | Completed phases (swarm) | | `swarm_result` | object | Full swarm result (swarm, completed runs) | *** ## POST /api/agent/scans/:uuid/cancel, Cancel an Agent Run Aborts an in-flight agent run (autopilot, swarm, query, or audit) by cancelling its context. The run unwinds and its finalization records the terminal `cancelled` status. This is the endpoint the workbench UI's "Cancel run" button calls. ```bash theme={null} curl -s -X POST \ http://localhost:9002/api/agent/scans/agt-661f9511-f3ac-52e5-b827-557766551111/cancel \ -H "Authorization: Bearer my-secret-key" | jq . ``` ```json theme={null} { "agentic_scan_uuid": "agt-661f9511-f3ac-52e5-b827-557766551111", "status": "cancelling", "message": "cancellation requested" } ``` Returns `404` with `{"error": "run not found or already finished"}` when no run with that UUID is currently running in this process (already finished, never started, or unknown). Cancellation only targets runs executing in the server process that receives the request. *** ## GET /api/agent/sessions, List Agent Sessions Paginated list of agent sessions from the database. Unlike `/api/agent/status/list` (which includes in-memory running state), this returns persisted historical sessions with structured metadata, but without large debug fields (`prompt_sent`, `agent_raw_output`, etc.) to keep responses lightweight. | Parameter | Type | Default | Description | | --------- | ------ | ------- | ------------------------------------------------------ | | `mode` | string | , | Filter by mode: `query`, `autopilot`, `swarm`, `audit` | | `limit` | int | `50` | Page size (max `500`) | | `offset` | int | `0` | Offset for pagination | ```bash theme={null} curl -s 'http://localhost:9002/api/agent/sessions?mode=swarm&limit=10' | jq . ``` *** ## GET /api/agent/sessions/:id, Agent Session Detail Full detail of a single agent session, including large debug fields: `prompt_sent`, `agent_raw_output`, `attack_plan`, `triage_result`, `result_json`. | Field | Type | Description | | ------------------ | --------- | ----------------------------------------------------- | | `input_raw` | string | Raw input provided to the agent run | | `module_names` | string\[] | Scanner modules used or selected | | `source_path` | string | Source code path used for the run | | `source_type` | string | How source was provided: `local`, `git-url`, or `gcs` | | `session_id` | string | Session ID (for autopilot resume) | | `prompt_sent` | string | Full prompt text sent to the agent | | `agent_raw_output` | string | Complete raw output from the agent | | `attack_plan` | string | JSON attack plan (swarm mode) | | `triage_result` | string | JSON triage result (swarm mode) | | `result_json` | string | Full result object as JSON | *** ## GET /api/agent/sessions/:id/logs, Session Console Logs Returns the raw `runtime.log` file for a session, the same live console stream the CLI user sees. ANSI colors are preserved by default so browser terminal emulators (xterm.js, etc.) render it exactly like the CLI. Works while the run is in progress *and* after it finishes. Two modes, selected via the `Accept` header: * **Plain text** (default): `text/plain; charset=utf-8` dump of the entire `runtime.log` at request time. * **Server-Sent Events** (`Accept: text/event-stream`): tails the file and emits each new byte range as a `chunk` event. Exits with a `done` event when the run reaches a terminal status, the client disconnects, or a 2-hour safety backstop fires. | Query param | Type | Description | | ----------- | ---- | -------------------------------------------------------------------------------------------------------- | | `strip` | bool | If truthy (`1`, `true`, `yes`), strip ANSI escape sequences server-side. Default: false (preserve ANSI). | ```bash theme={null} # Plain text dump curl -s http://localhost:9002/api/agent/sessions/agt-550e8400-.../logs # Live tail via SSE curl -N -H 'Accept: text/event-stream' \ http://localhost:9002/api/agent/sessions/agt-550e8400-.../logs ``` *** ## GET /api/agent/sessions/:id/artifacts, List Session Artifacts Returns a recursive list of files inside the session directory, capped at 500 entries. Useful for browsing generated extensions, vigolium-audit output, plan/checkpoint files. ```bash theme={null} curl -s http://localhost:9002/api/agent/sessions/agt-550e8400-.../artifacts | jq . ``` ```json theme={null} { "agentic_scan_uuid": "agt-550e8400-e29b-41d4-a716-446655440000", "session_dir": "/home/user/.vigolium/agent-sessions/agt-550e8400-...", "artifacts": [ {"name": "swarm-plan.json", "size": 2048, "modified_at": "2026-05-21T10:01:00Z", "kind": "file"}, {"name": "extensions/sqli-custom.js", "size": 1024, "modified_at": "2026-05-21T10:02:00Z", "kind": "file"}, {"name": "vigolium-audit/findings/p8-001.md", "size": 4096, "modified_at": "2026-05-21T10:03:00Z", "kind": "file"} ], "truncated": false } ``` ## GET /api/agent/sessions/:id/artifacts/, Read Artifact Read a specific artifact file. Wildcard supports nesting (e.g. `vigolium-audit/state.json`). | Query param | Type | Description | | ----------- | ---- | ----------------------------------------------------------------- | | `max_bytes` | int | Override per-request size cap (default 10 MiB, hard cap 100 MiB). | ```bash theme={null} curl -s 'http://localhost:9002/api/agent/sessions/agt-550e8400-.../artifacts/swarm-plan.json' | jq . ``` *** ## POST /api/agent/chat/completions, OpenAI-Compatible Chat Completions Accepts an OpenAI-compatible Chat Completions request and returns an OpenAI-compatible response. This allows any OpenAI-compatible client to use the in-process olium engine by changing the base URL. The `model` field is currently informational, every request is dispatched through the olium engine using the provider configured under `agent.olium.*` in `vigolium-configs.yaml`. This endpoint is **synchronous**, it blocks until the agent completes. It shares the concurrency lock with the run endpoints (returns `409 Conflict` if an agent is already running). | Field | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------- | | `model` | string | Yes | Required by the OpenAI schema; value is informational | | `messages` | array | Yes | Array of `{role, content}` message objects | ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "model": "vigolium-olium", "messages": [ { "role": "user", "content": "What are common JWT vulnerabilities?" } ] }' | jq . ``` ```python theme={null} from openai import OpenAI client = OpenAI( base_url="http://localhost:9002/api/agent", api_key="", ) response = client.chat.completions.create( model="vigolium-olium", messages=[{"role": "user", "content": "Explain CSRF attacks"}], ) ``` See [Agent Mode](/agentic-scan/agent-mode) for full agent documentation. # Authentication Source: https://docs.vigolium.com/api-references/authentication File-based user system with Bearer token authentication, roles, login, and token usage for the Vigolium API. Vigolium uses a file-based user system with Bearer token authentication. Each user has a unique access code that serves as their API token. ## Users and Roles Users are defined in a JSON file (default: `~/.vigolium/users.json`). On first run, the file is auto-created from the embedded template with randomly generated access codes (prefixed `vgl_`). Three roles control API access: | Role | Description | | ---------- | ----------------------------------------------------------------- | | `admin` | Full access, can delete resources, update config, manage projects | | `operator` | Can run scans, ingest traffic, and execute agent operations | | `viewer` | Read-only access to records, findings, stats, and scan history | Default users created on bootstrap: | Name | Role | | ------------------- | -------- | | `vigolium-admin` | admin | | `vigolium-operator` | operator | | `vigolium-analyst` | viewer | | `vigolium-auditor` | viewer | ## Login Exchange a username and access code for user info and a Bearer token. ### `POST /api/auth/login` This endpoint is publicly accessible (no authentication required). **Request body:** ```json theme={null} { "username": "vigolium-admin", "access_code": "vgl_abc123..." } ``` **Success response (200):** ```json theme={null} { "token": "vgl_abc123...", "user": { "uuid": "d4f5e6a7-...", "name": "vigolium-admin", "email": "", "role": "admin" } } ``` **Error responses:** | Status | Condition | Body | | ------ | --------------------------------- | ----------------------------------------------------------------- | | 400 | Missing or malformed request body | `{"error": "username and access_code are required", "code": 400}` | | 401 | Invalid username or access code | `{"error": "invalid username or access code", "code": 401}` | ## Current User Info Retrieve the authenticated user's identity and role. ### `GET /api/user/info` Requires a valid Bearer token. **Success response (200):** ```json theme={null} { "uuid": "d4f5e6a7-...", "name": "vigolium-admin", "email": "", "role": "admin" } ``` **Error responses:** | Status | Condition | Body | | ------ | ------------------------ | ------------------------------------------------ | | 401 | Missing or invalid token | `{"error": "invalid Bearer token", "code": 401}` | ## Using the Token Include the token as a Bearer token in the `Authorization` header for all subsequent API requests: ```bash theme={null} curl -H "Authorization: Bearer vgl_abc123..." http://localhost:9002/api/info ``` ## Project Scoping Projects are a **data** boundary, not an authentication boundary. The API key authenticates the caller; the `X-Project-UUID` header selects which project's data the request touches (the default project when absent). Any authenticated caller can address any project, so run separate servers or separate databases when engagements must not share an operator. ```bash theme={null} curl -H "Authorization: Bearer vgl_abc123..." \ -H "X-Project-UUID: proj-uuid" \ http://localhost:9002/api/findings ``` Setting `VIGOLIUM_PROJECT_READONLY=true` disables the mutating `vigolium project` CLI subcommands (`create`, `delete`) for the calling host. ## Disabling Authentication Set `no_auth: true` in `vigolium-configs.yaml` or pass the `--no-auth` flag to the server command to disable authentication entirely. This is not recommended for production use. # Vigolium API Reference, Config Source: https://docs.vigolium.com/api-references/config View and update server configuration using dot-notation keys with hot reload support. ## GET /api/config, View Configuration Returns the full server configuration as flattened dot-notation key-value pairs. Sensitive values (API keys, passwords, tokens) are redacted by default. **Query parameters:** | Parameter | Type | Default | Description | | ---------------- | ------ | ------- | ------------------------------------------------- | | `filter` | string | | Substring match on key name | | `show_sensitive` | string | `false` | Set to `true` to show unredacted sensitive values | ```bash theme={null} # View all config curl -s http://localhost:9002/api/config | jq . # Filter by section curl -s 'http://localhost:9002/api/config?filter=scope' | jq . # Show sensitive values curl -s 'http://localhost:9002/api/config?show_sensitive=true' | jq . ``` ```json theme={null} { "entries": [ { "key": "database.driver", "value": "sqlite" }, { "key": "database.enabled", "value": "true" }, { "key": "notify.enabled", "value": "false" }, { "key": "scope.applied_on_ingest", "value": "false" }, { "key": "server.auth_api_key", "value": "********", "sensitive": true } ], "total": 5 } ``` *** ## POST /api/config, Update Configuration Updates one or more configuration values using dot-notation keys. Values are coerced to match the existing field type (bool, int, float, string, or comma-separated list). Changes are persisted to the config file on disk. Reloadable sections (scope, notify, dynamic-assessment, mutation\_strategy) take effect immediately. Server and database changes require a restart. **Request body:** JSON object mapping dot-notation keys to string values. ```bash theme={null} # Update a single value curl -s -X POST http://localhost:9002/api/config \ -H "Content-Type: application/json" \ -d '{ "notify.enabled": "true" }' | jq . # Update multiple values at once curl -s -X POST http://localhost:9002/api/config \ -H "Content-Type: application/json" \ -d '{ "notify.enabled": "true", "scope.applied_on_ingest": "true", "dynamic-assessment.extensions.enabled": "false" }' | jq . # Update a list value (comma-separated) curl -s -X POST http://localhost:9002/api/config \ -H "Content-Type: application/json" \ -d '{ "dynamic-assessment.enabled_modules.active_modules": "xss-scanner,sqli-error-based,lfi-path-traversal" }' | jq . ``` **Response (success):** ```json theme={null} { "message": "Config updated successfully", "updated": [ { "key": "notify.enabled", "value": "true" }, { "key": "scope.applied_on_ingest", "value": "true" } ] } ``` **Response (partial success):** If some keys are valid and others are not, valid keys are still applied. The response includes both the updated entries and any errors. ```json theme={null} { "message": "Config partially updated", "updated": [ { "key": "notify.enabled", "value": "true" } ], "errors": [ "invalid.key: key \"invalid\" not found (unknown segment \"invalid\")" ] } ``` *** ## Config Hot Reload The server watches the config file (`~/.vigolium/vigolium-configs.yaml`) for changes. When the file is modified, whether by a text editor, the CLI (`vigolium config set`), or any other tool, reloadable sections are automatically applied without restarting the server. **Reloadable sections:** `scope`, `notify`, `dynamic-assessment`, `mutation_strategy` **Non-reloadable sections:** `server`, `database` (a warning is logged; restart required) Changes made via the API (`POST /api/config`, `POST /api/scope`) do not trigger a redundant reload. # Vigolium API Reference, Generic Database API Source: https://docs.vigolium.com/api-references/database Unified CRUD API for any database table with pagination, filtering, sorting, and full-text search. Unified CRUD API for interacting with any database table. Returns raw JSON and supports pagination, filtering, sorting, column selection, and full-text search across all tables. Read endpoints (GET) require **viewer** role or above. Write endpoints (POST, PUT, DELETE) require **admin** role. Project-scoped tables (`scans`, `http_records`, `findings`, `source_repos`, `session_hostnames`, `oast_interactions`, `scan_logs`, `agentic_scans`, `scopes`) automatically filter by the `X-Project-UUID` header. Pass `?all_projects=true` to disable project scoping (admin use). *** ## GET /api/db/tables, List All Tables Returns all database tables with their row counts. ```bash theme={null} curl -s http://localhost:9002/api/db/tables | jq . ``` ```json theme={null} { "tables": [ { "name": "agentic_scans", "row_count": 42 }, { "name": "findings", "row_count": 1567 }, { "name": "http_records", "row_count": 24301 }, { "name": "scans", "row_count": 15 }, { "name": "source_repos", "row_count": 3 } ], "total": 12 } ``` *** ## GET /api/db/tables/:table/columns, List Table Columns Returns column metadata and primary key information for a specific table. ```bash theme={null} # Get columns for the findings table curl -s http://localhost:9002/api/db/tables/findings/columns | jq . ``` ```json theme={null} { "table": "findings", "columns": [ { "name": "id", "type": "INTEGER", "nullable": "no" }, { "name": "project_uuid", "type": "TEXT", "nullable": "no" }, { "name": "module_id", "type": "TEXT", "nullable": "yes" }, { "name": "severity", "type": "TEXT", "nullable": "yes" }, { "name": "created_at", "type": "DATETIME", "nullable": "no" } ], "primary_key": ["id"], "total": 22 } ``` **Error responses:** | Code | Condition | | ---- | -------------------------------- | | 404 | Table name not found in database | *** ## GET /api/db/tables/:table/records, List Records Returns paginated, filtered, sorted records from any table. **Query parameters:** | Parameter | Type | Default | Description | | ----------------------- | ------ | ------- | ------------------------------------------------------------ | | `limit` | int | 100 | Number of records to return (max 1000) | | `offset` | int | 0 | Offset for pagination | | `sort` | string | | Column name to sort by (validated against table schema) | | `order` | string | `desc` | Sort order: `asc` or `desc` | | `columns` | string | | Comma-separated column whitelist (only return these columns) | | `search` | string | | Fuzzy search across all text/varchar columns | | `truncate` | int | 0 | Truncate large text/binary fields to N characters (0 = full) | | `all_projects` | string | `false` | Set to `true` to disable automatic project\_uuid filtering | | `filter.` | string | | Exact match filter on a column | | `filter.__like` | string | | SQL LIKE pattern match (`%` wildcards) | | `filter.__gt` | string | | Greater than comparison | | `filter.__gte` | string | | Greater than or equal comparison | | `filter.__lt` | string | | Less than comparison | | `filter.__lte` | string | | Less than or equal comparison | | `filter.__in` | string | | Comma-separated IN list | | `filter.__neq` | string | | Not equal comparison | ```bash theme={null} # List HTTP records with default pagination curl -s http://localhost:9002/api/db/tables/http_records/records | jq . # Filter findings by severity curl -s 'http://localhost:9002/api/db/tables/findings/records?filter.severity__in=critical,high' | jq . # Search across text fields curl -s 'http://localhost:9002/api/db/tables/http_records/records?search=example.com' | jq . # Select specific columns only curl -s 'http://localhost:9002/api/db/tables/http_records/records?columns=uuid,hostname,method,path,status_code' | jq . # Sort by response time, ascending curl -s 'http://localhost:9002/api/db/tables/http_records/records?sort=response_time_ms&order=asc' | jq . # Combine filters: GET requests with status > 399 curl -s 'http://localhost:9002/api/db/tables/http_records/records?filter.method=GET&filter.status_code__gt=399' | jq . # LIKE filter with wildcards curl -s 'http://localhost:9002/api/db/tables/http_records/records?filter.hostname__like=%25example%25' | jq . # Paginate through results curl -s 'http://localhost:9002/api/db/tables/http_records/records?limit=50&offset=100' | jq . # Truncate large fields for listing views curl -s 'http://localhost:9002/api/db/tables/http_records/records?truncate=200' | jq . # List all agent runs across all projects curl -s 'http://localhost:9002/api/db/tables/agentic_scans/records?all_projects=true' | jq . # List scan logs for a specific scan curl -s 'http://localhost:9002/api/db/tables/scan_logs/records?filter.scan_uuid=abc-123&sort=created_at&order=asc' | jq . ``` ```json theme={null} { "table": "http_records", "total": 4521, "limit": 100, "offset": 0, "columns": ["uuid", "hostname", "method", "path", "status_code", "..."], "records": [ { "uuid": "rec-0001-aaaa-bbbb-cccc", "hostname": "example.com", "method": "GET", "path": "/api/users", "status_code": 200 } ] } ``` **Error responses:** | Code | Condition | | ---- | --------------------------------- | | 400 | Invalid query parameter or filter | | 404 | Table not found | *** ## GET /api/db/tables/:table/records/:id, Get Single Record Returns a single record by its primary key value. Only works for tables with a single-column primary key. ```bash theme={null} # Get a specific HTTP record by UUID curl -s http://localhost:9002/api/db/tables/http_records/records/rec-0001-aaaa-bbbb-cccc | jq . # Get a specific finding by ID curl -s http://localhost:9002/api/db/tables/findings/records/42 | jq . ``` ```json theme={null} { "table": "findings", "record": { "id": 42, "project_uuid": "00000000-0000-0000-0000-000000000001", "module_id": "xss-reflected", "module_name": "Reflected XSS", "severity": "high", "confidence": "confirmed", "matched_at": "https://example.com/search?q=test", "created_at": "2026-03-20T14:30:00Z" } } ``` **Error responses:** | Code | Condition | | ---- | ----------------------------------------------- | | 400 | Table not found or composite PK (not supported) | | 404 | Record not found | *** ## POST /api/db/tables/:table/records, Create Record Inserts a new record into the specified table. Requires **admin** role. The request body is a JSON object where keys are column names and values are the data to insert. Column names are validated against the table schema. For project-scoped tables, `project_uuid` is automatically injected from the `X-Project-UUID` header if not provided in the body. ```bash theme={null} # Create a scope rule curl -s -X POST http://localhost:9002/api/db/tables/scopes/records \ -H 'Content-Type: application/json' \ -d '{ "name": "include-example", "rule_type": "include", "host_pattern": "*.example.com", "enabled": true }' | jq . ``` ```json theme={null} { "table": "scopes", "message": "record created" } ``` **Error responses:** | Code | Condition | | ---- | -------------------------------------------------- | | 400 | Invalid JSON, invalid column name, or empty fields | | 500 | Database constraint violation | *** ## PUT /api/db/tables/:table/records/:id, Update Record Updates one or more fields on an existing record. Requires **admin** role. Only the fields included in the request body are updated (partial update). Primary key columns cannot be updated. Column names are validated against the table schema. ```bash theme={null} # Update a finding's severity curl -s -X PUT http://localhost:9002/api/db/tables/findings/records/42 \ -H 'Content-Type: application/json' \ -d '{"severity": "critical"}' | jq . # Update an HTTP record's risk score and remarks curl -s -X PUT http://localhost:9002/api/db/tables/http_records/records/rec-0001-aaaa-bbbb-cccc \ -H 'Content-Type: application/json' \ -d '{"risk_score": 85, "remarks": "manually verified"}' | jq . ``` ```json theme={null} { "table": "findings", "id": "42", "message": "record updated" } ``` **Error responses:** | Code | Condition | | ---- | ----------------------------------------------------- | | 400 | Invalid JSON, invalid column, or attempt to update PK | | 404 | Record not found | *** ## DELETE /api/db/tables/:table/records/:id, Delete Record Deletes a single record by primary key. Requires **admin** role. ```bash theme={null} # Delete a finding curl -s -X DELETE http://localhost:9002/api/db/tables/findings/records/42 | jq . # Delete a scan log entry curl -s -X DELETE http://localhost:9002/api/db/tables/scan_logs/records/100 | jq . ``` ```json theme={null} { "table": "findings", "id": "42", "message": "record deleted" } ``` **Error responses:** | Code | Condition | | ---- | ---------------- | | 404 | Record not found | | 500 | Database error | # Vigolium API Reference, Diagnostics Source: https://docs.vigolium.com/api-references/diagnostics System readiness check covering database connectivity, agent provider, third-party tools, and directory configuration. ## GET /api/diagnostics, System Readiness Check Returns a diagnostic report checking database connectivity, agent provider readiness, third-party tools, and directory configuration. Useful for verifying the scanner is ready to operate before starting scans. **Auth:** Viewer (requires Bearer token) ```bash theme={null} curl -s -H "Authorization: Bearer $TOKEN" http://localhost:9002/api/diagnostics | jq . ``` ```json theme={null} { "status": "degraded", "timestamp": "2026-04-29T03:40:08+08:00", "database": { "status": "ok", "message": "driver=sqlite" }, "queue": { "status": "ok", "message": "depth=0" }, "agent": { "status": "ok", "name": "olium", "provider": "openai-codex-oauth", "model": "gpt-5.5" }, "browser": { "status": "warning", "message": "disabled in config" }, "tools": { "chromium": { "status": "ok", "path": "/opt/homebrew/bin/chromium" } }, "templates_dir": { "status": "ok", "message": "path=~/.vigolium/prompts, templates=38" }, "sessions_dir": { "status": "ok", "message": "path=~/.vigolium/agent-sessions, writable=true" } } ``` ### Top-Level Status | Value | Meaning | | ----------- | ------------------------------------------------------------------------------- | | `ready` | All checks passed | | `degraded` | Some non-critical checks failed (e.g., optional tool missing, browser disabled) | | `not_ready` | Critical checks failed (database or agent unavailable) | ### Check Statuses Each individual check returns one of: `ok`, `warning`, `error`. ### Checks Performed | Check | Critical | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `database` | Yes | Pings the database with a 2s timeout | | `agent` | Yes | Resolves the configured olium provider and confirms credentials are available | | `queue` | No | Reports queue depth and error counts | | `browser` | No | Checks `agent-browser` binary if enabled in config | | `tools.chromium` | No | Resolves the chromium/chrome binary (fallbacks: `chromium-browser`, `google-chrome`, `google-chrome-stable`). On the one-shot CLI paths it also smoke-launches the browser — see the note below | | `tools.piolium` | No | Reports whether the optional piolium harness is installed under `~/.piolium` | | `templates_dir` | No | Verifies prompt templates directory exists and contains `.md` files | | `sessions_dir` | No | Verifies agent sessions directory exists and is writable | **Browser smoke-test on the CLI paths.** On the one-shot CLI paths — `vigolium doctor` and the first-run dependency check — the `tools.chromium` check goes beyond reading the `--version` banner: it *actually launches* the resolved Chromium headless, performing the same DevTools remote-debugging handshake the spider relies on, in an isolated self-cleaning user-data dir, bounded to \~30s. A binary that prints a version but SIGTRAPs / crashes on real launch (seen with some distro Chromium builds) or is missing a runtime shared library is downgraded from `ok` to `warning`, carrying a remedy: run `vigolium doctor --fix --only chrome` to download a working Chrome for Testing, or point `spidering.browser_path` at a working browser. The server `GET /api/diagnostics` endpoint leaves this heavy probe **off** to stay a cheap, poll-safe readout. ### CLI Equivalent The same checks are available via the CLI without a running server: ```bash theme={null} # Colored console output vigolium doctor # JSON output vigolium doctor --json ``` The CLI version omits the `queue` check since the queue is only available when the server is running. # Vigolium API Reference, Extensions Source: https://docs.vigolium.com/api-references/extensions Manage JavaScript and YAML extensions that add custom scanning logic, including listing, editing, and API docs. Manage JavaScript (`.js`) and YAML (`.vgm.yaml`) extensions that add custom scanning logic. Extensions must be enabled and configured in `vigolium-configs.yaml` under `audit.extensions`. ## GET /api/extensions, List Extensions Returns metadata for all loaded extensions. Raw file content is excluded, use `GET /api/extensions/:name` to fetch the content of a specific extension. **Query parameters:** | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------ | | `type` | string | Filter by type: `active`, `passive`, `pre_hook`, `post_hook` | | `search` | string | Filter by ID, name, description, or tag | ```bash theme={null} # List all extensions curl -s http://localhost:9002/api/extensions | jq . # Filter by type curl -s 'http://localhost:9002/api/extensions?type=active' | jq . # Search by keyword (also matches tags) curl -s 'http://localhost:9002/api/extensions?search=xss' | jq . ``` **Response (200):** ```json theme={null} { "extensions": [ { "id": "reflected-param-scanner", "name": "Reflected Parameter Scanner", "language": "js", "type": "active", "severity": "medium", "confidence": "firm", "scan_types": ["per_insertion_point"], "tags": ["injection", "xss", "moderate"], "description": "Injects a canary value into each parameter and checks if the response reflects it back", "file": "/home/user/.vigolium/extensions/reflected_param_scanner.js", "file_name": "reflected_param_scanner.js" }, { "id": "sensitive-header-leak-yaml", "name": "Sensitive Header Leak (YAML)", "language": "yaml", "type": "passive", "severity": "info", "confidence": "certain", "scan_types": ["per_request"], "tags": ["info-disclosure", "header-security", "light"], "scope": "response", "description": "Detects responses that expose server technology details through HTTP headers", "file": "/home/user/.vigolium/extensions/sensitive_header_leak.vgm.yaml", "file_name": "sensitive_header_leak.vgm.yaml" } ], "total": 2, "extensions_enabled": true } ``` When extensions are not configured, returns `extensions_enabled: false` and an empty list. ### Declaring Tags in Extensions **JavaScript extensions**: add a `tags` array to `module.exports`: ```js theme={null} module.exports = { id: "my-scanner", type: "active", tags: ["injection", "xss", "moderate"], // ... }; ``` **YAML extensions**: add a `tags` list: ```yaml theme={null} id: my-scanner type: active tags: - injection - xss - moderate ``` *** ## GET /api/extensions/:name, Get Extension Returns full metadata plus raw file content for a single extension, looked up by filename. **Path parameters:** | Parameter | Description | | --------- | ------------------------------------------------------------------------------------------------- | | `name` | Filename of the extension (e.g. `reflected_param_scanner.js` or `sensitive_header_leak.vgm.yaml`) | ```bash theme={null} curl -s http://localhost:9002/api/extensions/reflected_param_scanner.js | jq . curl -s http://localhost:9002/api/extensions/sensitive_header_leak.vgm.yaml | jq . ``` **Response (200):** ```json theme={null} { "id": "reflected-param-scanner", "name": "Reflected Parameter Scanner", "language": "js", "type": "active", "severity": "medium", "confidence": "firm", "scan_types": ["per_insertion_point"], "tags": ["injection", "xss", "moderate"], "description": "Injects a canary value into each parameter and checks if the response reflects it back", "file": "/home/user/.vigolium/extensions/reflected_param_scanner.js", "file_name": "reflected_param_scanner.js", "raw_content": "// reflected_param_scanner.js\nmodule.exports = { id: \"reflected-param\", ... }" } ``` **Error responses:** | Code | Reason | | ---- | ------------------------------------------- | | 404 | Extension not found among loaded extensions | *** ## PUT /api/extensions/:name, Edit Extension Overwrites the content of an extension file identified by its filename (e.g. `reflected_param_scanner.js` or `my_check.vgm.yaml`). The file must already exist as a loaded extension. **Path parameters:** | Parameter | Description | | --------- | ------------------------------------------------------------ | | `name` | Filename of the extension (must end in `.js` or `.vgm.yaml`) | **Request body:** | Field | Type | Required | Description | | --------- | ------ | -------- | ----------------------------- | | `content` | string | Yes | New full content for the file | ```bash theme={null} # Edit a JS extension curl -s -X PUT http://localhost:9002/api/extensions/reflected_param_scanner.js \ -H "Content-Type: application/json" \ -d '{ "content": "module.exports = { id: \"reflected-param-scanner\", type: \"active\", scan: function(ctx) { /* ... */ } };" }' | jq . # Edit a YAML extension curl -s -X PUT http://localhost:9002/api/extensions/ai_xss_scanner.vgm.yaml \ -H "Content-Type: application/json" \ -d '{"content": "id: ai-xss-scanner\ntype: active\n..."}' | jq . ``` **Response (200):** ```json theme={null} { "message": "extension updated", "file": "/home/user/.vigolium/extensions/reflected_param_scanner.js", "file_name": "reflected_param_scanner.js" } ``` **Error responses:** | Code | Reason | | ---- | ------------------------------------------------------ | | 400 | Name does not end in `.js` or `.vgm.yaml`, or bad JSON | | 404 | Extension not found among loaded extensions | | 500 | File write failed | *** ## GET /api/extensions/docs, List JS API Functions Returns the full JS extension API catalog, all built-in `vigolium.*` functions available to extension scripts. **Query parameters:** | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------- | | `search` | string | Filter by function name, namespace, or description | ```bash theme={null} # List all API functions curl -s http://localhost:9002/api/extensions/docs | jq . # Search for HTTP-related functions curl -s 'http://localhost:9002/api/extensions/docs?search=http' | jq . ``` **Response (200):** ```json theme={null} { "functions": [ { "category": "Logging", "namespace": "vigolium.log", "name": "info", "full_name": "vigolium.log.info", "signature": ".info(msg: string)", "returns": "void", "description": "Log an informational message.", "example": "vigolium.log.info(\"scanning \" + ctx.request.url)" }, { "category": "HTTP", "namespace": "vigolium.http", "name": "send", "full_name": "vigolium.http.send", "signature": ".send(req: HttpRequest): HttpResponse", "returns": "HttpResponse", "description": "Send an HTTP request and return the response." } ], "total": 42, "namespaces": [ "vigolium.log", "vigolium.utils", "vigolium.http", "vigolium.scan", "vigolium.ingest", "vigolium.source", "vigolium.config" ] } ``` # Vigolium API Reference, Findings Source: https://docs.vigolium.com/api-references/findings List, retrieve, and delete vulnerability findings with filtering, pagination, and severity-based queries. ## GET /api/findings, List Findings Returns paginated vulnerability findings. **Query parameters:** | Parameter | Type | Default | Description | | ---------------- | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------- | | `limit` | int | 50 | Number of findings to return (max 500) | | `offset` | int | 0 | Offset for pagination | | `domain` | string | | Filter by hostname (supports `*` wildcards) | | `severity` | string | | Filter by severity (comma-separated): `critical`, `high`, `medium`, `low`, `info` | | `scan_id` | string | | Filter by scan UUID | | `module_name` | string | | Filter by module name | | `module_type` | string | | Filter by module type: `active` or `passive` | | `finding_source` | string | | Filter by finding source: `dynamic-assessment`, `known-issue-scan`, `agent`, `audit`, `oast`, `extension`, `import` | | `search` | string | | Search across description, module ID, matched\_at | | `sort` | string | `found_at` | Sort field: `found_at`, `created_at`, `severity`, `module_name`, `module_id`, `confidence` | | `order` | string | `desc` | Sort order: `asc` or `desc` | ```bash theme={null} # List all findings curl -s http://localhost:9002/api/findings | jq . # Filter by severity curl -s 'http://localhost:9002/api/findings?severity=critical,high' | jq . # Filter by domain and module curl -s 'http://localhost:9002/api/findings?domain=example.com&module_name=xss' | jq . # Filter by module type curl -s 'http://localhost:9002/api/findings?module_type=passive' | jq . # Filter by finding source curl -s 'http://localhost:9002/api/findings?finding_source=dynamic-assessment' | jq . # Search findings curl -s 'http://localhost:9002/api/findings?search=reflected' | jq . ``` ```json theme={null} { "data": [ { "id": 1, "http_record_uuids": ["abc-123"], "scan_uuid": "scan-456", "module_id": "xss-scanner", "module_name": "XSS Scanner", "module_type": "active", "module_short": "Detects reflected cross-site scripting via parameter injection", "finding_source": "dynamic-assessment", "description": "Reflected XSS via parameter 'q'", "severity": "high", "confidence": "firm", "tags": ["xss", "reflected"], "matched_at": ["https://example.com/search?q=test"], "extracted_results": [""], "additional_evidence": [ "GET /search?q=%3Cimg+src%3Dx+onerror%3Dalert(1)%3E HTTP/1.1\r\nHost: example.com\r\n\r\n\n---------\nHTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n..." ], "request": "GET /search?q=%3Cscript%3Ealert(1)%3C/script%3E HTTP/1.1\r\nHost: example.com\r\n\r\n", "response": "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n...", "finding_hash": "e3b0c44298fc1c14", "found_at": "2026-02-16T15:05:00Z", "created_at": "2026-02-16T15:05:00Z" } ], "total": 42, "limit": 50, "offset": 0, "has_more": false } ``` *** ## GET /api/findings/:id, Get Finding Detail Returns a single finding by its numeric ID. ```bash theme={null} curl -s http://localhost:9002/api/findings/1 | jq . ``` ```json theme={null} { "id": 1, "http_record_uuids": ["abc-123"], "scan_uuid": "scan-456", "module_id": "xss-scanner", "module_name": "XSS Scanner", "module_type": "active", "module_short": "Detects reflected cross-site scripting via parameter injection", "finding_source": "dynamic-assessment", "description": "Reflected XSS via parameter 'q'", "severity": "high", "confidence": "firm", "tags": ["xss", "reflected"], "matched_at": ["https://example.com/search?q=test"], "extracted_results": [""], "additional_evidence": [ "GET /search?q=%3Cimg+src%3Dx+onerror%3Dalert(1)%3E HTTP/1.1\r\nHost: example.com\r\n\r\n\n---------\nHTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n..." ], "request": "GET /search?q=%3Cscript%3Ealert(1)%3C/script%3E HTTP/1.1\r\nHost: example.com\r\n\r\n", "response": "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n...", "finding_hash": "e3b0c44298fc1c14", "found_at": "2026-02-16T15:05:00Z", "created_at": "2026-02-16T15:05:00Z" } ``` **Error responses:** | Code | Condition | | ---- | ------------------------- | | 400 | Invalid ID (not a number) | | 404 | Finding not found | | 503 | Database unavailable | *** ## PATCH /api/findings/:id/status, Update Finding Status Sets the workflow status on a finding (e.g. open / triaged / closed). The status field is opaque to the server — any string is accepted. ```bash theme={null} curl -s -X PATCH http://localhost:9002/api/findings/42/status \ -H "Content-Type: application/json" \ -d '{"status": "triaged"}' | jq . ``` ```json theme={null} { "id": 42, "status": "triaged" } ``` | Status | Description | | ------ | ----------------------- | | 200 | Status updated | | 400 | Invalid ID or body | | 404 | Finding not found | | 503 | Database not configured | *** ## DELETE /api/findings/:id, Delete Finding Deletes a single finding by its numeric ID. Associated `finding_records` junction rows are also removed. ```bash theme={null} curl -s -X DELETE http://localhost:9002/api/findings/42 | jq . ``` **Response:** ```json theme={null} { "message": "finding deleted", "id": 42 } ``` | Status | Description | | ------ | ------------------------- | | 200 | Finding deleted | | 400 | Invalid ID (not a number) | | 404 | Finding not found | | 503 | Database not configured | *** ## Finding Fields ### `additional_evidence` **Type:** `string[]` (optional, omitted when empty) Stores extra HTTP request/response pairs associated with a finding. Each entry is a single string containing a raw request and raw response separated by the delimiter `\n---------\n`. This field is populated in two ways: 1. **Modules and extensions**: A module or JS extension can attach supplementary evidence when creating a finding (e.g., multiple payloads tried, confirmation requests, or baseline comparisons). 2. **Deduplication**: When duplicate findings are merged (same `module_id`, `severity`, and `matched_at` URL), the `request`/`response` pairs from the removed duplicates are automatically collected into the surviving finding's `additional_evidence`. **Example entry format:** ``` GET /api?id=1'+OR+1=1 HTTP/1.1\r\nHost: example.com\r\n\r\n --------- HTTP/1.1 500 Internal Server Error\r\n\r\n{"error":"syntax error"} ``` To parse an entry, split on `\n---------\n`, the first part is the request, the second is the response. # Vigolium API Reference, HTTP Records Source: https://docs.vigolium.com/api-references/http-records List, retrieve, and delete HTTP request/response records with filtering, pagination, and sorting options. ## GET /api/http-records, List HTTP Records Returns paginated HTTP request/response records stored in the database. Response and request bodies are excluded from list responses for performance. **Query parameters:** | Parameter | Type | Default | Description | | -------------- | ------ | ------------ | ------------------------------------------------------------------------------------- | | `limit` | int | 50 | Number of records to return (max 500) | | `offset` | int | 0 | Offset for pagination | | `domain` | string | | Filter by hostname (supports `*` wildcards) | | `method` | string | | Filter by HTTP method (comma-separated) | | `path` | string | | Filter by path (supports `*` wildcards) | | `status_code` | string | | Filter by status code (comma-separated) | | `content_type` | string | | Filter by response content type | | `search` | string | | Search across URL and path | | `source` | string | | Filter by ingestion source (e.g. `ingest-server`, `cli`, `burp`, `caido`) | | `min_risk` | int | | Filter by minimum risk score | | `remark` | string | | Filter by remark | | `sort` | string | `created_at` | Sort field: `created_at`, `sent_at`, `method`, `path`, `status_code`, `response_time` | | `order` | string | `desc` | Sort order: `asc` or `desc` | > **Note:** When the server is started with `--burp-bridge-url` (alias `--caido-bridge-url`, see [Running the Server](/server-mode/running-the-server#live-proxy-bridge)), live Burp or Caido proxy history rows are merged into this endpoint's results before sorting and pagination. Live rows are labelled `source: burp` or `source: caido` depending on which listener answered — filter to just those live rows with `?source=burp` / `?source=caido`. The response carries `X-Vigolium-Bridge-Source` naming the vendor. No separate public bridge endpoint is required. ```bash theme={null} # List recent records curl -s http://localhost:9002/api/http-records | jq . # Filter by domain curl -s 'http://localhost:9002/api/http-records?domain=example.com' | jq . # Filter by status code and method curl -s 'http://localhost:9002/api/http-records?status_code=200,301&method=GET' | jq . # Paginate curl -s 'http://localhost:9002/api/http-records?limit=10&offset=20' | jq . # Sort by response time descending curl -s 'http://localhost:9002/api/http-records?sort=response_time&order=desc' | jq . # Wildcard domain search curl -s 'http://localhost:9002/api/http-records?domain=*.example.com' | jq . ``` ```json theme={null} { "data": [ { "uuid": "rec-0056-seed-aaaa-bbbb-cccc0038", "scheme": "https", "hostname": "example.com", "port": 443, "ip": "93.184.216.34", "method": "GET", "path": "/ws/notifications", "url": "https://example.com/ws/notifications", "http_version": "HTTP/1.1", "request_content_length": 0, "request_hash": "5eca33649eaa2c83a1cecfa1f039e465", "status_code": 101, "status_phrase": "Switching Protocols", "response_http_version": "HTTP/1.1", "response_content_length": 0, "response_hash": "c175cfa5a478d9b4320fff7b557ff80c", "response_time_ms": 5, "response_words": 0, "has_response": true, "sent_at": "2026-03-03T10:39:52.423708Z", "received_at": "2026-03-03T10:39:52.428708Z", "created_at": "2026-03-03T10:39:52.423708Z", "source": "seed", "risk_score": 0 } ], "total": 1234, "limit": 50, "offset": 0, "has_more": true } ``` > **Note:** The fields `raw_request`, `raw_response`, `request_body`, `response_body`, `request_headers`, and `response_headers` are excluded from list responses for performance. Use `GET /api/http-records/:uuid` to access the full record including headers and bodies. Fields with empty values (e.g. `request_content_type`, `parameters`, `remarks`) are omitted from the JSON response. *** ## GET /api/http-records/:uuid, Get HTTP Record Detail Returns a single HTTP record by UUID, including full blob fields (`raw_request`, `raw_response`, `request_body`, `response_body`). ```bash theme={null} curl -s http://localhost:9002/api/http-records/abc-123 | jq . ``` ```json theme={null} { "uuid": "abc-123", "scheme": "https", "hostname": "example.com", "port": 443, "method": "POST", "path": "/api/login", "url": "https://example.com/api/login", "status_code": 200, "raw_request": "POST /api/login HTTP/1.1\r\nHost: example.com\r\n...", "raw_response": "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n...", "request_body": "{\"user\":\"admin\",\"pass\":\"test\"}", "response_body": "{\"token\":\"eyJ...\"}", "created_at": "2026-02-16T15:00:00Z" } ``` **Error responses:** | Code | Condition | | ---- | -------------------- | | 400 | Missing UUID | | 404 | Record not found | | 503 | Database unavailable | *** ## DELETE /api/http-records/:uuid, Delete HTTP Record Deletes a single HTTP record by UUID. Associated `finding_records` junction rows are also removed. ```bash theme={null} curl -s -X DELETE http://localhost:9002/api/http-records/550e8400-e29b-41d4-a716-446655440000 | jq . ``` **Response:** ```json theme={null} { "message": "HTTP record deleted", "uuid": "550e8400-e29b-41d4-a716-446655440000" } ``` | Status | Description | | ------ | ----------------------- | | 200 | Record deleted | | 404 | Record not found | | 503 | Database not configured | # Vigolium API Reference, Ingestion Source: https://docs.vigolium.com/api-references/ingestion Import HTTP request/response data into the database for scanning via URLs, curl commands, HAR, OpenAPI, and more. ## POST /api/ingest-http, Ingest HTTP Data Import HTTP request/response data into the database for scanning. Supports multiple input formats. **Headers:** | Header | Required | Description | | ------------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `Content-Type` | Yes | Must be `application/json` | | `X-Project-UUID` | No | Project UUID to scope the imported data. Falls back to the default project if omitted. | | `X-Vigolium-Source` | No | The pushing tool: `burp` or `caido`. Anything else (or omitted) stores the record as `ingest-server`. | ### Declaring the pushing tool Records ingested through this endpoint are stored with `source: "ingest-server"` by default — the same label every HTTP client shares. A client can name itself instead, so its traffic stays distinguishable later: ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -H "X-Vigolium-Source: caido" \ -d '{"input_mode": "url", "content": "https://example.com/api/users?id=1"}' | jq . ``` Accepted values are `burp` and `caido` — the same labels the [live bridge](/server-mode/running-the-server#live-proxy-bridge) writes, so one `?source=caido` filter covers traffic that arrived either by Vigolium pulling over the bridge or by the plugin pushing here. The Vigolium [Burp extension](/getting-started/burp-suite) and [Caido plugin](/getting-started/caido-plugin) set it automatically. The list is a closed **allowlist**, not a passthrough, and anything else falls back to `ingest-server`. `source` is not only a display label: it decides which rows scan-on-receive feeds back into a running scan, so a client free to claim `scanner` could silently exclude its own traffic from the scan it just asked for. **Request body:** | Field | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------------- | | `input_mode` | string | Yes | Input format (see modes below) | | `url` | string | No | Override URL (used as base URL for some modes) | | `content` | string | No | Raw content (plaintext) | | `content_base64` | string | No | Base64-encoded content | | `http_request_base64` | string | No | Base64-encoded raw HTTP request (for `burp_base64`) | | `http_response_base64` | string | No | Base64-encoded raw HTTP response (for `burp_base64`) | **Input modes:** | Mode | Description | Content field | | ---------------------- | ----------------------------------- | --------------------- | | `url` | Single URL | `content` | | `url_file` | Newline-separated list of URLs | `content` | | `curl` | Single curl command | `content` | | `burp_base64` | Base64 raw HTTP request (+response) | `http_request_base64` | | `openapi` / `swagger` | OpenAPI/Swagger spec (JSON or YAML) | `content` | | `postman_collection` | Postman Collection v2 | `content` | | `har` / `http_archive` | HAR (HTTP Archive) 1.2 JSON | `content` | ### Ingest a URL ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d '{ "input_mode": "url", "content": "https://example.com/api/users?id=1" }' | jq . ``` ### Ingest a curl command ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d '{ "input_mode": "curl", "content": "curl -X POST https://example.com/login -d \"user=admin&pass=test\"" }' | jq . ``` ### Ingest a list of URLs ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d '{ "input_mode": "url_file", "content": "https://example.com/page1\nhttps://example.com/page2\nhttps://example.com/page3" }' | jq . ``` ### Ingest a raw HTTP request (base64) ```bash theme={null} # Base64-encode a raw HTTP request REQ_B64=$(echo -n "GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\n" | base64) curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"burp_base64\", \"http_request_base64\": \"$REQ_B64\" }" | jq . ``` ### Ingest a raw HTTP request with a URL hint (base64) Raw HTTP requests don't contain the scheme (`https` vs `http`) and the `Host` header alone may not reflect the actual target (e.g. behind a reverse proxy). Provide `url` alongside `http_request_base64` so the parser can resolve the correct scheme and hostname. ```bash theme={null} REQ_B64=$(echo -n "POST /api/login HTTP/1.1\r\nHost: internal-lb\r\nContent-Type: application/json\r\n\r\n{\"user\":\"admin\"}" | base64) curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"burp_base64\", \"url\": \"https://app.example.com\", \"http_request_base64\": \"$REQ_B64\" }" | jq . ``` The `url` field provides the scheme (`https`) and the public hostname (`app.example.com`), overriding whatever `Host` header appeared in the raw request. ### Ingest a HAR file ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"har\", \"content_base64\": \"$(base64 < recording.har)\" }" | jq . ``` ### Ingest an OpenAPI spec ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"openapi\", \"content_base64\": \"$(base64 < openapi.json)\" }" | jq . ``` **Response:** ```json theme={null} { "project_uuid": "default", "imported": 15, "skipped": 0, "errors": [], "message": "imported 15 requests from OpenAPI spec" } ``` ### Ingest into a specific project Use the `X-Project-UUID` header to scope imported data to a project. If omitted, data is stored under the default project. ```bash theme={null} curl -s -X POST http://localhost:9002/api/ingest-http \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "input_mode": "url", "content": "https://example.com/api/users" }' | jq . ``` ```json theme={null} { "project_uuid": "my-project-uuid", "imported": 1, "skipped": 0, "message": "imported 1 request from URL" } ``` ## POST /api/burp/sitemap/snapshot, Ingest a Burp Site Map Snapshot Uploads a chunk of a Burp Suite **Target Site map** snapshot into the database. This is the endpoint the companion [`burp-vigolium`](https://github.com/vigolium/burp-vigolium) extension calls when you snapshot your Target Site map (`Ctrl+Alt+S` or periodic snapshots). Snapshots are **chunked** (a single site map is split across sequential requests), **incremental** within a Burp session, and **idempotent** on the server — unchanged records are never duplicated. **Headers:** | Header | Required | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------- | | `Authorization` | Yes | `Bearer ` — requires **operator** (or admin) role | | `Content-Type` | Yes | Must be `application/json` | | `X-Project-UUID` | No | Project UUID to scope the imported records. Falls back to the default project if omitted. | **Request body:** | Field | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------------------------------- | | `snapshot_id` | string | Yes | Identifier for the snapshot the chunks belong to | | `chunk_index` | int | Yes | Zero-based index of this chunk within the snapshot | | `final_chunk` | bool | No | `true` on the last chunk of the snapshot | | `captured_at` | string | No | Timestamp the snapshot was captured in Burp | | `records` | array | Yes | Up to **200** site map records per chunk (see below) | Each entry in `records`: | Field | Type | Required | Description | | ---------------------- | ------ | -------- | ----------------------------------------------------------------- | | `url` | string | No | Absolute URL — supplies the scheme and host the raw request lacks | | `request_base64` | string | Yes | Base64-encoded raw HTTP request | | `response_base64` | string | No | Base64-encoded raw HTTP response, when Burp captured one | | `identity_fingerprint` | string | No | Extension-computed request-identity hash (drives dedup) | | `content_fingerprint` | string | No | Extension-computed response-content hash (detects changed rows) | A chunk carrying more than 200 records is rejected with `400`. ### Upload a snapshot chunk ```bash theme={null} curl -s -X POST http://localhost:9002/api/burp/sitemap/snapshot \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"snapshot_id\": \"snap-2026-07-11T10-00-00\", \"chunk_index\": 0, \"final_chunk\": true, \"records\": [ { \"url\": \"https://example.com/api/users\", \"request_base64\": \"$REQ_B64\", \"response_base64\": \"$RESP_B64\", \"identity_fingerprint\": \"a1b2c3\", \"content_fingerprint\": \"d4e5f6\" } ] }" | jq . ``` **Response:** ```json theme={null} { "received": 1, "inserted": 1, "updated": 0, "unchanged": 0, "skipped": 0, "errors": [] } ``` Every record is upserted individually: `inserted` counts new rows, `updated` counts rows whose response changed, `unchanged` counts idempotent no-ops, and `skipped` counts records that failed to decode or parse (with the reason appended to `errors`). `received` is the number of records in the chunk. # Vigolium API Reference, Modules Source: https://docs.vigolium.com/api-references/modules List registered active and passive scanner modules with search, tag filtering, and tag categories. ## GET /api/modules, List Modules Returns all registered scanner modules (active and passive). **Query parameters:** | Parameter | Type | Description | | --------- | ------ | --------------------------------------------------------------------------- | | `search` | string | Filter by module name, ID, description, or tag | | `tag` | string | Filter by exact tag (case-insensitive, e.g. `spring`, `injection`, `heavy`) | ```bash theme={null} # List all modules curl -s http://localhost:9002/api/modules | jq . # Search for XSS modules curl -s 'http://localhost:9002/api/modules?search=xss' | jq . # Filter by tag curl -s 'http://localhost:9002/api/modules?tag=spring' | jq . # Combine search and tag curl -s 'http://localhost:9002/api/modules?search=misconfig&tag=java' | jq . ``` **Response (200):** ```json theme={null} { "modules": [ { "id": "xss-light", "name": "XSS Light Scanner", "description": "...", "short_description": "Detects reflected XSS via character transformation analysis", "confirmation_criteria": "Confirmed when injected probe characters are reflected without sanitization", "severity": "high", "confidence": "firm", "scan_scope": ["PER_REQUEST"], "tags": ["injection", "xss", "light"], "type": "active" }, { "id": "spring-actuator-misconfig", "name": "Spring Actuator Misconfiguration", "description": "...", "short_description": "Detects exposed Spring Boot actuator endpoints", "confirmation_criteria": "...", "severity": "high", "confidence": "certain", "scan_scope": ["PER_HOST"], "tags": ["spring", "java", "misconfiguration", "info-disclosure", "light"], "type": "active" } ], "total": 2 } ``` ### Tag Categories Modules are tagged across four dimensions: | Category | Example tags | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Technology** | `spring`, `rails`, `laravel`, `django`, `express`, `nextjs`, `aspnet`, `iis`, `php`, `wordpress`, `drupal`, `java`, `python`, `ruby`, `nodejs`, `javascript`, `nginx`, `tomcat`, `firebase`, `graphql`, `aem`, `adobe`, `salesforce`, `aura`, `lightning`, `servicenow`, `powerpages`, `dataverse`, `mcp` | | **Vulnerability class** | `injection`, `xss`, `sqli`, `ssti`, `lfi`, `rce`, `ssrf`, `xxe`, `csrf`, `idor`, `open-redirect`, `deserialization`, `prototype-pollution`, `cache-poisoning`, `smuggling`, `race-condition` | | **Category** | `misconfiguration`, `info-disclosure`, `fingerprint`, `authentication`, `session`, `cryptography`, `file-exposure`, `cloud`, `cms`, `api`, `api-security`, `header-security`, `behavior-analysis`, `supply-chain`, `source-analysis` | | **Resource cost** | `light` (few requests / passive), `moderate` (typical active scan), `heavy` (timing-based, blind, many requests) | # Vigolium API Reference, OAST Interactions Source: https://docs.vigolium.com/api-references/oast-interactions List, retrieve, and delete out-of-band application security testing interactions from interactsh callbacks. Out-of-band Application Security Testing (OAST) interactions recorded from interactsh callbacks. These represent DNS, HTTP, or other protocol interactions triggered by payloads injected during active scanning. ## GET /api/oast-interactions, List OAST Interactions Returns paginated OAST interactions. Heavy fields (`raw_request`, `raw_response`) are excluded from list responses for performance. **Query parameters:** | Parameter | Type | Default | Description | | ----------- | ------ | ------- | --------------------------------------------------- | | `limit` | int | 50 | Number of interactions to return (max 500) | | `offset` | int | 0 | Offset for pagination | | `scan_id` | string | | Filter by scan UUID | | `protocol` | string | | Filter by protocol (e.g. `dns`, `http`, `smtp`) | | `module_id` | string | | Filter by module ID | | `search` | string | | Search across target URL, parameter name, unique ID | ```bash theme={null} # List all OAST interactions curl -s http://localhost:9002/api/oast-interactions | jq . # Filter by protocol curl -s 'http://localhost:9002/api/oast-interactions?protocol=dns&limit=5' | jq . # Filter by scan curl -s 'http://localhost:9002/api/oast-interactions?scan_id=scan-456' | jq . # Search by target URL curl -s 'http://localhost:9002/api/oast-interactions?search=example.com' | jq . ``` ```json theme={null} { "data": [ { "id": 1, "scan_uuid": "scan-456", "unique_id": "abc123def456", "full_id": "abc123def456.oast.fun", "protocol": "dns", "q_type": "A", "remote_address": "203.0.113.42", "interacted_at": "2026-02-16T15:10:00Z", "target_url": "https://example.com/api/users?id=1", "parameter_name": "id", "injection_type": "param_value", "module_id": "ssrf-detection", "created_at": "2026-02-16T15:10:01Z" } ], "total": 12, "limit": 50, "offset": 0, "has_more": false } ``` > **Note:** The fields `raw_request` and `raw_response` are excluded from list responses. Use `GET /api/oast-interactions/:id` to access full interaction data. *** ## GET /api/oast-interactions/:id, Get OAST Interaction Detail Returns a single OAST interaction by its numeric ID, including full `raw_request` and `raw_response` fields. ```bash theme={null} curl -s http://localhost:9002/api/oast-interactions/1 | jq . ``` ```json theme={null} { "id": 1, "scan_uuid": "scan-456", "unique_id": "abc123def456", "full_id": "abc123def456.oast.fun", "protocol": "http", "q_type": "", "raw_request": "GET / HTTP/1.1\r\nHost: abc123def456.oast.fun\r\n\r\n", "raw_response": "HTTP/1.1 200 OK\r\n\r\n...", "remote_address": "203.0.113.42", "interacted_at": "2026-02-16T15:10:00Z", "target_url": "https://example.com/api/users?id=1", "parameter_name": "id", "injection_type": "param_value", "module_id": "ssrf-detection", "created_at": "2026-02-16T15:10:01Z" } ``` **Error responses:** | Code | Condition | | ---- | -------------------------- | | 400 | Invalid ID (not a number) | | 404 | OAST interaction not found | | 503 | Database unavailable | *** ## DELETE /api/oast-interactions/:id, Delete OAST Interaction Deletes a single OAST interaction by its numeric ID. Returns `404` if the interaction does not exist. ```bash theme={null} curl -s -X DELETE http://localhost:9002/api/oast-interactions/1 | jq . ``` **Response (200):** ```json theme={null} { "message": "OAST interaction deleted", "id": 1 } ``` **Error responses:** | Code | Condition | | ---- | -------------------------- | | 400 | Invalid ID (not a number) | | 404 | OAST interaction not found | | 503 | Database unavailable | # Vigolium API Reference, Overview Source: https://docs.vigolium.com/api-references/overview Base URL, authentication, project scoping, health checks, and common endpoints for the Vigolium API server. Base URL: `http://localhost:9002` (default) ## Starting the Server ```bash theme={null} # No authentication (development) vigolium server -A # With API key export VIGOLIUM_API_KEY="my-secret-key" vigolium server -A # Custom host/port vigolium server -A --host 127.0.0.1 --service-port 8080 ``` ## Authentication `/api/*` routes require a Bearer token when the server is started with `VIGOLIUM_API_KEY` or `server.auth_api_key` set. ```bash theme={null} curl -H "Authorization: Bearer my-secret-key" http://localhost:9002/api/stats ``` Public endpoints (no auth required): `GET /`, `GET /health`, `GET /server-info`, `GET /swagger/*`, `GET /metrics`, and `POST /api/auth/login`. Demo-only and view-only modes (`cfg.DemoOnly` / `cfg.ViewOnly`) further gate mutating routes; admin/operator/viewer roles enforced by `RoleGuard` middleware override the per-route checks. ## Project Scoping All API operations are scoped to a project via the `X-Project-UUID` request header. If the header is omitted, the default project (`00000000-0000-0000-0000-000000000001`) is used. ```bash theme={null} # Scope requests to a specific project curl -H "Authorization: Bearer my-secret-key" \ -H "X-Project-UUID: a1b2c3d4-..." \ http://localhost:9002/api/findings ``` This applies to all data endpoints: ingestion, findings, HTTP records, stats, scans, source repos, and OAST interactions. See [Projects](/api-references/projects) for the full multi-tenancy reference. *** ## GET /api/info, App Info Returns basic application metadata. (`GET /` serves the embedded static UI, not JSON.) ```bash theme={null} curl -s http://localhost:9002/api/info | jq . ``` ```json theme={null} { "name": "vigolium", "version": "v0.1.8-alpha", "author": "vigolium", "docs": "https://docs.vigolium.com", "build_time": "2026-05-21T15:22:43Z", "commit": "67bdce4" } ``` *** ## GET /health, Health Check Returns server health status. ```bash theme={null} curl -s http://localhost:9002/health | jq . ``` ```json theme={null} { "status": "healthy", "timestamp": "2026-02-16T15:30:00Z" } ``` *** ## GET /server-info, Server Info Returns detailed server information including uptime, database driver, queue depth, and record/finding totals. ```bash theme={null} curl -s http://localhost:9002/server-info | jq . ``` ```json theme={null} { "name": "vigolium", "version": "v0.1.8-alpha", "author": "vigolium", "docs": "https://docs.vigolium.com", "build_time": "2026-05-21T15:22:43Z", "commit": "67bdce4", "uptime": "5m32s", "service_addr": "0.0.0.0:9002", "proxy_addr": "", "queue_depth": 0, "total_records": 1234, "total_findings": 42, "license_spdx": "AGPL-3.0", "source": "https://github.com/vigolium/vigolium" } ``` *** ## GET /swagger/\*, Swagger UI Interactive API documentation. Open in a browser. ``` http://localhost:9002/swagger/ ``` The raw OpenAPI 3.0 spec is available at: ```bash theme={null} curl -s http://localhost:9002/swagger/doc.json | jq .info ``` *** ## GET /metrics, Prometheus Metrics Returns Prometheus-formatted metrics. The route is always registered and unauthenticated; the handler gates the response on the `server.enable_metrics` config key (on by default; there is no CLI flag for it). ```bash theme={null} curl -s http://localhost:9002/metrics ``` *** ## CORS CORS can be enabled via the `cors_allowed_origins` server config: | Value | Behavior | | ----------------- | ------------------------------------------------------------ | | `*` | Allow all origins | | `reflect-origin` | Reflect the request's `Origin` header (allows credentials) | | `origin1,origin2` | Allow specific origins (comma-separated, allows credentials) | | *(empty/omitted)* | CORS disabled | Allowed methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`. Allowed headers: `Content-Type`, `Authorization`, `X-Project-UUID`, `X-User-Email`. `PATCH` was missing from the preflight allowlist before **v0.4.2**, so a browser client could never reach `PATCH /api/findings/:id/status` — i.e. could never mark a finding triaged. *** ## Response Headers Every response carries the server banner and its companion author header: ``` Server: Vigolium v0.4.5 X-Author: j3ssie ``` `X-Vigolium-Bridge-Source` additionally appears on `/api/http-records` when a Burp/Caido bridge answered, naming the vendor. *** ## Request Body Limits Request bodies are capped per route. Since **v0.4.2** a chunked body is capped at `limit + 1` bytes and **drained before the 413** is returned, rather than being read to completion — previously a chunked upload could grow the heap to the framework's 512 MB ceiling on a route whose own limit was 4 MB. `--debug` logging no longer buffers streamed uploads into memory on the large-upload routes either. *** ## Error Responses All errors follow a consistent format: ```json theme={null} { "error": "error message", "code": 400, "details": "optional additional details" } ``` **Common error codes:** | Code | Meaning | | ---- | -------------------------------------------------------------------- | | 400 | Bad request (invalid JSON, missing fields) | | 401 | Unauthorized (missing or invalid Bearer token) | | 404 | Not found (e.g. agent run ID not found, or a missing storage object) | | 409 | Conflict (scan or agent already running) | | 413 | Payload too large (per-route body limit exceeded) | | 500 | Internal server error | | 503 | Database not available | **v0.4.2:** storage downloads used to answer a missing object with a *truncated stream* instead of a JSON 404 — a client saw a short file, not an error. They now return the standard error shape, and the storage read endpoints are reachable in `--view-only` mode. # Vigolium API Reference, Projects Source: https://docs.vigolium.com/api-references/projects Manage projects for multi-tenant data isolation. All scan data is scoped to a project via project_uuid. Manage projects for multi-tenant data isolation. All scan data (HTTP records, findings, scopes, scans) is scoped to a project via `project_uuid`. > **Note:** These endpoints manage project records themselves. To scope API operations to a specific project, use the `X-Project-UUID` request header on other endpoints. ## GET /api/projects, List Projects Returns all projects with aggregated statistics. Optionally filter by owner UUID. **Query parameters:** | Parameter | Type | Default | Description | | --------- | ------ | ------- | -------------------- | | `owner` | string | | Filter by owner UUID | ```bash theme={null} # List all projects curl -s http://localhost:9002/api/projects | jq . # Filter by owner curl -s 'http://localhost:9002/api/projects?owner=00000000-0000-0000-0000-000000000001' | jq . ``` ```json theme={null} [ { "uuid": "00000000-0000-0000-0000-000000000001", "name": "default", "description": "Default project", "owner_uuid": "00000000-0000-0000-0000-000000000001", "created_at": "2026-02-19T10:00:00Z", "updated_at": "2026-02-19T10:00:00Z", "stats": { "http_records": { "total": 1234, "success": 980, "redirect": 54, "client_err": 180, "server_err": 20 }, "findings": { "total": 42, "critical": 2, "high": 10, "medium": 15, "low": 10, "info": 5 }, "scans": 3, "agentic_scans": 7, "oast_interactions": 12 } } ] ``` **Stats fields:** | Field | Type | Description | | ------------------------------- | ---- | ----------------------------------------------------- | | `stats.http_records.total` | int | Total HTTP records in the project | | `stats.http_records.success` | int | 2xx status code count | | `stats.http_records.redirect` | int | 3xx status code count | | `stats.http_records.client_err` | int | 4xx status code count | | `stats.http_records.server_err` | int | 5xx status code count | | `stats.findings.total` | int | Total findings | | `stats.findings.critical` | int | Critical severity count | | `stats.findings.high` | int | High severity count | | `stats.findings.medium` | int | Medium severity count | | `stats.findings.low` | int | Low severity count | | `stats.findings.info` | int | Info severity count | | `stats.scans` | int | Total native scan sessions | | `stats.agentic_scans` | int | Total agentic scan runs (query/autopilot/swarm/audit) | | `stats.oast_interactions` | int | Total OAST (out-of-band) interactions | **Errors:** | Code | Condition | | ---- | ---------------------- | | 503 | Database not connected | *** ## POST /api/projects, Create Project **Request body:** | Field | Type | Required | Description | | ------------- | ------ | -------- | ----------------------- | | `name` | string | Yes | Project name | | `description` | string | No | Project description | | `owner_uuid` | string | No | UUID of the owning user | ```bash theme={null} curl -s -X POST http://localhost:9002/api/projects \ -H 'Content-Type: application/json' \ -d '{"name": "my-project", "description": "Web app audit"}' | jq . ``` ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "my-project", "description": "Web app audit", "created_at": "2026-03-06T12:00:00Z", "updated_at": "2026-03-06T12:00:00Z" } ``` **Errors:** | Code | Condition | | ---- | ---------------------- | | 400 | Missing `name` field | | 400 | Invalid request body | | 503 | Database not connected | *** ## GET /api/projects/:uuid, Get Project Retrieve a single project by UUID with aggregated statistics. ```bash theme={null} curl -s http://localhost:9002/api/projects/a1b2c3d4-e5f6-7890-abcd-ef1234567890 | jq . ``` ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "my-project", "description": "Web app audit", "owner_uuid": "00000000-0000-0000-0000-000000000001", "created_at": "2026-03-06T12:00:00Z", "updated_at": "2026-03-06T12:00:00Z", "stats": { "http_records": { "total": 567, "success": 450, "redirect": 30, "client_err": 72, "server_err": 15 }, "findings": { "total": 18, "critical": 1, "high": 4, "medium": 6, "low": 5, "info": 2 }, "scans": 2, "agentic_scans": 3, "oast_interactions": 5 } } ``` **Errors:** | Code | Condition | | ---- | ---------------------- | | 404 | Project not found | | 503 | Database not connected | *** ## PUT /api/projects/:uuid, Update Project Update fields on an existing project. Only non-empty fields are applied. **Request body:** | Field | Type | Required | Description | | ------------- | ------ | -------- | ---------------- | | `name` | string | No | New project name | | `description` | string | No | New description | | `owner_uuid` | string | No | New owner UUID | ```bash theme={null} curl -s -X PUT http://localhost:9002/api/projects/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H 'Content-Type: application/json' \ -d '{"description": "Updated description"}' | jq . ``` ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "my-project", "description": "Updated description", "owner_uuid": "00000000-0000-0000-0000-000000000001", "created_at": "2026-03-06T12:00:00Z", "updated_at": "2026-03-06T12:30:00Z" } ``` **Errors:** | Code | Condition | | ---- | ---------------------- | | 400 | Invalid request body | | 404 | Project not found | | 503 | Database not connected | *** ## DELETE /api/projects/:uuid, Delete Project Delete a project by UUID. The default project (`00000000-0000-0000-0000-000000000001`) cannot be deleted. All data (scans, HTTP records, findings, scopes, source repos, OAST interactions, scan logs) belonging to the deleted project is automatically reassigned to the default project. ```bash theme={null} curl -s -X DELETE http://localhost:9002/api/projects/a1b2c3d4-e5f6-7890-abcd-ef1234567890 | jq . ``` ```json theme={null} { "message": "project deleted", "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ``` **Errors:** | Code | Condition | | ---- | ------------------------------------ | | 400 | Attempting to delete default project | | 500 | Database deletion failed | | 503 | Database not connected | # Vigolium API Reference, Scan Source: https://docs.vigolium.com/api-references/scan Single-target scans, scan management, scan history, pause/resume, logs, selective record scans, and repository uploads. ## Project Scoping All scan endpoints support multi-tenancy via the `X-Project-UUID` header. When provided, scans are created and queried within the specified project. Responses include the `project_uuid` field to confirm the project context. ```bash theme={null} # Scope a request to a specific project curl -s -X POST http://localhost:9002/api/scans/run \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{}' | jq . ``` If the header is omitted, the default project is used. *** ## Single-Target Scans ### POST /api/scan-url, Scan a URL Starts an asynchronous scan of a single URL. Equivalent to the CLI `scan-url` command. Returns `202 Accepted` immediately with a scan ID. **Request body:** | Field | Type | Required | Description | | ------------ | ------------------ | -------- | --------------------------------- | | `url` | string | Yes | Target URL to scan | | `method` | string | No | HTTP method (default: `GET`) | | `body` | string | No | Request body | | `headers` | map\[string]string | No | Custom request headers | | `modules` | string | No | Comma-separated module IDs to run | | `no_passive` | bool | No | Skip passive modules | ```bash theme={null} # Simple GET scan curl -s -X POST http://localhost:9002/api/scan-url \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "url": "https://example.com/api/users?id=1" }' | jq . # POST with body and specific modules curl -s -X POST http://localhost:9002/api/scan-url \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "url": "https://example.com/api/login", "method": "POST", "body": "{\"user\":\"admin\",\"pass\":\"test\"}", "headers": { "Content-Type": "application/json" }, "modules": "xss-scanner,sqli-error-based" }' | jq . ``` **Response (202):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "scan-url started for https://example.com/api/users?id=1" } ``` *** ### POST /api/scan-request, Scan a Raw HTTP Request Starts an asynchronous scan from a base64-encoded raw HTTP request. Equivalent to the CLI `scan-request` command. Returns `202 Accepted` immediately with a scan ID. **Request body:** | Field | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- | | `http_request_base64` | string | Yes | Base64-encoded raw HTTP request (preferred; `raw_request` is accepted as a legacy alias) | | `http_response_base64` | string | No | Optional base64-encoded raw HTTP response to pair with the request | | `url` | string | No | URL hint when the raw request lacks scheme/host | | `target_url` | string | No | Override target URL (scheme://host) for the request | | `modules` | string | No | Comma-separated module IDs to run | | `no_passive` | bool | No | Skip passive modules | ```bash theme={null} # Base64-encode a raw HTTP request REQ_B64=$(echo -n "POST /api/login HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\n\r\n{\"user\":\"admin\"}" | base64) curl -s -X POST http://localhost:9002/api/scan-request \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d "{ \"http_request_base64\": \"$REQ_B64\", \"target_url\": \"https://example.com\" }" | jq . ``` **Response (202):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "661f9511-f3ac-52e5-b827-557766551111", "status": "running", "message": "scan-request started for https://example.com/api/login" } ``` **Error responses (both endpoints):** | Code | Condition | | ---- | ---------------------------------------- | | 400 | Missing required fields or invalid input | Use `GET /api/scan/status` to check the progress of the scan. *** ## Scan Management ### POST /api/scans/run, Run Target Scan Triggers a background scan against target URLs. Equivalent to `vigolium scan -t `. At least one target URL is required, use `POST /api/scan-all-records` to scan existing DB records. Returns `202 Accepted` on success, `200 OK` for dry runs, or `409 Conflict` if a scan is already running. **Request body:** | Field | Type | Required | Description | | ----------------------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `targets` | string\[] | Yes\* | Target URLs (like `-t`). | | `urls` | string\[] | Yes\* | Alias for `targets`. Both fields are merged if provided. | | `dry_run` | bool | No | Validate params and create scan record without launching the runner | | `strategy` | string | No | Strategy preset: `lite`, `balanced`, `deep` | | `only` | string | No | Single phase isolation (like `--only`). Accepts aliases. | | `skip` | string\[] | No | Skip specific phases (like `--skip`). Accepts aliases. | | `modules` | string\[] | No | Module IDs with fuzzy match (like `-m`) | | `module_tags` | string\[] | No | Filter modules by tag (like `--module-tag`) | | `concurrency` | int | No | Number of parallel workers | | `timeout` | string | No | Request timeout as Go duration (e.g. `"30s"`) | | `max_per_host` | int | No | Maximum concurrent requests per host | | `rate_limit` | int | No | Maximum request submissions per second | | `scanning_max_duration` | string | No | Global max scan duration as Go duration. Per-phase durations are derived automatically using `duration_factor` from the scanning pace config (e.g. spidering gets `0.15 × max_duration`). | | `scope_origin` | string | No | Scope origin mode: `all`, `relaxed`, `balanced`, `strict` | | `heuristics_check` | string | No | Heuristics check level: `none`, `basic`, `advanced` | | `headers` | map\[string]string | No | Custom HTTP headers included in all requests | | `scanning_profile` | string | No | Scanning profile name or path | > Source-aware whitebox analysis lives in agent mode. Use `POST /api/agent/run/swarm` with a `source` field for AI-driven route extraction + code audit + targeted scanning, `POST /api/agent/run/autopilot` for autonomous pentest with code context, or `POST /api/agent/run/audit` (CLI: `vigolium agent audit`) for the unified source-audit driver dispatcher. **Phase duration factors:** The `scanning_max_duration` field sets the global max duration. Each phase derives its own limit by multiplying the global value by its `duration_factor` from the scanning pace config. Default factors: | Phase | Default Factor | Example (2h global) | | ------------------ | -------------- | ------------------- | | Spidering | 0.15 | 18m | | External Harvester | 0.20 | 24m | | Dynamic-Assessment | 1.00 | 2h | | KnownIssueScan | 3.00 | 6h | Per-phase `max_duration` overrides in the YAML config take precedence over the factor calculation. **Phase names and aliases:** | Canonical name | Aliases | | -------------------- | ----------------------------- | | `discovery` | `deparos`, `discover` | | `spidering` | `spitolas` | | `dynamic-assessment` | `audit`, `dast`, `assessment` | | `extension` | `ext` | | `ingestion` | , | | `external-harvest` | , | | `known-issue-scan` | , | > **Note:** `only` and `skip` are mutually exclusive, providing both returns `400`. ```bash theme={null} # Scan a target with default strategy curl -s -X POST http://localhost:9002/api/scans/run \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "targets": ["https://example.com"] }' | jq . # Using "urls" alias curl -s -X POST http://localhost:9002/api/scans/run \ -H "Content-Type: application/json" \ -d '{ "urls": ["https://example.com", "https://api.example.com"] }' | jq . # Lite strategy, discovery only, with custom headers curl -s -X POST http://localhost:9002/api/scans/run \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "targets": ["https://example.com"], "strategy": "lite", "only": "discovery", "headers": {"Authorization": "Bearer tok123"}, "concurrency": 20, "timeout": "10s" }' | jq . # Dry run — validate params without launching the scan curl -s -X POST http://localhost:9002/api/scans/run \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "targets": ["https://example.com"], "strategy": "deep", "dry_run": true }' | jq . ``` **Response (202):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "scan started", "targets_count": 1, "scan_mode": "target" } ``` **Response, dry run (200):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "dry_run", "message": "scan record created (dry run)", "targets_count": 1, "scan_mode": "target" } ``` **Scan Configuration output:** When a scan starts (via API or CLI), the runner logs a configuration summary to stderr showing the effective settings. This is useful for verifying that `scanning_max_duration` and per-phase duration factors are applied correctly. ``` ✦ Scan Configuration ℹ Project: my-project-uuid ℹ Strategy: balanced ℹ Phases: ✓ ExternalHarvest (24m0s, x0.2) | ✓ Spidering (18m0s, x0.2) | ✓ Discovery ✓ KnownIssueScan (6h0m0s, x3.0) | ✓ Dynamic-Assessment (2h0m0s, x1.0) ℹ Speed: concurrency=50 | rate-limit=100 | max-per-host=30 ℹ Modules: 207 active, 116 passive ``` **Error responses:** | Code | Condition | | ---- | -------------------------------------------------- | | 400 | Missing targets, invalid parameters, or missing DB | | 409 | A scan is already running | | 500 | Failed to create scan runner | *** ### GET /api/scan/status, Scan Status Returns the status of the current or most recent scan. The `status` field reflects pause state when applicable. ```bash theme={null} curl -s http://localhost:9002/api/scan/status \ -H "X-Project-UUID: my-project-uuid" | jq . ``` **Running:** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "scan-abc123", "running": true, "status": "running" } ``` **Paused:** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "scan-abc123", "running": true, "status": "paused" } ``` **Idle (no scan running):** ```json theme={null} { "project_uuid": "my-project-uuid", "running": false, "status": "idle" } ``` *** > To cancel a running scan, call `POST /api/scans/:uuid/stop` (covered under [Scan Management](#post-apiscansuuidstop--stop-a-running-scan)) — there is no global `DELETE /api/scan` route. *** ## Scan History ### GET /api/scans, List Scans Returns paginated scan history ordered by creation date (newest first). **Query parameters:** | Parameter | Type | Default | Description | | --------- | ---- | ------- | ----------------------------------- | | `limit` | int | 50 | Number of scans to return (max 500) | | `offset` | int | 0 | Offset for pagination | ```bash theme={null} # List recent scans curl -s http://localhost:9002/api/scans \ -H "X-Project-UUID: my-project-uuid" | jq . # Paginate curl -s 'http://localhost:9002/api/scans?limit=10&offset=0' \ -H "X-Project-UUID: my-project-uuid" | jq . ``` ```json theme={null} { "project_uuid": "my-project-uuid", "data": [ { "uuid": "scan-abc123", "name": "api-scan", "status": "completed", "scan_source": "api", "scan_mode": "incremental", "modules": "all", "total_findings": 5, "processed_count": 150, "started_at": "2026-02-16T15:00:00Z", "finished_at": "2026-02-16T15:05:00Z", "created_at": "2026-02-16T15:00:00Z" } ], "total": 12, "limit": 50, "offset": 0, "has_more": false } ``` *** ### GET /api/scans/:uuid, Get Scan Detail Returns a single scan by UUID. ```bash theme={null} curl -s http://localhost:9002/api/scans/scan-abc123 \ -H "X-Project-UUID: my-project-uuid" | jq . ``` **Error responses:** | Code | Condition | | ---- | -------------------- | | 400 | Missing UUID | | 404 | Scan not found | | 503 | Database unavailable | *** ### DELETE /api/scans/:uuid, Delete Scan Deletes a scan record by UUID. ```bash theme={null} curl -s -X DELETE http://localhost:9002/api/scans/scan-abc123 \ -H "X-Project-UUID: my-project-uuid" | jq . ``` ```json theme={null} { "project_uuid": "my-project-uuid", "message": "scan deleted", "uuid": "scan-abc123" } ``` **Error responses:** | Code | Condition | | ---- | -------------------- | | 400 | Missing UUID | | 404 | Scan not found | | 503 | Database unavailable | *** ### POST /api/scans/:uuid/stop, Stop a Running Scan Stops a specific running scan by UUID. The scan must be the currently active scan. Workers finish their current tasks before fully stopping. ```bash theme={null} curl -s -X POST http://localhost:9002/api/scans/scan-abc123/stop \ -H "X-Project-UUID: my-project-uuid" | jq . ``` ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "scan-abc123", "running": true, "status": "cancelling", "message": "scan stop requested, workers finishing current tasks" } ``` **Error responses:** | Code | Condition | | ---- | ----------------------------------------------- | | 400 | Missing UUID | | 409 | No scan running, or UUID is not the active scan | *** ### POST /api/scans/:uuid/pause, Pause a Running Scan Pauses a running scan. Workers finish their current item then block until resumed. The scan status is set to `"paused"` in the database. ```bash theme={null} curl -s -X POST http://localhost:9002/api/scans/scan-abc123/pause \ -H "X-Project-UUID: my-project-uuid" | jq . ``` ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "scan-abc123", "running": true, "status": "paused", "message": "scan paused, workers finishing current items" } ``` **Error responses:** | Code | Condition | | ---- | ----------------------------------------------------------------------- | | 400 | Missing UUID | | 409 | No scan running, UUID is not the active scan, or scan is already paused | *** ### POST /api/scans/:uuid/resume, Resume a Paused Scan Resumes a previously paused scan. Blocked workers continue processing items and the scan status is set back to `"running"`. ```bash theme={null} curl -s -X POST http://localhost:9002/api/scans/scan-abc123/resume \ -H "X-Project-UUID: my-project-uuid" | jq . ``` ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "scan-abc123", "running": true, "status": "running", "message": "scan resumed" } ``` **Error responses:** | Code | Condition | | ---- | ------------------------------------------------------------------- | | 400 | Missing UUID | | 409 | No scan running, UUID is not the active scan, or scan is not paused | *** ### GET /api/scans/:uuid/logs, Get Scan Logs Returns log entries for a scan, ordered by creation time ascending. Logs are captured at multiple levels: * **Structured events** (`info`, `warn`, `error`): Phase lifecycle events (start, complete, fail, skip), scan start/finish, pause/resume, configuration snapshots, and panic recovery. * **Raw console output** (`trace`): Every line printed to the terminal during the scan, with ANSI color codes stripped. This includes phase headers, traffic lines (`❯ spider │ [200] GET ...`), progress feedback, and scan configuration banners. **Query parameters:** | Parameter | Type | Default | Description | | ----------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `limit` | int | 100 | Number of log entries to return | | `offset` | int | 0 | Offset for pagination | | `level` | string | | Filter by log level: `trace`, `info`, `warn`, `error`. Use `trace` to replay raw console output. | | `phase` | string | | Filter by scan phase: `config`, `heuristics`, `harvest`, `discovery`, `spidering`, `known-issue-scan`, `dynamic-assessment` (alias `audit`), `seed`. Use `config` to retrieve the scan configuration snapshot. | | `max_bytes` | int | 2 MiB | Cap on how much of `runtime.log` is read, up to 64 MiB (v0.4.2) | **v0.4.2:** this endpoint returns the **last 2 MiB** of `runtime.log` by default rather than the whole file on every poll — a long-running scan's log grows without bound, and a dashboard polling it was re-reading all of it each time. Raise the window with `?max_bytes=` (hard cap 64 MiB). ```bash theme={null} # Get all logs for a scan curl -s http://localhost:9002/api/scans/scan-abc123/logs \ -H "X-Project-UUID: my-project-uuid" | jq . # Filter by level with pagination curl -s 'http://localhost:9002/api/scans/scan-abc123/logs?level=error&limit=20' \ -H "X-Project-UUID: my-project-uuid" | jq . # Replay raw console output (terminal log) curl -s 'http://localhost:9002/api/scans/scan-abc123/logs?level=trace' \ -H "X-Project-UUID: my-project-uuid" | jq . # Get only spidering phase logs curl -s 'http://localhost:9002/api/scans/scan-abc123/logs?phase=spidering' \ -H "X-Project-UUID: my-project-uuid" | jq . # Get scan configuration snapshot curl -s 'http://localhost:9002/api/scans/scan-abc123/logs?phase=config' \ -H "X-Project-UUID: my-project-uuid" | jq . # Combine filters: structured discovery events only curl -s 'http://localhost:9002/api/scans/scan-abc123/logs?level=info&phase=discovery' \ -H "X-Project-UUID: my-project-uuid" | jq . ``` **Response, structured events (`level=info`):** ```json theme={null} { "project_uuid": "my-project-uuid", "logs": [ { "id": 1, "scan_uuid": "scan-abc123", "level": "info", "message": "scan started", "created_at": "2026-02-16T15:00:00Z" }, { "id": 2, "scan_uuid": "scan-abc123", "level": "info", "phase": "config", "message": "scan configuration snapshot", "metadata": "{\"project_uuid\":\"my-project-uuid\",\"targets\":[\"https://example.com\"],\"strategy\":\"balanced\",\"concurrency\":50,\"rate_limit\":100,\"max_per_host\":30,\"active_modules\":201,\"passive_modules\":116,\"spidering_enabled\":true,\"discovery_enabled\":true,\"known_issue_scan_enabled\":false}", "created_at": "2026-02-16T15:00:00Z" }, { "id": 3, "scan_uuid": "scan-abc123", "level": "info", "phase": "heuristics", "message": "phase started", "created_at": "2026-02-16T15:00:01Z" }, { "id": 4, "scan_uuid": "scan-abc123", "level": "info", "phase": "heuristics", "message": "phase completed", "created_at": "2026-02-16T15:00:02Z" }, { "id": 5, "scan_uuid": "scan-abc123", "level": "info", "phase": "spidering", "message": "phase started", "created_at": "2026-02-16T15:00:02Z" }, { "id": 6, "scan_uuid": "scan-abc123", "level": "info", "phase": "spidering", "message": "phase completed", "created_at": "2026-02-16T15:01:30Z" }, { "id": 7, "scan_uuid": "scan-abc123", "level": "info", "phase": "discovery", "message": "phase started", "created_at": "2026-02-16T15:01:31Z" }, { "id": 8, "scan_uuid": "scan-abc123", "level": "info", "phase": "discovery", "message": "soft-deduplicated 42 similar records", "created_at": "2026-02-16T15:02:30Z" }, { "id": 9, "scan_uuid": "scan-abc123", "level": "info", "phase": "discovery", "message": "phase completed", "created_at": "2026-02-16T15:03:00Z" }, { "id": 10, "scan_uuid": "scan-abc123", "level": "info", "phase": "audit", "message": "phase started", "metadata": "{\"active_modules\":201,\"passive_modules\":116}", "created_at": "2026-02-16T15:03:01Z" }, { "id": 11, "scan_uuid": "scan-abc123", "level": "info", "phase": "audit", "message": "phase completed", "created_at": "2026-02-16T15:10:00Z" }, { "id": 12, "scan_uuid": "scan-abc123", "level": "info", "message": "scan finished", "created_at": "2026-02-16T15:10:01Z" } ], "total": 12 } ``` **Response, raw console output (`level=trace`):** ```json theme={null} { "project_uuid": "my-project-uuid", "logs": [ { "id": 100, "scan_uuid": "scan-abc123", "level": "trace", "phase": "heuristics", "message": "✦ HeuristicsCheck probing CLI target root pages to optimize phase selection", "created_at": "2026-02-16T15:00:01Z" }, { "id": 101, "scan_uuid": "scan-abc123", "level": "trace", "phase": "heuristics", "message": "◆ Level: basic | Targets: 1", "created_at": "2026-02-16T15:00:01Z" }, { "id": 102, "scan_uuid": "scan-abc123", "level": "trace", "phase": "spider", "message": "✦ Spidering browser-based crawling to discover dynamic content and API endpoints", "created_at": "2026-02-16T15:00:02Z" }, { "id": 103, "scan_uuid": "scan-abc123", "level": "trace", "phase": "spider", "message": "❯ spider │ [200] GET text/html http://localhost:3000/", "created_at": "2026-02-16T15:00:03Z" }, { "id": 104, "scan_uuid": "scan-abc123", "level": "trace", "phase": "spider", "message": "❯ spider │ [200] GET application/json http://localhost:9002/api/projects", "created_at": "2026-02-16T15:00:04Z" }, { "id": 105, "scan_uuid": "scan-abc123", "level": "trace", "phase": "discovery", "message": "✦ Discovery ingest input + content discovery into database", "created_at": "2026-02-16T15:01:31Z" } ], "total": 580 } ``` **Response, configuration snapshot (`phase=config`):** ```json theme={null} { "project_uuid": "my-project-uuid", "logs": [ { "id": 2, "scan_uuid": "scan-abc123", "level": "info", "phase": "config", "message": "scan configuration snapshot", "metadata": "{\"project_uuid\":\"my-project-uuid\",\"targets\":[\"https://example.com\"],\"strategy\":\"balanced\",\"scanning_profile\":\"\",\"concurrency\":50,\"rate_limit\":100,\"max_per_host\":30,\"heuristics_check\":\"basic\",\"scope_origin_mode\":\"relaxed\",\"active_modules\":201,\"passive_modules\":116,\"spidering_enabled\":true,\"discovery_enabled\":true,\"known_issue_scan_enabled\":false,\"external_harvest\":false,\"skip_dynamic\":false}", "created_at": "2026-02-16T15:00:00Z" } ], "total": 1 } ``` **Log entry fields:** | Field | Type | Description | | ------------ | ------ | ------------------------------------------------------------------------------------------------------- | | `id` | int | Auto-incrementing log entry ID | | `scan_uuid` | string | UUID of the scan this log belongs to | | `level` | string | Log level: `trace`, `info`, `warn`, or `error` | | `phase` | string | Scan phase (see table below), or empty for global events (scan start/finish, pause/resume) | | `message` | string | Human-readable log message. For `trace` entries, this is the raw console line with ANSI codes stripped. | | `metadata` | string | Optional JSON blob with structured context. Present on config snapshots and phase-start events. | | `created_at` | string | ISO 8601 timestamp | **Phase values:** | Phase | Description | | -------------------- | ------------------------------------------------------- | | `config` | Scan configuration snapshot (logged once at scan start) | | `heuristics` | Target root page probing to optimize phase selection | | `harvest` | External URL harvesting from intelligence sources | | `discovery` | Input ingestion + content discovery | | `spidering` | Browser-based crawling | | `seed` | CLI target seeding (when discovery is skipped) | | `known-issue-scan` | Known issue scan (Nuclei + Kingfisher) | | `dynamic-assessment` | Active/passive module scanning (alias: `audit`) | **Log levels:** | Level | Description | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `trace` | Raw console output lines (ANSI-stripped). High volume, includes traffic lines, phase banners, progress feedback. Buffered and batch-inserted every 2 seconds. | | `info` | Structured lifecycle events: phase start/complete/skip, config snapshots, scan start/finish. | | `warn` | Non-fatal issues: phase deduplication failures, heuristics warnings. | | `error` | Phase failures, panic recovery, critical errors. | **Error responses:** | Code | Condition | | ---- | -------------------- | | 400 | Missing UUID | | 404 | Scan not found | | 503 | Database unavailable | *** ## Selective Record Scan ### POST /api/scan-records, Scan Specific HTTP Records Starts an asynchronous scan on specific HTTP records identified by UUID. Returns `202 Accepted` on success or `409 Conflict` if a scan is already running. Only one scan can run at a time. **Request body:** | Field | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------- | | `record_uuids` | string\[] | Yes | UUIDs of HTTP records to scan (max **10,000**) | | `enable_modules` | string\[] | No | Restrict scan to specific module IDs | **Fixed in v0.4.2:** record UUIDs were validated **unscoped**, so a leaked UUID could pull another project's traffic into a scan. They are now resolved within the requesting project only. The endpoint also caps `record_uuids` at 10,000 and checks scan admission *before* enumerating them, so a rejected request costs nothing. ```bash theme={null} # Scan specific records curl -s -X POST http://localhost:9002/api/scan-records \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "record_uuids": ["abc-123", "def-456", "ghi-789"] }' | jq . # Scan with specific modules curl -s -X POST http://localhost:9002/api/scan-records \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "record_uuids": ["abc-123"], "enable_modules": ["xss-scanner", "sqli-error-based"] }' | jq . ``` **Response (202):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "selective scan started", "records_to_scan": 3 } ``` **Error responses:** | Code | Condition | | ---- | ------------------------------------------------ | | 400 | Missing `record_uuids` or no valid records found | | 409 | A scan is already running | | 503 | Database unavailable | Use `GET /api/scan/status` or `GET /api/scans/:uuid` to check scan progress. *** ### POST /api/scan-all-records, Scan All DB Records Scans existing HTTP records from the database with optional filtering. Equivalent to the DB-record scan mode, but as a dedicated route with rich filtering options. Returns `202 Accepted` on success, `200 OK` for dry runs, or `409 Conflict` if a scan is already running. **Request body:** | Field | Type | Required | Description | | ----------------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `hostname` | string | No | Hostname filter (supports `*` wildcards, e.g. `*.example.com`) | | `methods` | string\[] | No | HTTP methods filter (e.g. `["GET", "POST"]`) | | `path` | string | No | Path filter (supports `*` wildcards, e.g. `/api/*`) | | `status_codes` | int\[] | No | Status code filter (e.g. `[200, 301]`) | | `source` | string | No | Record source filter (e.g. `ingest-server`, `scanner`) | | `search` | string | No | Search across URL and path | | `min_risk_score` | int | No | Minimum risk score filter | | `remark` | string | No | Remark substring filter | | `force` | bool | No | Force full rescan (scan mode `full` vs `incremental`) | | `dry_run` | bool | No | Count matching records without launching the scan | | `modules` | string\[] | No | Module IDs with fuzzy match (like `-m`) | | `module_tags` | string\[] | No | Filter modules by tag (like `--module-tag`) | | `concurrency` | int | No | Number of parallel workers | | `timeout` | string | No | Request timeout as Go duration (e.g. `"30s"`) | | `max_per_host` | int | No | Maximum concurrent requests per host | | `rate_limit` | int | No | Maximum request submissions per second | | `scanning_max_duration` | string | No | Global max scan duration as Go duration. Per-phase durations are derived using `duration_factor` (see [phase duration factors](#post-apiscanrun--run-target-scan) above). | | `heuristics_check` | string | No | Heuristics check level: `none`, `basic`, `advanced` | | `headers` | map\[string]string | No | Custom HTTP headers included in all requests | | `scanning_profile` | string | No | Scanning profile name or path | ```bash theme={null} # Scan all records (no filters) curl -s -X POST http://localhost:9002/api/scan-all-records \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{}' | jq . # Scan only records matching a hostname curl -s -X POST http://localhost:9002/api/scan-all-records \ -H "Content-Type: application/json" \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "hostname": "*.example.com", "modules": ["xss-scanner", "sqli-error-based"] }' | jq . # Scan POST requests with high risk score curl -s -X POST http://localhost:9002/api/scan-all-records \ -H "Content-Type: application/json" \ -d '{ "methods": ["POST", "PUT"], "min_risk_score": 5, "force": true }' | jq . # Dry run — count matching records without scanning curl -s -X POST http://localhost:9002/api/scan-all-records \ -H "Content-Type: application/json" \ -d '{ "hostname": "api.example.com", "dry_run": true }' | jq . ``` **Response (202):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "running", "message": "all-records scan started", "records_to_scan": 142, "scan_mode": "full" } ``` **Response, dry run (200):** ```json theme={null} { "project_uuid": "my-project-uuid", "scan_uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "dry_run", "message": "scan record created (dry run)", "records_to_scan": 142, "scan_mode": "incremental" } ``` **Error responses:** | Code | Condition | | ---- | ------------------------------------------- | | 400 | No records match filters, or invalid params | | 409 | A scan is already running | | 503 | Database unavailable | *** ## Source Code Upload Source code uploads for AI-driven analysis (swarm, autopilot, audit) are handled via the cloud-storage API. See [Storage API](/api-references/storage): * `POST /api/storage/upload-source`: upload a source archive (zip, tar.gz, tar.bz2, tar.xz) * `GET /api/storage/source/:key`: download a previously-uploaded archive * `POST /api/storage/presign`: get a presigned URL for direct client-side upload The returned `storage_url` (e.g. `gs:///ugc/source.tar.gz`) can be passed to `--source` (CLI) or the `source` field on `POST /api/agent/run/{query,autopilot,swarm,audit}`. # Vigolium API Reference, Scope Source: https://docs.vigolium.com/api-references/scope View and update the scope configuration that controls which HTTP records are in scope for scanning. ## GET /api/scope, View Scope Config Returns the current scope configuration that controls which HTTP records are in scope for scanning. ```bash theme={null} curl -s http://localhost:9002/api/scope | jq . ``` ```json theme={null} { "host": { "include": ["*"], "exclude": [] }, "path": { "include": ["*"], "exclude": [] }, "status_code": { "include": ["*"], "exclude": [] }, "request_content_type": { "include": ["*"], "exclude": [] }, "response_content_type": { "include": ["*"], "exclude": [] }, "request_string": { "include": [], "exclude": [] }, "response_string": { "include": [], "exclude": [] } } ``` *** ## POST /api/scope, Update Scope Config Partially updates the scope configuration. Only provided fields are overwritten; omitted fields keep their current values. Changes are persisted to the config file on disk. **Scope rules:** Each scope rule has `include` and `exclude` lists. Exclude takes priority over include. Patterns support `*` wildcards. ```bash theme={null} # Exclude internal hosts curl -s -X POST http://localhost:9002/api/scope \ -H "Content-Type: application/json" \ -d '{ "host": { "exclude": ["*.internal.com", "localhost"] } }' | jq . # Exclude specific status codes curl -s -X POST http://localhost:9002/api/scope \ -H "Content-Type: application/json" \ -d '{ "status_code": { "exclude": ["404", "500"] } }' | jq . # Restrict scanning to specific hosts curl -s -X POST http://localhost:9002/api/scope \ -H "Content-Type: application/json" \ -d '{ "host": { "include": ["*.example.com", "api.target.io"], "exclude": ["cdn.example.com"] } }' | jq . # Exclude static assets by path curl -s -X POST http://localhost:9002/api/scope \ -H "Content-Type: application/json" \ -d '{ "path": { "exclude": ["*.css", "*.js", "*.png", "*.jpg", "*.svg", "*.woff*"] } }' | jq . ``` **Response:** ```json theme={null} { "message": "Scope updated successfully", "scope": { "host": { "include": ["*"], "exclude": ["*.internal.com", "localhost"] }, "path": { "include": ["*"], "exclude": [] }, "status_code": { "include": ["*"], "exclude": [] }, "request_content_type": { "include": ["*"], "exclude": [] }, "response_content_type": { "include": ["*"], "exclude": [] }, "request_string": { "include": [], "exclude": [] }, "response_string": { "include": [], "exclude": [] } } } ``` # Vigolium API Reference, Stats Source: https://docs.vigolium.com/api-references/stats Retrieve aggregated statistics about HTTP records, scanner modules, and findings. ## GET /api/stats, Scan Statistics Returns aggregated statistics about HTTP records, modules, and findings. ```bash theme={null} curl -s http://localhost:9002/api/stats | jq . ``` ```json theme={null} { "http_records": { "total": 1234 }, "modules": { "active": { "total": 18, "enabled": 15 }, "passive": { "total": 4, "enabled": 4 } }, "findings": { "total": 42, "by_severity": { "critical": 2, "high": 10, "medium": 15, "low": 10, "info": 5 } } } ``` # Vigolium API Reference, Cloud Storage Source: https://docs.vigolium.com/api-references/storage Cloud object storage integration for source code upload/download and scan result archival via S3-compatible APIs (GCS, AWS S3, MinIO). ## Overview The storage API provides cloud object storage integration for source code upload/download and scan result archival. All objects are scoped to a project via the `X-Project-UUID` header and stored under `//`. Storage is **disabled by default**. Enable it by setting `storage.enabled: true` in `vigolium-configs.yaml`. All endpoints return `503 Service Unavailable` when storage is not configured. | Endpoint | Method | Role | Description | | --------------------------------- | ------ | -------- | -------------------------------------- | | `/api/storage/upload-source` | POST | Operator | Upload source code archive | | `/api/storage/source/:key` | GET | Viewer | Download source code by key | | `/api/storage/results/:scan-uuid` | GET | Viewer | Download scan result bundle (.tar.gz) | | `/api/storage/presign` | POST | Operator | Generate presigned upload/download URL | *** ## Security All storage operations enforce project-level isolation: * Every object path is prefixed with the authenticated project UUID (`//`). The project UUID comes from the `X-Project-UUID` header, set by server middleware, clients cannot override the prefix. * Keys and UUIDs are validated against path traversal (`../`, `..\\`, `..`) before any storage operation. Malicious keys are rejected with `400 Bad Request`. * Presigned URLs are scoped to the requesting project, a presigned URL for project A cannot access objects in project B. *** ## GCP Setup Vigolium uses S3-compatible HMAC keys to talk to GCS. You need to create HMAC credentials from a service account, then configure them in `vigolium-configs.yaml`. ### Step 1: Create HMAC Keys from a Service Account If you have a service account JSON key (e.g. `gcs-readwrite-key.json`), activate it and create HMAC credentials: ```bash theme={null} # Authenticate with the service account gcloud auth activate-service-account --key-file=/path/to/gcs-readwrite-key.json # Get the service account email from the key file SA_EMAIL=$(jq -r '.client_email' /path/to/gcs-readwrite-key.json) # Create HMAC keys for the service account gcloud storage hmac create "$SA_EMAIL" ``` This outputs an `accessId` and `secret`. Save both, the secret is only shown once. ### Step 2: Create a GCS Bucket ```bash theme={null} # Create a bucket in your preferred region gcloud storage buckets create gs://my-vigolium-bucket \ --location=asia-southeast1 \ --uniform-bucket-level-access # Grant the service account read/write access gcloud storage buckets add-iam-policy-binding gs://my-vigolium-bucket \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/storage.objectAdmin" ``` ### Step 3: Configure Vigolium Set the credentials and bucket name as environment variables: ```bash theme={null} export VIGOLIUM_STORAGE_ACCESS_KEY="GOOG1E..." # accessId from step 1 export VIGOLIUM_STORAGE_SECRET_KEY="abc123..." # secret from step 1 export VIGOLIUM_STORAGE_BUCKET_NAME="my-vigolium-bucket" ``` Then configure `~/.vigolium/vigolium-configs.yaml`: ```yaml theme={null} storage: enabled: true driver: gcs bucket: ${VIGOLIUM_STORAGE_BUCKET_NAME} region: asia-southeast1 access_key: ${VIGOLIUM_STORAGE_ACCESS_KEY} secret_key: ${VIGOLIUM_STORAGE_SECRET_KEY} use_ssl: true ``` ### Step 4: Verify Connectivity ```bash theme={null} # Upload a test file curl -s -X POST http://localhost:9002/api/storage/upload-source \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -F "file=@test-source.tar.gz" | jq . # Generate a presigned download URL curl -s -X POST http://localhost:9002/api/storage/presign \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -d '{"key": "ugc/test-source.tar.gz", "method": "GET"}' | jq .url ``` ### AWS S3 / MinIO For AWS S3, change the driver and region: ```yaml theme={null} storage: enabled: true driver: s3 bucket: ${VIGOLIUM_STORAGE_BUCKET_NAME} region: us-east-1 access_key: ${AWS_ACCESS_KEY_ID} secret_key: ${AWS_SECRET_ACCESS_KEY} ``` For self-hosted MinIO, set the endpoint explicitly: ```yaml theme={null} storage: enabled: true driver: minio endpoint: minio.internal:9000 bucket: ${VIGOLIUM_STORAGE_BUCKET_NAME} access_key: ${MINIO_ACCESS_KEY} secret_key: ${MINIO_SECRET_KEY} use_ssl: false path_style: true ``` *** ## Storage Object Layout All objects are prefixed with the project UUID for multi-tenant isolation: ``` / / ugc/ # User-uploaded source code source-code.tar.gz my-app.zip native-scans/ # Native scan result bundles / results.tar.gz # Bundled findings JSONL, HTML report, stats agentic-scans/ # Agentic scan result bundles / results.tar.gz # Bundled session dir (output.md, extensions/, plan.json) ``` Result bundles use `.tar.gz` format (gzip-compressed tar), matching the `vigolium db export --format bundle` format. *** ## CLI Usage ### Source Download from Storage Use `gs://` URIs with `--source` to download and extract source code from cloud storage before scanning: ```bash theme={null} # Native scan with source from GCS vigolium scan -t https://example.com \ --source gs:///ugc/source-code.tar.gz # Agentic swarm with source from GCS vigolium agent swarm -t https://example.com \ --source gs:///ugc/source-code.tar.gz # Autopilot with source from GCS vigolium agent autopilot -t https://example.com \ --source gs:///ugc/source-code.tar.gz ``` The archive is downloaded, extracted to a temp directory, and cleaned up after the scan completes. The `source_type` field in the DB records `"gcs"`. ### Result Upload to Storage Add `--upload-results` to upload scan results to cloud storage after completion: ```bash theme={null} # Native scan — upload findings JSONL + HTML report as tar.gz bundle vigolium scan -t https://example.com -o results --format jsonl,html --upload-results # Agentic swarm — upload session dir as tar.gz bundle vigolium agent swarm -t https://example.com --upload-results # Autopilot — upload session artifacts as tar.gz bundle vigolium agent autopilot -t https://example.com --source ./src --upload-results ``` Results are uploaded to: * **Native scans:** `gs:///native-scans//results.tar.gz` * **Agentic scans:** `gs:///agentic-scans//results.tar.gz` The `storage_url` field on the Scan / AgenticScan DB record is updated with the `gs://` URL after upload. ### Managing objects with `vigolium storage` The `storage` subcommands operate on the active project's bucket prefix: ```bash theme={null} vigolium storage ls # list objects for the active project vigolium storage ls --prefix ugc/ --tree # limit to a key prefix, render as a tree vigolium storage upload ./source.tar.gz # → ugc/source.tar.gz vigolium storage upload ./report.html --key reports/latest.html --content-type text/html vigolium storage download ugc/source.tar.gz -o ./source.tar.gz vigolium storage results # fetch a scan's result bundle vigolium storage presign --key ugc/source.tar.gz --method PUT --expiry 24h vigolium storage rm ugc/old.tar.gz ugc/older.tar.gz ``` | Flag | Command | What it does | | --------------------- | --------------------- | ------------------------------------------------------------------------- | | `--prefix ` | `ls` | Limit results to keys under this prefix | | `--tree` | `ls` | Render objects as a directory tree instead of a flat list | | `--key ` | `upload`, `presign` | Object key (`upload` defaults to `ugc/`; required on `presign`) | | `--content-type ` | `upload` | Content-Type to set on the uploaded object | | `--method GET\|PUT` | `presign` | Direction of the presigned URL (default `GET`) | | `--expiry ` | `presign` | URL validity — `30m`, `1h`, `24h` (default `1h`) | | `-o, --output ` | `download`, `results` | Write to this file instead of stdout / the default name | | `--json` | `ls`, `presign` | Machine-readable output | `vigolium import` can also read straight from storage (`vigolium import gs:///imports/scan.tar.gz`), and `--upload-key` overrides where an `import --upload` lands (default `imports/-.`). *** ## POST /api/storage/upload-source Uploads a source code archive to cloud storage, scoped to the project. **Content-Type:** `multipart/form-data` | Field | Type | Required | Description | | ------ | ---- | -------- | ----------------------------- | | `file` | file | Yes | Source code archive to upload | ```bash theme={null} curl -s -X POST http://localhost:9002/api/storage/upload-source \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -F "file=@source-code.tar.gz" | jq . ``` **Response (200):** ```json theme={null} { "storage_url": "gs://my-project-uuid/ugc/source-code.tar.gz", "key": "ugc/source-code.tar.gz", "filename": "source-code.tar.gz", "size": 1048576, "message": "source uploaded successfully" } ``` The returned `storage_url` can be passed directly to `--source` (CLI) or the `source` field (API). *** ## GET /api/storage/source/:key Downloads a previously uploaded source file. ```bash theme={null} curl -s -o source.tar.gz \ http://localhost:9002/api/storage/source/source-code.tar.gz \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" ``` Returns the file as `application/octet-stream` with `Content-Disposition: attachment`. Returns `400` if the key contains path traversal sequences. *** ## GET /api/storage/results/:scan-uuid Downloads the result bundle for a native scan or agentic scan. Searches `native-scans//results.tar.gz` first, then `agentic-scans//results.tar.gz`. ```bash theme={null} curl -s -o results.tar.gz \ http://localhost:9002/api/storage/results/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" ``` Returns `application/gzip` with `Content-Disposition: attachment`. Returns `404` if no results have been uploaded for the given UUID. Extract the bundle: ```bash theme={null} tar xzf results.tar.gz ``` *** ## POST /api/storage/presign Generates a presigned URL for direct upload or download, bypassing the API server. Useful for large files or client-side uploads. **Request body:** | Field | Type | Required | Description | | ---------------- | ------ | -------- | ------------------------------------------------ | | `key` | string | Yes | Object key (e.g. `ugc/source-code.tar.gz`) | | `method` | string | No | `GET` (default) or `PUT` | | `expiry_seconds` | int | No | URL expiry in seconds (default: `3600` / 1 hour) | ```bash theme={null} # Generate a download URL curl -s -X POST http://localhost:9002/api/storage/presign \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "key": "ugc/source-code.tar.gz", "method": "GET", "expiry_seconds": 3600 }' | jq . # Generate an upload URL (for client-side direct upload) curl -s -X POST http://localhost:9002/api/storage/presign \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "key": "ugc/my-app.tar.gz", "method": "PUT" }' | jq . ``` **Response (200):** ```json theme={null} { "url": "https://storage.googleapis.com/my-bucket/my-project-uuid/ugc/source-code.tar.gz?X-Goog-Algorithm=...", "key": "ugc/source-code.tar.gz", "method": "GET", "expiry_seconds": 3600 } ``` Keys are validated against path traversal, requests with `../` or similar sequences are rejected with `400`. *** ## Using Storage with Agentic Scans (API) ### Upload Source, Then Run Agentic Scan ```bash theme={null} # 1. Upload source code STORAGE_URL=$(curl -s -X POST http://localhost:9002/api/storage/upload-source \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -F "file=@my-app.tar.gz" | jq -r '.storage_url') echo "Uploaded to: $STORAGE_URL" # 2. Run swarm with uploaded source + result upload curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -d "{ \"input\": \"https://example.com/api/users?id=1\", \"source\": \"$STORAGE_URL\", \"upload_results\": true, \"triage\": true }" | jq . # 3. After scan completes, download and extract results curl -s -o results.tar.gz \ http://localhost:9002/api/storage/results/ \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" tar xzf results.tar.gz ``` ### Run Autopilot with Local Source + Upload Results ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/autopilot \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "target": "http://localhost:3000", "source": "/home/user/src/my-app", "upload_results": true, "intensity": "balanced" }' | jq . ``` ### Run Swarm with GCS Source ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -H "X-Project-UUID: my-project-uuid" \ -d '{ "input": "https://example.com", "source": "gs://my-project-uuid/ugc/source-code.tar.gz", "upload_results": true, "code_audit": true, "triage": true }' | jq . ``` *** ## Storage URL in Scan Records When `upload_results` is enabled, the `storage_url` field is populated on the Scan or AgenticScan record after upload completes. **Native scan:** ```bash theme={null} curl -s http://localhost:9002/api/scans/ \ -H "Authorization: Bearer " | jq '.storage_url' # "gs://my-project-uuid/native-scans//results.tar.gz" ``` **Agentic scan:** ```bash theme={null} curl -s http://localhost:9002/api/agent/sessions/ \ -H "Authorization: Bearer " | jq '.storage_url' # "gs://my-project-uuid/agentic-scans//results.tar.gz" ``` # Agentic Scan Architecture Source: https://docs.vigolium.com/architecture/agentic-scan How agent mode works, the in-process olium engine, prompt orchestration, the swarm/autopilot pipelines, and the provider model. Architecture series: [Overview](/architecture/overview) · [Native Scan](/architecture/native-scan) · **Agentic Scan** · [Data & Storage](/architecture/data-and-storage) · [Server & API](/architecture/server-and-api) Vigolium's agent mode runs AI-driven security scans on top of the native scanner. This document explains the architecture, the moving parts, and the flow of a typical agent run. **Recent shift:** the previous subprocess-based SDK backends (`claudesdk`, `codexsdk`) have been removed. All AI dispatch now goes through an in-process Go runtime called **olium** (`pkg/olium/`). One unified provider interface, one conversation state, one place to reason about timeouts and retries. *** ## 1. Subcommand surface `vigolium agent` is a parent command with informational flags only (`--list-templates`, `--list-agents`). Real work happens in subcommands. | Subcommand | Purpose | Key file | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `query` | Single-shot prompt (template or inline). Code review, secret hunt, endpoint discovery. | `pkg/cli/agent.go` | | `autopilot` | Agentic scan: autonomous operator with full tool access. | `pkg/cli/agent_autopilot.go` | | `swarm` | Agentic scan: 10-phase guided pipeline (plan → extension → scan → triage). | `pkg/cli/agent_swarm.go` | | `triage` | Confirm or downgrade one existing finding with the AI triager. | `pkg/cli/agent_triage.go` | | `audit` | Unified driver dispatcher for source-code audit. `--driver auto\|both\|audit\|piolium` drives the embedded vigolium-audit harness, the standalone piolium harness, or both. | `pkg/cli/agent_audit.go` | | `olium` | Interactive olium TUI or headless prompt. | `pkg/cli/agent_olium.go` | | `session` | List or inspect past agent runs (sessions list / detail view). | `pkg/cli/agent_session.go` | `query` is the only mode that does **not** orchestrate a scan, it's a one-shot prompt with optional source-code context. `autopilot` and `swarm` are the two **agentic scan** modes. *** ## 2. Architecture layers ``` ┌─────────────────────────────────────────────────────────────────┐ │ CLI pkg/cli/agent_*.go │ │ query · autopilot · swarm · triage · audit · olium · session │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Orchestrators pkg/agent/ │ │ │ │ SwarmRunner swarm.go + swarm_pipeline.go │ │ normalize → auth → source-analysis → code-audit → │ │ discovery → plan → extension → scan → triage → finalize │ │ │ │ AutopilotPipelineRunner autopilot_pipeline.go │ │ audit (optional, foreground) → autonomous operator │ │ │ │ Audit dispatcher audit_drivers.go + audit_chain.go │ │ driver=auto|both|audit|piolium · per-driver child rows │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Engine pkg/agent/engine.go │ │ Preflight → buildPrompt → enrichContext → run on olium → │ │ parse → ingest (findings / http_records / plans / triage) │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Olium runtime pkg/olium/{engine,tool,skill} │ │ Multi-turn agent loop · tool registry · event stream │ └───────────────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────────────▼─────────────────────────────────┐ │ Providers pkg/olium/provider/ │ │ anthropic (API / OAuth / CLI / Vertex) · openai (API / │ │ codex-OAuth) · google-vertex · openai-compatible (HTTP) │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.1 Engine (`pkg/agent/engine.go`) The engine is the seam between orchestrators and the olium runtime. Its job is to: 1. **Preflight**: validate provider/model selection (`Preflight(agentName)`). 2. **Build the prompt**: load template, parse frontmatter, render with `TemplateData`. 3. **Enrich context**: pull DB context (previous findings, discovered endpoints, high-risk endpoints, module list, scan stats) through a thread-safe LRU cache (`agentprompt.ContextCache`) that lives for one swarm/autopilot run. 4. **Dispatch**: call `runOliumOnEngineWithThinking()` against an olium engine instance. 5. **Retry**: exponential backoff on transient errors (`retry.go`, default 2 retries, 2-30s backoff) with jitter. 6. **Parse**: schema-aware JSON extraction tolerating fences, prose, and type coercion (string ↔ int, object ↔ string body). 7. **Ingest**: write parsed findings/HTTP records to the DB repository. Key entry points: * `Engine.Run(ctx, opts)`: one-shot prompt execution; creates a fresh olium engine. * `Engine.RunOnOliumEngine(ctx, opts, eng)`: runs against a **shared engine instance**, preserving conversation prefix for prompt-cache hits across phases. * `Engine.RunSourceAnalysisParallel(ctx, cfg)`: fan-out source analysis (single explore call → parallel format/extension sub-calls on the same engine). A global semaphore caps in-flight provider calls; a positive `agent.olium.max_concurrent` is the cap, `0`/unset resolves to the default of 4, and a negative value disables the cap. ### 2.2 Olium runtime (`pkg/olium/`) Native, in-process replacement for the old SDK pool. * **`pkg/olium/engine`**: `Engine.Run(ctx, prompt) <-chan Event` returns a stream of events: `EventTextDelta`, `EventThinkingDelta`, `EventToolCall`, `EventTurnDone` (with token usage), `EventError`. Conversation state (system prompt, tool definitions, prior turns) lives on the engine and is reused across calls when phases share an engine. * **`pkg/olium/tool`**: registry of built-in tools (bash, file ops, grep, fetch, …) registered via `tool.NewRegistry()` + `tool.RegisterBuiltins()`. Autopilot exposes the full set; swarm uses a smaller set per phase. * **`pkg/olium/skill`**: optional skill files (Markdown SKILL.md packages) that augment the system prompt; loaded from embedded assets and `~/.vigolium/skills/`. * **`pkg/olium/provider`**: provider dispatch: | Provider | Auth source | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `openai-codex-oauth` | `oauth_cred_path` (JSON from `codex login`) | | `anthropic-api-key` | `llm_api_key` or `$ANTHROPIC_API_KEY` | | `anthropic-oauth` | `oauth_token` (from `claude setup-token`); falls back to `$ANTHROPIC_API_KEY` | | `openai-api-key` | `llm_api_key` or `$OPENAI_API_KEY` | | `openai-responses` | `llm_api_key` or `$OPENAI_API_KEY` (public OpenAI Responses API, `/v1/responses`) | | `anthropic-cli` | shells out to the `claude` binary (alias: `anthropic-claude-cli`) | | `anthropic-claude-sdk-bridge` | logged-in Claude Code subscription via the `vigolium-audit bridge` sidecar; `bridge_binary` / `--bridge-bin` (embedded blob, then `PATH`) | | `anthropic-compatible` | `custom_provider.base_url` (Anthropic Messages `/v1/messages`), `custom_provider.api_key`, `custom_provider.model_id`, `custom_provider.extra_headers` | | `anthropic-vertex` | `oauth_cred_path` (GCP SA JSON, or `$GOOGLE_APPLICATION_CREDENTIALS`) + `google_cloud_project` / `google_cloud_location` | | `google-vertex` | same GCP creds; routes `gemini-*` models | | `openai-compatible` | `custom_provider.base_url` (required), `custom_provider.api_key` (optional), `custom_provider.model_id`, `custom_provider.extra_headers` | Configured in `agent.olium` of `vigolium-configs.yaml`. Per-call deadline defaults to 10m (`call_timeout_sec`). The `openai-compatible` provider speaks the OpenAI Chat Completions wire format and works with any backend that does too, Ollama, OpenRouter, LM Studio, vLLM, Together, Groq, LocalAI, custom proxies. Set `base_url` to the endpoint (`/v1` root or full `/v1/chat/completions` URL, both work); leave `api_key` empty for unauthenticated local servers. `extra_headers` are applied after the standard headers, so they can override `Authorization` for backends that use a non-Bearer scheme. **Tool-calling caveat.** OpenAI-style function tools are supported by the wire format but not by every model. Local instruction-tuned models that work well: `qwen2.5-coder`, `llama3.1` instruct, `mistral-nemo`. Smaller models often silently ignore tool definitions and reply in prose. If the agent never calls tools, that's the likely cause, switch to a tool-trained model. ### 2.3 Prompt templates Markdown files with YAML frontmatter, loaded from (in order): 1. `agent.templates_dir` (config dir) 2. `~/.vigolium/prompts/` 3. Embedded (`public/presets/prompts/` baked into the binary) Frontmatter declares the **output schema** the agent is expected to produce: | Schema | Used by | Parsed into | | ---------------------------- | ----------------------------- | ------------------------ | | `findings` | code review, triage, audit | `[]AgentFinding` → DB | | `http_records` | endpoint discovery | `[]AgentHTTPRecord` → DB | | `source_analysis` | swarm source-analysis phase | `SourceAnalysisResult` | | `attack_plan` / `swarm_plan` | swarm plan + extension phases | `SwarmPlan` | | `triage_result` | swarm triage phase | `TriageResult` | Templates render against `TemplateData` (`pkg/agent/agenttypes/types.go`), which carries: source code snippets, directory tree, target URL, hostname, previous findings (DB), discovered endpoints (DB), module list/tags, scan stats, and a free-form `Extra` map for orchestrator-injected hints. *** ## 3. Swarm pipeline `vigolium agent swarm --target ... [--source ...]` runs a state-machine pipeline. Each step implements `swarmPhaseStep.Run(ctx, *swarmPipelineState)`. ``` ┌────────────────────────────────────────────────────┐ │ agent swarm │ └────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────┐ │ native-normalize │ parse curl/raw HTTP/Burp/URL → records └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ auth (optional) │ browser-based login (--browser-auth; browser always on) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ source-analysis (AI)│ if --source: parallel explore + format │ │ emits routes, session-config, source extensions └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ code-audit (AI) │ optional code-level audit (--code-audit) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ native-discover │ optional crawl/spidering (--discover or deep) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ plan (AI) │ master agent picks modules + extensions │ │ batched (5 records/batch) for large input sets └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ native-extension │ compile/validate JS extensions (Sobek) │ │ LLM repair on syntax errors (max 5 in parallel) └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ native-scan │ ScanFunc: native modules + custom extensions └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ triage (AI) ────────┼──→ rescan? loop up to MaxIterations └──────────┬──────────┘ (re-runs native-scan with targeted modules) ▼ ┌─────────────────────┐ │ finalize │ aggregate results, token usage, DB update └─────────────────────┘ ``` Phase names prefixed `native-` are pure-Go (no LLM). The pipeline is gated by: * `--only` / `--skip` / `--start-from` flags (with legacy aliases via `NormalizeSwarmPhase`) * intensity preset (`SwarmPresets[Quick|Balanced|Deep]` in `agenttypes/constants.go`) * `cfg.SourcePath`: empty source skips source-analysis and code-audit * `cfg.Discover`, `cfg.CodeAudit`, `cfg.Triage` toggles * checkpoint state for observability; `--start-from` creates a new run with earlier phases marked complete A parallel **vigolium-audit** subprocess can run in the background (`cfg.Audit != ""`) when source is provided, contributing source-code audit findings without blocking the swarm. Swarm uses the embedded vigolium-audit harness directly; the multi-driver `agent audit` command layers piolium support on top. ### Plan & extension phases The master agent receives input records (chunked into `MasterBatchSize`, default 5) and returns a `SwarmPlan`: ```go theme={null} SwarmPlan { ModuleTags / ModuleIDs // which native modules to enable Extensions []GeneratedExtension // custom JS extensions (full source) QuickChecks []QuickCheck // shorthand → expanded JS by extensions/quickcheck_gen.go FocusAreas / Snippets / Hints // free-form guidance } ``` The extension phase compiles every JS extension through the Sobek engine. Syntax errors trigger `RepairExtensionsWithLLM()`, which fans out repair calls (max 5 parallel). Surviving extensions are written to `/extensions/` with sanitized filenames; an `extensionRenames` map preserves original→renamed mapping for downstream result attribution. ### Triage loop After the native scan, if findings exist and triage is enabled, the triage agent receives a fixture (truncated by detail tiers, 15 full-detail / 40 table-with-top-10 / etc.) and emits: ```go theme={null} TriageResult { Confirmed []Finding FalsePositive []Finding FollowUpScans []FollowUpScan // optional rescans (modules + URLs) } ``` If `FollowUpScans` is non-empty and rescan is enabled, the pipeline loops back to `native-scan` with targeted modules. Loop bounded by `MaxIterations` (default 3); early-exits when all findings have "certain" confidence. *** ## 4. Autopilot pipeline `vigolium agent autopilot --target ... [--source ...]` is simpler, no plan/extension phases. The agent itself decides what to run. ``` ┌─────────────────────────────────────────────┐ │ vigolium-audit (optional, foreground) │ │ if --source: run vigolium-audit, freeze │ │ findings into vigolium-audit/ directory │ └─────────────────┬───────────────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ Context preparation │ │ - load frozen vigolium-audit findings │ │ - import Burp + summarize prior DB data │ │ - distill/index --knowledge-base docs │ │ - prepareAutopilotAuth → AuthHeaders │ │ - buildAutopilotContextBundle │ │ (routes, auth flows, browser decision) │ │ - buildAutopilotPlan │ │ (budgets, tasks, stop criteria) │ └─────────────────┬───────────────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ Autonomous operator (AI) │ │ olium engine with full tool access: │ │ Bash, Read, Grep, Glob, Edit, Write, │ │ vigolium scan-url, vigolium finding, │ │ vigolium traffic, etc. │ │ bounded by MaxCommands + Timeout │ │ legacy: growing conversation │ │ shadow/enforced: rotating durable sections│ └─────────────────┬───────────────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ Verification │ │ durable candidates → fresh-context grade │ │ enforced: promote confirmed candidates │ └─────────────────────────────────────────────┘ ``` The default `agent.olium.autopilot_mode: legacy` preserves direct `report_finding` behavior. `shadow` rotates context while mirroring candidates; `enforced` replaces direct reports with `propose_candidate` and promotes only verifier-confirmed claims. Durable runs can continue with `autopilot --resume `. ### Intensity presets (autopilot) | Intensity | MaxCommands | Timeout | Vigolium-audit mode | Browser | | --------- | ----------- | ------- | ------------------- | ------- | | quick | 150 | 1h | lite | on | | balanced | 500 | 6h | balanced | on | | deep | 1500 | 12h | deep | on | *** ## 5. Session directories Every swarm and autopilot run writes a session dir under `agent.sessions_dir` (default `~/.vigolium/agent-sessions//`). Layout: ``` / ├── checkpoint.json # swarm: completed phases, record stats, last triage round ├── swarm-plan.json # serialized SwarmPlan ├── session-config.json # auth session definitions (login flows, token rules) ├── extensions/ # compiled JS extensions (sanitized filenames) ├── vigolium-audit/ # vigolium-audit subprocess output (audit-state.json + findings) ├── piolium/ # piolium output (when the audit dispatcher runs it) ├── audit-stream.jsonl # `agent audit` NDJSON stream (when audit driver runs) ├── master-output.md # master-agent output (debug) ├── source-analysis-output.md ├── code-audit-output.md ├── runtime.log # human-readable runtime/tool log ├── transcript.jsonl # autopilot: conversation + section events ├── transcript-*.jsonl # swarm/query: per-phase conversations ├── transcript-verify-*.jsonl # durable candidate verifier transcripts ├── autopilot/scratchpad.json # durable plan, stop criteria, and notes ├── knowledge-base-brief.md # when --knowledge-base is supplied ├── tool-results/ # spilled large tool outputs ├── output.txt # query: raw agent output ├── inputs.json # normalized input records └── skills/ # copied embedded skills (vigolium-scanner) ``` `EnsureSessionDir(baseDir, agenticScanUUID)` in `pipeline_types.go` is the canonical creator. *** ## 6. Configuration All agent settings live under `agent` in `vigolium-configs.yaml`: ```yaml theme={null} agent: default_agent: olium templates_dir: ~/.vigolium/prompts sessions_dir: ~/.vigolium/agent-sessions context_limits: max_findings: 50 max_endpoints: 100 max_high_risk: 20 min_risk_score: 50 olium: provider: openai-codex-oauth # or anthropic-api-key | openai-api-key | anthropic-oauth | anthropic-cli | anthropic-vertex | google-vertex | openai-compatible model: gpt-5.5 # provider default if empty oauth_cred_path: ~/.codex/auth.json llm_api_key: ${ANTHROPIC_API_KEY} reasoning_effort: medium max_tokens: 1000000 max_turns: 32 max_concurrent: 4 # 0/unset = default 4; negative = unbounded autopilot_mode: legacy # legacy | shadow | enforced call_timeout_sec: 600 # per-call deadline; -1 = no timeout custom_provider: # only used when provider == openai-compatible base_url: http://localhost:11434/v1 # Ollama default; OpenRouter / LM Studio / vLLM also work model_id: gemma4:latest # fallback for olium.model and --model api_key: "" # optional; empty = no Authorization header extra_headers: # optional; applied after standard headers # X-Provider: custom audit: # vigolium-audit / piolium harness defaults — see internal/config/agent.go AuditAgentConfig # … browser: # optional agent-browser integration ``` CLI flags (`--provider`, `--model`, `--oauth-token`, `--llm-api-key`, `--base-url`) override the config at runtime. *** ## 7. Where things live | What | Where | | ------------------------------ | ---------------------------------------------- | | Subcommand wiring | `pkg/cli/agent*.go` | | Swarm orchestrator | `pkg/agent/swarm.go`, `swarm_pipeline.go` | | Autopilot orchestrator | `pkg/agent/autopilot_pipeline.go` | | Audit driver dispatcher | `pkg/agent/audit_drivers.go`, `audit_chain.go` | | Audit / piolium runners | `pkg/agent/audit_agent.go`, `pkg/piolium/` | | Engine (prompt → dispatch) | `pkg/agent/engine.go` | | Prompt templates / rendering | `pkg/agent/prompt/`, `public/presets/prompts/` | | Output parsers (JSON-tolerant) | `pkg/agent/parsing/` | | Olium runtime | `pkg/olium/engine`, `tool`, `skill` | | Olium providers | `pkg/olium/provider/` | | Phase constants & presets | `pkg/agent/agenttypes/constants.go` | | Core types | `pkg/agent/agenttypes/types.go` | | Public aliases | `pkg/agent/aliases.go` | | Config schema | `internal/config/agent.go` | *** ## 8. Quick mental model * **Engine** turns a prompt template + DB context into a structured result. One LLM call. * **Orchestrator** sequences many engine calls plus native steps (discovery, scan), checkpoints state, and writes a session directory. * **Olium** is the agent runtime, it holds conversation state and dispatches to a provider. One olium engine can serve many engine calls cheaply (prompt cache hits). * **Swarm phases** are controlled by `--only`, `--skip`, and `--start-from`; swarm has no public `--resume` flag. * **Autopilot resume** takes an AgenticScan UUID and restores durable scratchpad/candidate state; it is not phase-based. * **Intensity** is a single knob that hydrates a bundle of toggles (commands, timeout, vigolium-audit mode, discover/audit/triage flags, browser/auth). *** ## Related Task-oriented guide to the agent subcommands. Autonomous operator mode in practice. The multi-phase AI + native pipeline. Use the agent runtime directly (TUI / headless). # Data & Persistence Architecture Source: https://docs.vigolium.com/architecture/data-and-storage How scan data is scoped (multi-tenancy), modeled (the database schema), written (the repository pattern and async writer), and moved between machines (cloud storage). Architecture series: [Overview](/architecture/overview) · [Native Scan](/architecture/native-scan) · [Agentic Scan](/architecture/agentic-scan) · **Data & Storage** · [Server & API](/architecture/server-and-api) Every scan path in Vigolium, native, agentic, ingestion, converges on the same persistence layer. This document explains how data is **scoped** (multi-tenancy), **modeled** (the database schema), **written** (the repository pattern and async writer), and **moved between machines** (cloud storage). It is the architectural companion to the task-oriented [Projects API](/api-references/projects) and [Storage API](/api-references/storage) guides; reach for those for CLI/API recipes. *** ## 1. Multi-Tenancy: the `project_uuid` spine All scan data is partitioned by **project**, a named container with a UUID, optional config overlay, and optional access-control lists. There is no separate database per project; isolation is a `project_uuid` column on every major table, filtered on every read and stamped on every write. ``` Built-in defaults → ~/.vigolium/vigolium-configs.yaml (global config) → ~/.vigolium/projects//config.yaml (per-project overlay) → --scanning-profile (scanning profile) → CLI flags (highest precedence) ``` * **Default project**: `00000000-0000-0000-0000-000000000001`, created during `vigolium init`. Used whenever no project is selected. * **Selection precedence**: `--project-uuid` > `--project-name` > `VIGOLIUM_PROJECT_UUID` > `VIGOLIUM_PROJECT_NAME` > default. On the server, the `X-Project-UUID` request header plays the same role. The legacy `VIGOLIUM_PROJECT` env var was removed in v0.4.3. * **Project config** is a partial YAML overlay (same shape as a scanning profile) at `~/.vigolium/projects//config.yaml`; only the keys it sets are overridden. * **Access control**: a project is a data boundary, not an auth boundary — the server's API key authenticates, `X-Project-UUID` selects, and any authenticated caller can address any project. Isolate engagements with separate servers or databases. `VIGOLIUM_PROJECT_READONLY=true` disables the mutating `project` CLI subcommands. ### Tables carrying `project_uuid` `scans` · `http_records` · `findings` · `scopes` · `source_repos` · `oast_interactions` · `scan_logs` Existing databases are migrated in place, the column is added with the default-project UUID as its default, so pre-multi-tenancy data lands in the default project. *** ## 2. The database backend Vigolium uses the **repository pattern over Bun ORM**, with two interchangeable backends: | Backend | Driver | Use | | ---------------- | ---------------------- | --------------------------------------------- | | SQLite (default) | `sqliteshim` → modernc | Single-binary, zero-config, local scans | | PostgreSQL | `pgdriver` | Shared/server deployments, concurrent writers | The schema is intentionally **denormalized**, there are no separate hosts or parameters tables; JSONB columns carry structured sub-data. This keeps a single `http_records` row self-contained and avoids join fan-out on the hot ingestion path. ### Core models (`pkg/database/models.go`) **`HTTPRecord`** (table `http_records`), the unit of ingested traffic: * *Identity*: `UUID` (PK), `RequestHash` (SHA-256 of the raw request, used for per-source dedup) * *Host*: `Scheme`, `Hostname`, `Port`, `IP` * *Request*: `Method`, `Path`, `URL`, `RequestHeaders` (JSONB), `RawRequest`, `RequestBody` * *Response*: `StatusCode`, `ResponseHeaders` (JSONB), `RawResponse`, `ResponseBody`, `ResponseTitle`, `ResponseWords` * *Derived*: `Parameters` (JSONB array of `EmbeddedParam`), `RiskScore`, `Remarks` * *Metadata*: `Source`, `SentAt`, `ReceivedAt`, `CreatedAt` **`Finding`** (table `findings`), a detected issue: * *Identity*: `ID` (auto-increment), `FindingHash` (unique constraint → dedup key, set from the `ResultEvent` ID) * *Module*: `ModuleID`, `ModuleName`, `Description`, `Severity`, `Confidence` * *Evidence*: `MatchedAt` (JSONB), `ExtractedResults`, `Request`, `Response`, `AdditionalEvidence` (merged-duplicate request/response pairs, capped at 10) * *Relations*: `HTTPRecordUUIDs` (JSONB), `ScanUUID`; the `finding_records` junction table is the many-to-many link to HTTP records. Agent-produced findings (autopilot/swarm/audit/piolium) flow into the **same `findings` table**, tagged by source so native and AI results coexist and dedup together. ### Converters (`pkg/database/converters.go`) The in-memory scan types never touch the DB directly. `HTTPRecord.FromHttpRequestResponse()` and `Finding.FromResultEvent()` are the only seam, they generate UUIDs, compute hashes, parse URLs, extract titles, and count words, keeping persistence concerns out of the executor and modules. *** ## 3. Write paths | Method (`pkg/database/repository.go`) | Role | | ------------------------------------------------------ | ----------------------------------------------------------------------------------- | | `SaveRecord()` / `SaveRecordsBatch()` | Single vs. bulk INSERT (batch = one transaction) | | `SaveFinding()` | `INSERT … ON CONFLICT (finding_hash) DO NOTHING` + evidence append + junction rows | | `DeduplicateFindings()` | Post-phase grouping: merge findings sharing `(module_id, severity, matched_at URL)` | | `CreateScanWithCursor()` / `CountRecordsAfterCursor()` | Cursor bookkeeping for incremental rescans | | `GetRecordsWithResponseBody()` | UUID-cursor pagination for batch scanners (e.g. Kingfisher) | | `UpdateRiskScores()` | Batched `CASE/WHEN` UPDATE, 500 UUIDs per statement | ### Async batched ingestion, `RecordWriter` High-throughput ingestion (proxy capture, bulk import, spidering) does **not** call the repository synchronously. `pkg/database/record_writer.go` fronts it with a buffered channel: ``` Write() ──► buffered chan (cap 4096) ──► single flushLoop goroutine │ batch of 128, or 50ms tick ▼ repo.SaveRecordsBatch() (one txn) ``` Each caller blocks until its row is flushed and gets a `WriteResult{UUID, Err}` back on a per-request result channel, backpressure is the channel capacity, ordering is preserved, and the DB sees large batched transactions instead of a write per request. **SQLite DSN note:** the modernc driver needs pragmas in `_pragma=name(value)` form; the `mattn`-style `_busy_timeout=` is silently ignored. Relevant when tuning concurrent-writer behavior. *** ## 4. Deduplication Two layers, by design: 1. **Per-source HTTP-record dedup**: `RequestHash` (SHA-256 of the raw request) plus `DeduplicateRecordsBySource` collapses re-ingested identical requests within a source. 2. **Finding dedup**: the `finding_hash` unique constraint prevents exact duplicates at insert time; `DeduplicateFindings()` runs after a phase to *group* near-duplicates (same module/severity/URL), folding the extra request/response pairs into the survivor's `AdditionalEvidence` (capped). The multi-driver `audit` command runs an additional project-wide findings dedup pass once its drivers exit. *** ## 5. Cloud storage (optional) Storage is **disabled by default**. When enabled (`storage.enabled: true`), a single minio-go S3 client talks to GCS (HMAC), S3, or self-hosted MinIO, the driver differs, the rest is identical. ``` gs:/// ⇒ s3://// ``` The key architectural point: the **project UUID is the in-bucket prefix**, not the bucket name. Every key is validated (`storage.ValidateKey` rejects `..`, backslashes, absolute paths) and project-prefixed server-side, so one bucket safely holds many projects and clients cannot reach outside their own. | Conventional prefix | Producer | | ----------------------------------------- | ----------------------------------- | | `ugc/` | `vigolium storage upload` (default) | | `imports/-.` | `vigolium import --upload` | | `native-scans//results.tar.gz` | `vigolium scan --upload-results` | | `agentic-scans//results.tar.gz` | `vigolium agent … --upload-results` | `vigolium import` auto-detects its input: a vigolium-audit folder, a JSONL export, a `.tar.gz`/`.zip` archive of either, or **another vigolium SQLite database** (detected by its magic header). A SQLite input is a lossless, idempotent SQLite→SQLite merge — records, findings, scans, agentic scans, and OAST interactions are deduped on their natural keys and each row keeps its original project, so re-importing the same database is a no-op. The destination is the `--db` target (or the default database). `gs://` URLs are first-class inputs/outputs: `vigolium import gs://…` downloads-then-imports, and any `export -o gs://…` writes locally then uploads on success. The `{ts}` and `{project-uuid}` placeholders expand in any `-o` path. The `bundle` export format round-trips a full snapshot (JSONL + HTML report + manifest + agent session dirs) that another machine can re-import. *** ## Related Project CLI/API recipes and access-control management. Full `vigolium storage` command and `gs://` reference. Stage 11 traces a finding from `ResultEvent` to row. The `storage:` and project config YAML blocks. # Native Scan Architecture Source: https://docs.vigolium.com/architecture/native-scan Anatomy of a scan, tracing an HTTP request from the command line through the deterministic Go pipeline to a vulnerability finding. Architecture series: [Overview](/architecture/overview) · **Native Scan** · [Agentic Scan](/architecture/agentic-scan) · [Data & Storage](/architecture/data-and-storage) · [Server & API](/architecture/server-and-api) This document traces the complete lifecycle of an HTTP request through a Vigolium scan, from `vigolium scan -t https://example.com` on the command line to a vulnerability finding written to the terminal. It is an architecture deep-dive intended for contributors who want to understand the scanning pipeline end-to-end. ## High-Level Pipeline ``` CLI invocation │ ▼ ┌──────────────────────┐ │ CLI Entry & Config │ cmd/vigolium/main.go → pkg/cli/scan.go │ Flag parsing, config│ Config loading, strategy/profile, DB init │ loading, DB init │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Input Parsing │ pkg/input/source/ │ URL/file/stdin → │ InputSource.Next() → WorkItem │ WorkItem stream │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Runner Orchestration│ internal/runner/runner.go │ 6-phase pipeline: │ Heuristics → Harvest → Spider → │ build infra, run │ Discovery → KnownIssueScan → │ phases in order │ DynamicAssessment └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Executor │ pkg/core/executor.go │ Worker pool feeds │ feedItems() → worker() → processItem() │ items to modules │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Module Dispatch │ pkg/modules/ │ Passive (sequential)│ ScanPerHost → ScanPerRequest │ Active (parallel) │ ScanPerHost/Request/InsertionPoint └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Result Emission │ pkg/output/output.go │ Post-hooks → DB │ assignModuleInfo → emitResult → │ save → output write │ SaveFinding → OnResult → Notify └──────────────────────┘ ``` ## Stage 1: CLI Entry and Configuration ### Entry Point `cmd/vigolium/main.go` prints the banner (unless `--json` or certain subcommands suppress it), then calls `cli.Execute()` which invokes the Cobra root command. ### Root Command, `pkg/cli/root.go` `rootCmd.PersistentPreRunE` fires before every subcommand and: 1. Initializes the global `zap.Logger` via `initLogger()`. 2. Falls back to the `VIGOLIUM_PROXY` environment variable if `--proxy` is empty. 3. Runs first-time setup via `ensureInitialized()`, creates `~/.vigolium/` and writes the default config, profiles, and prompt templates if they don't exist. 4. Handles early-exit flags: `--list-modules`, `--list-input-mode`, `--full-example`. ### Scan Command, `pkg/cli/scan.go` `runScanCmd()` is the heart of the scan flow. It performs these steps in order: 1. **Copy global flags** into `scanOpts` (`*types.Options`): targets, concurrency, timeout, modules, proxy, format, phases, etc. 2. **Reconcile `--json` and `--format`**: if `--json` is set and format is still the default `"console"`, switch to `"jsonl"`. 3. **Load config**: `config.LoadSettings(configPath)` reads `~/.vigolium/vigolium-configs.yaml`. CLI overrides are applied for origin mode, OAST URL, and database settings. Validates database, extensions, and strategy configs. 4. **Resolve scanning profile**: precedence is `--scanning-profile` flag > `settings.ScanningStrategy.ScanningProfile`. Profiles are loaded from `~/.vigolium/profiles/` or embedded presets, and applied via `config.ApplyProfile()`. 5. **Resolve scanning strategy**: precedence is `--strategy` flag > `settings.ScanningStrategy.DefaultStrategy`. Strategy determines which phases are enabled (discovery, spidering, KnownIssueScan, etc.). 6. **Resolve heuristics check level**: `--skip-heuristics` > `--heuristics-check` > config > default `"basic"`. 7. **Phase isolation**: `--only` and `--skip` are mutually exclusive. `--only ` enables a single phase and disables all others. `--skip ` disables specific phases. Phase aliases are normalized: `deparos`/`discover` → `discovery`, `spitolas` → `spidering`, `ext` → `extension`, `audit`/`dast`/`assessment` → `dynamic-assessment`. 8. **Validate HTML output**: `--format html` requires `--output` and is only allowed with `--only discovery` or `--only spidering`. 9. **Apply scanning pace**: concurrency and max-per-host from config are applied unless explicitly set on CLI. 10. **Initialize database**: `database.NewDB()` → `CreateSchema()` → `database.NewRepository()`. 11. **Handle `--source`**: clone git URLs or resolve local paths, link source repos to targets in DB. 12. **Branch into one of three execution paths**: ``` Has --input file? ──yes──▶ runScanWithIngest() Parse file, create InputSource, run │ no ▼ Has targets? ──no───▶ runDBScan() Scan existing DB records (empty source) │ yes ▼ runner.New(scanOpts) ──────────────────▶ Target-based: build source from CLI targets .SetSettings(settings) .SetRepository(repo) .RunNativeScan() .Close() ``` ## Stage 2: Input Parsing ### The InputSource Interface, `pkg/input/source/source.go` Input sources provide a pull-based stream of work items: ```go theme={null} type InputSource interface { Next(ctx context.Context) (*work.WorkItem, error) Close() error } ``` Return conventions: `(*WorkItem, nil)` = next item, `(nil, io.EOF)` = source exhausted, `(nil, context.Canceled)` = cancelled. The optional `Countable` interface adds `Count() int64` for progress tracking. ### InputSource Implementations | Type | File | Description | Countable | | ------------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------ | --------- | | `TargetSource` | `source.go` | Iterates CLI `-t` targets, builds GET requests via `GetRawRequestFromURL()` | Yes | | `FileSource` | `file.go` | Parses input files (OpenAPI, WSDL/SOAP, Burp, HAR, cURL, etc.) via format-specific parsers | Yes | | `StdinSource` | `stdin.go` | Reads URLs line-by-line from stdin | No | | `SingleSource` | `single_source.go` | Returns a single item, then EOF. Used by `scan-url`/`scan-request` | Yes (1) | | `MultiSource` | `multi.go` | Drains sub-sources sequentially in order | Yes (sum) | | `ConcurrentMultiSource` | `concurrent.go` | Reads all sub-sources concurrently. Used for queue-based sources | No | | `ExternalHarvesterInputSource` | `external_harvester_source.go` | Runs external harvesting (Wayback, CommonCrawl, etc.) | No | | `DeparosDiscoverySource` | `deparos_discovery.go` | Runs content discovery engine per target | No | `NewInputSource(cfg SourceConfig)` is the factory function. Based on the config fields, it creates `TargetSource`, `FileSource`, and/or `StdinSource`, wrapping multiple sources in a `MultiSource`. ### Supported Input Formats Resolved by `resolveFormat()` in `file.go`: | Format names | Parser | | ----------------------------- | ----------------------------------- | | `urls`, `url`, `list` | Line-delimited URLs | | `openapi`, `swagger` | OpenAPI/Swagger spec | | `wsdl`, `soap`, `svc` | WSDL 1.1 / SOAP service description | | `postman` | Postman collection | | `curl` | cURL commands | | `burpraw`, `burp-raw`, `raw` | Burp raw request files | | `burpxml`, `burp-xml`, `burp` | Burp XML export | | `nuclei`, `nuclei-output` | Nuclei JSONL output | | `deparos`, `deparos-output` | Deparos discovery output | ### The WorkItem, `pkg/work/item.go` ```go theme={null} type WorkItem struct { Request *httpmsg.HttpRequestResponse EnableModules []string // per-item module selection (empty = all) RecordUUID string // pre-existing DB record UUID (skip store) onComplete func() // queue ack callback (unexported) } ``` `Complete()` is called after processing to acknowledge queue-based sources. ## Stage 3: HTTP Types ### HttpRequestResponse, `pkg/httpmsg/http_request_response.go` The central data type flowing through the entire pipeline. It pairs an HTTP request with an optional response: ```go theme={null} type HttpRequestResponse struct { request *HttpRequest // required response *HttpResponse // optional, may be nil } ``` Key methods: `Request()`, `Response()`, `HasResponse()`, `Service()`, `URL()`, `Target()`, `ID()` (FNV-1a hash of `host:port:method`), `Clone()`, `WithResponse()`, `CreateInsertionPoints()`, `BuildRetryableRequest()`. Factory functions: * `GetRawRequestFromURL(url)`: builds a minimal GET request from a URL string (used by `TargetSource` and `StdinSource`) * `ParseRawRequest(raw)`: parses raw HTTP text * `FromStdRequest(req)`: converts a stdlib `http.Request` ### HttpRequest, `pkg/httpmsg/http_request.go` Stores the raw HTTP request bytes as the source of truth, with lazy-parsed accessors: ```go theme={null} type HttpRequest struct { raw []byte // source of truth service *Service // host/port/protocol // lazy-parsed cache (populated by ensureParsed()) method, path string headers []HttpHeader bodyOffset int parsed bool mu sync.RWMutex } ``` `ensureParsed()` is thread-safe via a double-checked RW mutex. It extracts headers, method, path, and body offset from the raw bytes. Immutable builder methods (`WithMethod()`, `WithPath()`, `WithHeader()`, `WithBody()`, etc.) return new `*HttpRequest` instances with rebuilt raw bytes. The `RequestOption` / `Apply()` batch builder pattern rebuilds raw bytes only once for multiple changes. ### HttpResponse, `pkg/httpmsg/http_response.go` Same lazy-parsing pattern as `HttpRequest`: ```go theme={null} type HttpResponse struct { raw []byte statusCode int headers []HttpHeader bodyOffset int parsed bool mu sync.RWMutex } ``` ### Service, `pkg/httpmsg/service.go` A host/port/protocol triple: ```go theme={null} type Service struct { host string // hostname only (no port) port int protocol string // "http" or "https" } ``` ## Stage 4: Runner Orchestration ### The Runner, `internal/runner/runner.go` The Runner is the high-level orchestrator. It builds shared infrastructure and executes the multi-phase scan pipeline. ```go theme={null} type Runner struct { output output.Writer options *types.Options settings *config.Settings inputSource source.InputSource dedupManager *dedup.Manager repository *database.Repository heuristicsResults map[string]*HeuristicsResult } ``` ### buildInfrastructure() Called once at the top of `RunNativeScan()`. Creates all shared services in the `phaseInfra` container: ```go theme={null} type phaseInfra struct { svc *services.Services httpRequester *http.Requester scopeMatcher *config.ScopeMatcher hostLimiter *hostlimit.HostRateLimiter notifier *notify.Manager hookChain *jsext.HookChain jsEngine *jsext.Engine scanUUID string } ``` Built in order: 1. **Notifier**: Telegram and/or Discord backends (from config or env vars). 2. **Services**: wraps Options, Notifier, DedupManager, and HostErrors (circuit breaker for unresponsive hosts). 3. **HostRateLimiter**: per-host concurrency control (CLI default 30 concurrent per host, 1000 max tracked hosts, 30s idle eviction). 4. **HTTP Requester**: HTTP client with retry, proxy, redirect, and middleware support. 5. **ScopeMatcher**: host/path/status/content-type/body-string filtering from config. 6. **JS Engine**: Grafana Sobek engine for JavaScript extensions, including pre/post hook chains. ### RunNativeScan(), The 6-Phase Pipeline ``` RunNativeScan() │ ├── buildInfrastructure() │ ├─── Phase 0: Heuristics Check [guard: heuristicsCheck != "none"] │ Probes target root pages, detects blank/JSON/SPA responses. │ Flags targets to skip spidering. │ ├─── Phase 1: External Harvest [guard: ExternalHarvestEnabled] │ Queries Wayback, CommonCrawl, AlienVault, URLScan, VirusTotal. │ Ingests discovered URLs into DB (no modules, pure ingestion). │ ├─── Phase 2: Spidering [guard: SpideringEnabled] │ Browser-based crawling (Chromium). Applies heuristics filter. │ Stores discovered pages in DB via repository. │ ├─── Phase 3: Discovery [guard: !SkipIngestion] │ Content discovery (brute-force dirs/files via deparos engine) │ + CLI input source. Both wrapped in MultiSource. │ Ingests into DB (no modules, pure ingestion). │ Fallback: seedCLITargets() if ingestion skipped but KnownIssueScan/DA need records. │ ├─── Phase 4: KnownIssueScan [guard: KnownIssueScanEnabled] │ Nuclei template scan + Kingfisher secret detection on stored │ response bodies. Targets enriched with discovered paths │ (enrich_targets). Filters out secret_detect passive module │ to avoid duplicates in DA phase. │ Post-phase: DeduplicateFindings() groups same-module/URL findings. │ └─── Phase 5: DynamicAssessment [guard: !SkipDynamicAssessment] THE CORE SCANNING PHASE. Reads records from DB, dispatches active + passive modules. Per-module finding cap suppresses noisy modules. Feedback loop (up to 3 rounds) re-scans newly discovered URLs. Post-phase: DeduplicateFindings() merges redundant findings. ``` Phases 0-4 populate the database with HTTP records. Phase 5 reads those records back and runs the full module pipeline against them. ### Phase 4 Detail: KnownIssueScan 1. Queries distinct paths from DB via `GetDistinctPaths()`. 2. Builds target URLs, either path-enriched (default, `enrich_targets: true`) or host-level only. 3. Runs Nuclei templates + Kingfisher secret scanning against targets. 4. **Post-phase dedup**: calls `DeduplicateFindings()` to group findings with identical `(module_id, severity, matched_at URL)`. ### Phase 5 Detail: DynamicAssessment 1. Creates a `database.Scan` record with cursor tracking. 2. Resolves DA concurrency from config (separate from discovery concurrency). 3. Optionally starts the OAST (out-of-band) service. 4. Runs a **feedback loop** (up to `maxFeedbackRounds = 3`): * Creates a `OneShotDBInputSource` that reads records after the scan cursor. * Builds an Executor with all active + passive modules, `SkipBaseline: true` (responses already in DB). * The Executor enforces a per-module finding cap (`MaxFindingsPerModule`, default 10), once a module emits this many findings, further results from that module are suppressed. * After each round, checks for newly created records. Breaks early if none. 5. **Post-phase dedup**: calls `DeduplicateFindings()` to merge findings where the same module fired on the same URL with different payloads. 6. Marks the scan as completed. ## Stage 5: The Executor ### Executor Struct, `pkg/core/executor.go` The Executor is the central dispatch engine. It receives work items, distributes them to a worker pool, and dispatches modules. ```go theme={null} type Executor struct { cfg ExecutorConfig source source.InputSource activeModules []modules.ActiveModule passiveModules []modules.PassiveModule httpClient *http.Requester scanCtx *modules.ScanContext hooks HookRunner // Pre-grouped by scan scope at init time perHostActive []modules.ActiveModule perRequestActive []modules.ActiveModule perIPActive []modules.ActiveModule perHostPassive []modules.PassiveModule perRequestPassive []modules.PassiveModule ipCache *lru.Cache[string, []httpmsg.InsertionPoint] // 4096-entry LRU requestUUIDs *shardedMap // request hash → DB record UUID } ``` ### Module Pre-Grouping At construction time, `NewExecutor()` pre-groups all modules by their `ScanScope` bitmask into five slices. A module declaring `ScanScopeInsertionPoint | ScanScopeRequest` appears in both `perIPActive` and `perRequestActive`. This avoids per-item scope-check iteration. ### Execute(), Worker Pool ```go theme={null} func (e *Executor) Execute(ctx context.Context) (bool, error) ``` 1. Spawns `Workers` goroutines reading from a buffered channel (`cap = Workers * 2`). 2. Calls `feedItems()` on the calling goroutine (producer loop). 3. Closes the channel, waits for all workers to drain. 4. Flushes passive modules (`Flusher` interface) and OAST service. 5. Returns `(foundResults, nil)`. ### feedItems(), The Producer For each item from `source.Next()`: 1. **Static file filter**: if path matches a static file extension (`.jpg`, `.css`, etc.), skip. 2. **Pre-request scope check**: `ScopeMatcher.InScopeRequest(host, path, "", "")`, host + path only, no HTTP round-trip. Rejects obviously out-of-scope items early. 3. **Host error check**: if `HostErrors.Check(hostID)` returns true (host has been circuit-broken), skip. 4. Send item to the worker channel. ### worker(), The Consumer Each worker goroutine loops on the channel: ```go theme={null} for item := range itemCh { e.processItem(item) item.Complete() e.statsTracker.Increment() } ``` ## Stage 6: Processing an Item `processItem()` is the per-item hot path. Every item that passes `feedItems()` goes through these steps: ### Step 1: Baseline HTTP Fetch ``` if SkipBaseline && response already attached: use existing response (DB-sourced items in dynamic-assessment phase) else: httpClient.Execute(request) → response copy response bytes from pool before Close() attach response to request via WithResponse() ``` Response bytes are copied from a `sync.Pool` of recycled buffers (32 KiB initial, max 1 MiB for pool return) to reduce GC pressure. ### Step 2: Traffic Callback If configured, calls `OnTraffic(method, url, statusCode, contentType)`, an observer hook for printing traffic lines to stderr. ### Step 3: Pre-Hooks ``` hooks.RunPreHooks(request) → error: log and skip item → nil return: hook filtered it out, skip item → modified request: continue with transformed request ``` Pre-hooks can inject auth headers, transform requests, or signal to skip entirely. ### Step 4: Body Size Enforcement If `ScopeMatcher` is set, checks request and response body sizes: * `BodySizeDrop` → drop item entirely. * `BodySizeTruncate` → truncate bodies to limits, continue scanning. * `BodySizeSkipScan` → truncate, save to DB, but skip scanning. ### Step 5: Scope Check + Database Save ``` if ScopeMatcher configured: check full scope (host, path, status, content types, body strings) if out-of-scope and ScopeOnIngest: drop entirely (no save, no scan) save to database if out-of-scope: saved but not scanned → return else: save to database and continue ``` `saveToDatabase()` calls `repo.SaveRecord()` and stores the returned UUID in the `requestUUIDs` sharded map (keyed by request SHA-256 hash) for later finding linkage. ### Step 6: Eligibility Pre-Computation `computeEligibility()` runs once per item (not per module): 1. Request nil check 2. URL parse check 3. Media/JS URL check (`utils.IsMediaAndJSURL`) 4. HTTP method check (skip `OPTIONS`, `CONNECT`, `HEAD`, `TRACE`) The cached `baseEligible` result lets the executor skip calling `CanProcess()` on modules that embed the standard base checks when the base would reject. ### Step 7: Module Filter If `item.EnableModules` is non-empty, builds a map-based O(1) filter. Otherwise uses the `allModulesFilter` sentinel. ### Step 8: Passive Module Execution (Sequential) ``` runPassivePerHost(request, filter) sequential loop over perHostPassive runPassivePerRequest(request, filter) sequential loop over perRequestPassive ``` For each module: check filter → check `CanProcess()` → call scan method → process results. No goroutines, passive modules do not perform network I/O. ### Step 9: Active Module Execution (Parallel) Three categories run in parallel via `conc.WaitGroup`: ``` var g conc.WaitGroup g.Go(func() { runActivePerHost(request, filter, eligibility) }) g.Go(func() { runActivePerRequest(request, filter, eligibility) }) g.Go(func() { runActivePerInsertionPoint(request, filter, eligibility) }) g.Wait() ``` Within each category, eligible modules also run concurrently (inner `conc.WaitGroup`). For the insertion-point category specifically, insertion points are iterated **serially** (one at a time), but all eligible modules for a given point run **concurrently**: ``` insertion points = ipCache.GetOrCompute(requestHash) for each insertionPoint: for each eligible module (parallel): module.ScanPerInsertionPoint(request, insertionPoint, httpClient, scanCtx) ``` ### Concurrency Model Summary ``` Execute() ├── feedItems() [calling goroutine, producer] └── Workers goroutines [consumer pool] └── processItem() ├── Passive modules [sequential on worker goroutine] │ ├── runPassivePerHost │ └── runPassivePerRequest └── Active modules [3-way parallel via conc.WaitGroup] ├── runActivePerHost [inner parallel: all modules] ├── runActivePerRequest [inner parallel: all modules] └── runActivePerInsertionPoint └── for each IP (serial) [inner parallel: all modules] ``` ## Stage 7: Insertion Points ### The InsertionPoint Interface, `pkg/httpmsg/insertion_point.go` ```go theme={null} type InsertionPoint interface { Name() string // parameter name (e.g. "id", "username") BaseValue() string // original value at this position Type() InsertionPointType // one of INS_* constants BuildRequest(payload []byte) []byte // new request bytes with payload injected PayloadOffsets(payload []byte) []int // [startOffset, endOffset] in built request } ``` ### InsertionPointType Constants | Constant | Value | Description | | -------------------------- | ----- | --------------------------- | | `INS_PARAM_URL` | 0 | URL query parameter value | | `INS_PARAM_BODY` | 1 | POST body parameter value | | `INS_PARAM_COOKIE` | 2 | Cookie value | | `INS_PARAM_XML` | 3 | XML element value | | `INS_PARAM_XML_ATTR` | 4 | XML attribute value | | `INS_PARAM_MULTIPART_ATTR` | 5 | Multipart attribute value | | `INS_PARAM_JSON` | 6 | JSON value | | `INS_HEADER` | 32 | HTTP header value | | `INS_URL_PATH_FOLDER` | 33 | REST URL path folder | | `INS_PARAM_NAME_URL` | 34 | URL parameter name | | `INS_PARAM_NAME_BODY` | 35 | Body parameter name | | `INS_ENTIRE_BODY` | 36 | Entire request body | | `INS_URL_PATH_FILENAME` | 37 | REST URL path filename | | `INS_USER_PROVIDED` | 64 | User-defined position | | `INS_EXTENSION_PROVIDED` | 65 | Extension-provided position | ### InsertionPoint Implementations | Type | Description | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ParameterInsertionPoint` | Standard parameter replacement. Uses offset-based splicing with type-aware payload encoding (URL-encode for URL/body/cookie, JSON-aware for JSON params, raw for XML). | | `HeaderInsertionPoint` | Header value replacement. Uses `AddOrReplaceHeader()` instead of offset splicing. Created for existing injectable headers + synthetic headers (`X-Forwarded-For`, `X-Forwarded-Host`, `Referer`, `True-Client-IP`, `X-Real-IP`). | | `NestedInsertionPoint` | Multi-level encoding chains (e.g., URL-encoded JSON inside a body parameter). `BuildRequest()` applies inner-to-outer: child builds first, then parent encodes the result. | | `EncodedInsertionPoint` | Custom encoder chain. Applies `prefix + payload → encoder.Encode() → splice`. Used for complex encoding scenarios. | ### LRU Cache The Executor maintains a 4096-entry LRU cache (`ipCache`) keyed by request SHA-256 hash. `CreateAllInsertionPoints()` is called once per unique request, and the results are reused for all modules scanning that request. ### Shared Base Request `CreateAllInsertionPoints()` creates a single `sharedBaseRequest` clone of the raw bytes, shared across all `ParameterInsertionPoint` instances from that call. This is safe because `BuildRequest()` never mutates the shared bytes, it always allocates a new result slice. ## Stage 8: Module Dispatch ### Module Interface Hierarchy, `pkg/modules/` ``` Module (base) ├── ActiveModule │ ├── ScanPerInsertionPoint(request, insertionPoint, httpClient, scanCtx) │ ├── ScanPerRequest(request, httpClient, scanCtx) │ ├── ScanPerHost(request, httpClient, scanCtx) │ └── AllowedInsertionPointTypes() InsertionPointTypeSet │ └── PassiveModule ├── ScanPerRequest(request, scanCtx) ├── ScanPerHost(request, scanCtx) ├── Scope() PassiveScanScope └── (optional) Flusher: Flush(scanCtx) ``` ### ScanScope Bitmask, `pkg/modules/modkit/types.go` ```go theme={null} const ( ScanScopeInsertionPoint ScanScope = 1 << iota // = 1 ScanScopeRequest // = 2 ScanScopeHost // = 4 ) ``` A module declares one or more scopes by OR-ing constants. The executor uses `ScanScopes().Has(scope)` to pre-group modules at startup. ### InsertionPointTypeSet, `pkg/modules/modkit/types.go` A `uint32` bitmask where each bit corresponds to an `InsertionPointType`. Checked by the executor before calling `ScanPerInsertionPoint()`: ```go theme={null} module.AllowedInsertionPointTypes().Contains(ip.Type()) ``` Pre-built presets: `URLParamTypes`, `BodyParamTypes`, `CookieTypes`, `HeaderTypes`, `AllParamTypes`. ### CanProcess Semantics **Active modules** (via `BaseActiveModule`): reject nil requests, unparseable URLs, media/JS URLs, and non-testable HTTP methods (`OPTIONS`, `CONNECT`, `HEAD`, `TRACE`). The executor pre-computes these checks in `computeEligibility()` and skips calling `CanProcess()` when the base would reject. **Passive modules** (via `BasePassiveModule`): only check that the required HTTP transaction parts (request and/or response) are present. They process all content types including media, no method filtering. ### Execution Pattern ``` Per item: 1. Passive per-host → sequential loop, no goroutines 2. Passive per-request → sequential loop, no goroutines 3. Active per-host → parallel: all eligible modules concurrently 4. Active per-request → parallel: all eligible modules concurrently 5. Active per-IP → for each insertion point (serial): all eligible modules concurrently ``` Steps 3-5 run as three concurrent goroutine groups via `conc.WaitGroup`. ### ScanContext, `pkg/modules/modkit/context.go` Shared resources available to all modules during scanning: ```go theme={null} type ScanContext struct { DedupManager *dedup.Manager RiskScoreUpdater RiskScoreUpdater RequestUUIDResolver RequestUUIDResolver OASTProvider OASTProvider MutationGen MutationGenerator baselineCache sync.Map // "METHOD:host/path" → *BaselineEntry } ``` * **DedupManager**: request-level deduplication. * **OASTProvider**: generates out-of-band callback URLs for blind vulnerability detection. * **MutationGenerator**: classifies parameter values and generates test mutations. * **baselineCache**: caches baseline responses for diff-based scanning. ### Flusher Interface Passive modules that buffer state across many requests (e.g., `anomaly_ranking`) implement `Flusher`: ```go theme={null} type Flusher interface { Flush(scanCtx *ScanContext) } ``` Called by the executor after all workers complete, enabling end-of-scan aggregation and final result emission. ### Module Development Defaults, `pkg/modules/modkit/` Module authors embed `BaseActiveModule` or `BasePassiveModule` to get default implementations of all interface methods. Module IDs must be lowercase kebab-case with prefix `active-` or `passive-` (validated at construction, panics on violation). The `modkit` package also provides `NewBaseModule()`, `NewBaseActiveModule()`, and `NewBasePassiveModule()` constructors. ## Stage 9: Result Emission ### ResultEvent, `pkg/output/output.go` ```go theme={null} type ResultEvent struct { ModuleID string `json:"template-id"` Info Info `json:"info,inline"` Type string `json:"type"` Host string `json:"host,omitempty"` URL string `json:"url,omitempty"` Matched string `json:"matched-at,omitempty"` ExtractedResults []string `json:"extracted-results,omitempty"` Request string `json:"request,omitempty"` Response string `json:"response,omitempty"` Metadata map[string]interface{} `json:"meta,omitempty"` Timestamp time.Time `json:"timestamp"` // ... } ``` `ResultEvent.ID()` computes a SHA-1 hash over `ModuleID | Description | Severity | Matched`, this becomes `finding_hash` in the database for deduplication. ### processResults() and emitResult() When a module returns results, the executor processes them: ``` Module returns []*ResultEvent │ ▼ processResults(results, module) │ for each result: │ ├── moduleFindingAllowed(module.ID()) │ Per-module finding cap check (MaxFindingsPerModule). │ When > 0, suppresses results after the limit is reached. │ Logs a one-time warning when a module hits its cap. │ ├── assignModuleInfo(result, module) │ Set ModuleID, Info.Name, Description, Severity, Confidence │ Default Type = "http" │ Derive Matched from URL if empty │ Derive URL from request bytes if empty │ Fill Host from URL │ └── emitResult(result) │ ├── 1. Post-hooks: RunPostHooks(result) │ nil return → drop result (hook filtered it out) │ ├── 2. Set results flag: e.results.Store(true) │ ├── 3. Database save: │ Build temp HttpRequest from result.Request │ Look up requestUUIDs[requestHash] → recordUUID │ repo.SaveFinding(result, [recordUUID], scanUUID) │ Uses INSERT ON CONFLICT (finding_hash) DO NOTHING │ ├── 4. OnResult callback → output writer │ └── 5. Notifier.Send(result) → Telegram/Discord ``` ## Stage 10: Output ### Writer Interface, `pkg/output/output.go` ```go theme={null} type Writer interface { Close() Write(*ResultEvent) error WriteFileOnly(*ResultEvent) error } ``` ### StandardWriter The default `Writer` implementation: 1. Sets `Timestamp = time.Now()`, defaults `Type = "http"`, forces `MatcherStatus = true`. 2. Serializes to JSON via `jsoniter.Marshal()`. 3. Under mutex: * **Stdout**: writes JSON (if `--json`) or formatted console output (if not `--silent`). * **File**: appends JSON line to output file (JSONL format). ### Console Format, `pkg/output/format_screen.go` ``` [› phase │] [moduleType] [moduleName] [severity] matched-at [extracted-results] [fuzz-param] ``` * Module ID split into type (`active`/`passive`) and name, colored accordingly. * Severity shown with symbol and ANSI color (Critical=magenta, High=red, Medium=yellow, Low=green). * Output truncated to terminal width. ### JSON Format, `pkg/output/format_json.go` Serializes `ResultEvent` via `jsoniter.Marshal()`. Response body is stripped unless `--include-response` is set. ### HTML Format, `pkg/output/format_html.go` Uses a streaming approach: splits the embedded HTML template at the results-JSON marker, writes the before-portion with simple string replacement (avoids `text/template` because bundled JS contains template-delimiter sequences), then streams JSON array items one at a time, then writes the after-portion. ### File Output Writer, `pkg/output/file_output_writer.go` ```go theme={null} type fileWriter struct { file *os.File mu sync.Mutex } ``` Mutex-locked, appends JSON + newline (JSONL format). Opens with `O_APPEND|O_CREATE|O_WRONLY` for safe resume across invocations. ## Stage 11: Database Persistence ### Data Models, `pkg/database/models.go` #### HTTPRecord (table: `http_records`) Fully denormalized, no separate hosts or parameters tables. Key fields: * **Identity**: `UUID` (primary key), `RequestHash` (SHA-256 of raw request) * **Host info**: `Scheme`, `Hostname`, `Port`, `IP` * **Request**: `Method`, `Path`, `URL`, `RequestHeaders` (JSONB), `RawRequest` (bytea), `RequestBody` (bytea) * **Response**: `StatusCode`, `ResponseHeaders` (JSONB), `RawResponse` (bytea), `ResponseBody` (bytea), `ResponseTitle`, `ResponseWords` * **Parameters**: `Parameters` (JSONB array of `EmbeddedParam`) * **Risk**: `RiskScore`, `Remarks` (JSONB array) * **Metadata**: `Source`, `SentAt`, `ReceivedAt`, `CreatedAt` #### Finding (table: `findings`) * **Identity**: `ID` (auto-increment), `FindingHash` (unique constraint for dedup) * **Module info**: `ModuleID`, `ModuleName`, `Description`, `Severity`, `Confidence` * **Match data**: `MatchedAt` (JSONB array), `ExtractedResults`, `Request`, `Response` * **Relations**: `HTTPRecordUUIDs` (JSONB array), `ScanUUID` * **Grouped evidence**: `AdditionalEvidence` (JSONB array of strings), request/response pairs from duplicate findings that were merged into this survivor (capped at 10 entries) The `finding_records` junction table links findings to HTTP records (many-to-many). ### Converters, `pkg/database/converters.go` * `HTTPRecord.FromHttpRequestResponse()`: converts the in-memory type to the DB model. Generates UUID, parses URL, copies headers/body, computes hashes, extracts HTML title, counts response words. * `Finding.FromResultEvent()`: maps `ResultEvent` fields to `Finding`. Sets `FindingHash = event.ID()` (the SHA-1 dedup hash). ### Repository, `pkg/database/repository.go` Key methods: | Method | Description | | ------------------------------ | ----------------------------------------------------------------------------------- | | `SaveRecord()` | Single INSERT, returns UUID | | `SaveRecordsBatch()` | Bulk INSERT in one transaction | | `SaveFinding()` | INSERT ON CONFLICT (finding\_hash) DO NOTHING + evidence append + junction table | | `DeduplicateFindings()` | Post-phase grouping: merge findings sharing (module\_id, severity, matched\_at URL) | | `CreateScanWithCursor()` | Creates scan record, copies cursor from last completed scan | | `CountRecordsAfterCursor()` | Counts new records since cursor (used for feedback loop) | | `GetRecordsWithResponseBody()` | UUID-cursor pagination for batch scanning (Kingfisher) | | `UpdateRiskScores()` | Batch CASE/WHEN UPDATE, 500 UUIDs per statement | ### RecordWriter, `pkg/database/record_writer.go` Batched asynchronous persistence for high-throughput ingestion: ```go theme={null} type RecordWriter struct { repo *Repository cfg RecordWriterConfig // BufferSize=4096, BatchSize=128, FlushInterval=50ms ch chan writeRequest // backpressure via channel capacity } ``` * `Write()` converts to `HTTPRecord`, sends to buffered channel, blocks until flushed. * `flushLoop()` runs as a single background goroutine: accumulates batch, flushes on batch-full or ticker-fire via `repo.SaveRecordsBatch()`. * Each caller gets a `WriteResult{UUID, Err}` back on a per-request result channel. ## Stage 12: Supporting Systems ### Scope Matching, `internal/config/scope_matcher.go` `ScopeMatcher` evaluates items against configurable rules across multiple dimensions (all AND-ed): 1. **Host**: glob match + origin mode filtering (cached per host) 2. **Path**: `filepath.Match` glob patterns 3. **Static file extension**: configurable extension set 4. **Status code**: exact, wildcard (`2xx`), or range (`400-499`) 5. **Content type**: glob patterns for request and response 6. **Body strings**: case-insensitive substring matching on request/response bodies **Origin modes** control how CLI targets constrain host scope: | Mode | Matching Rule | | ------------------- | ----------------------------------------- | | `all` | No restriction | | `strict` | Exact hostname match | | `balanced` | eTLD+1 must match (e.g., `*.example.com`) | | `relaxed` (default) | Host contains target keyword | ### Rate Limiting, `pkg/core/ratelimit/host_limiter.go` `HostRateLimiter` provides per-host concurrency control: * **32 fixed shards** with inline FNV-1a hashing for shard selection. * Each host gets a **buffered channel semaphore** (capacity = `MaxPerHost`, CLI default 30). * `Acquire(ctx, host)` blocks until a slot is free; `Release(host)` frees a slot. * Background eviction goroutine removes idle entries (default: 30s idle, checked every 10s). * Per-shard capacity cap with oldest-entry eviction when exceeded. ### Host Error Circuit Breaker, `pkg/core/hosterrors/` `hosterrors.Cache` tracks consecutive errors per host: * `MarkFailed()` increments the error counter (with regex-based error matching). * `Check()` returns true when the counter reaches `MaxHostError` (default 30). * `MarkSuccess()` resets the counter (but not if already at threshold). * The executor's `feedItems()` pre-checks this to skip items for quarantined hosts. ### JS Extension Hooks, `pkg/jsext/hooks.go` **Pre-hooks** (`PreHookExecutor`): transform or filter requests before module dispatch. Return `nil` to skip the item. **Post-hooks** (`PostHookExecutor`): transform or filter results before output. Return `nil` to drop the result. `HookChain` executes hooks sequentially, passing each hook's output to the next. On error, the hook is skipped (non-fatal). On `nil` return, the chain is aborted immediately. Each hook uses a `VMPool` (`sync.Pool` of Sobek VMs), VMs are reused across concurrent invocations with no shared mutable state. ### OAST (Out-of-Band) Out-of-band callback detection for blind vulnerabilities (SSRF, XXE, etc.). The OAST service generates unique callback URLs per module/parameter/request, and is flushed at the end of the scan with a grace period to catch late callbacks. ### Deduplication and Finding Grouping Three levels of deduplication prevent noise and redundancy: 1. **Request-level**: `DedupManager` prevents scanning duplicate requests (checked before module dispatch). 2. **Finding-level (inline)**: `finding_hash` unique constraint in the database uses `INSERT ON CONFLICT DO NOTHING`. When a duplicate hash is detected at insert time, `appendRecordsToFinding()` appends the new HTTP record UUIDs and request/response pair (as `AdditionalEvidence`) to the existing finding instead of creating a new row. 3. **Finding-level (post-phase grouping)**: `DeduplicateFindings()` runs after the KnownIssueScan and dynamic-assessment phases. It groups findings that share the same `(module_id, severity, matched_at[0] URL)` within a project, this catches cases where the same module fires multiple times on the same URL with different payloads (e.g., an injection probe producing dozens of results per endpoint). The grouping process: * Partitions findings by `module_id || severity || matched_at[0]` and orders by `created_at ASC` * Keeps the earliest finding per group as the **survivor** * Collects request/response pairs from duplicates into the survivor's `AdditionalEvidence` field (capped at 10 entries to bound storage) * Deletes all duplicate findings and their `finding_records` junction rows * Returns counts of deleted findings and merged groups for user feedback ``` Phase completes (KnownIssueScan or dynamic-assessment) │ ▼ DeduplicateFindings(projectUUID) │ ├── GROUP BY (module_id, severity, matched_at[0]) │ ORDER BY created_at ASC → survivor = row_number 1 │ ├── For each group with duplicates: │ Merge duplicate request/response → survivor.AdditionalEvidence │ Cap at 10 evidence entries │ ├── DELETE duplicate findings + junction rows │ └── Print feedback: "grouped N findings into M" ``` ## Putting It All Together ### End-to-End Flow ``` vigolium scan -t https://example.com │ ▼ ┌─────────────┐ │ CLI Parse │ pkg/cli/scan.go: runScanCmd() │ + Config │ Load settings, resolve strategy/profile │ + DB Init │ database.NewDB() → CreateSchema() └─────┬───────┘ │ ▼ ┌─────────────┐ │ Runner │ internal/runner/runner.go │ Build Infra│ HTTP client, scope matcher, rate limiter, hooks └─────┬───────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ RunNativeScan() — 6 Phases │ │ │ │ [Heuristics] → [Harvest] → [Spider] │ │ → [Discovery/Ingest] → [KnownIssueScan] → [Dynamic Assess] │ │ │ │ Phases 0-4: populate DB with HTTP records │ │ Phase 4-5: DeduplicateFindings() after each │ │ Phase 5: scan records with modules │ └───────────────────────┬──────────────────────────────────────────┘ │ ▼ (Phase 6 detail) ┌────────────────────────────────────────────────────┐ │ Executor │ │ │ │ feedItems(): │ │ source.Next() → static filter → scope check │ │ → host error check → send to worker channel │ │ │ │ worker() → processItem(): │ │ 1. Baseline HTTP fetch (or use DB response) │ │ 2. Traffic callback │ │ 3. Pre-hooks (JS transform/filter) │ │ 4. Body size enforcement │ │ 5. Scope check + DB save │ │ 6. Eligibility pre-computation │ │ 7. Passive modules (sequential) │ │ 8. Active modules (parallel, 3-way) │ │ └── per insertion point: all modules │ │ │ │ Post-processing: │ │ Flush passive modules (Flusher interface) │ │ Flush OAST service (grace period) │ └───────────────────────┬────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────┐ │ Result Emission │ │ │ │ Per-module finding cap (suppress after limit) │ │ assignModuleInfo() → emitResult(): │ │ 1. Post-hooks (JS transform/filter) │ │ 2. SaveFinding() to DB (dedup via finding_hash │ │ + evidence append on conflict) │ │ 3. OnResult → StandardWriter.Write() │ │ 4. Notifier.Send() → Telegram/Discord │ └───────────────────────┬────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────┐ │ Output │ │ │ │ Console: colored severity + module + matched URL │ │ JSON: JSONL via jsoniter │ │ HTML: embedded ag-grid template │ │ File: append-only JSONL with mutex │ └────────────────────────────────────────────────────┘ ``` ### Summary Table | Stage | Key File | Key Function | Data In | Data Out | | ---------------- | -------------------------------- | ---------------------------------------- | ------------------------ | ------------------------------------ | | CLI Entry | `cmd/vigolium/main.go` | `main()` → `cli.Execute()` | CLI args | , | | Config | `pkg/cli/scan.go` | `runScanCmd()` | Flags + YAML | `*types.Options`, `*config.Settings` | | Input | `pkg/input/source/` | `InputSource.Next()` | URLs/files/stdin | `*work.WorkItem` | | HTTP Types | `pkg/httpmsg/` | `GetRawRequestFromURL()` | URL string | `*HttpRequestResponse` | | Runner | `internal/runner/runner.go` | `RunNativeScan()` | Options + Settings | Phase results | | Executor | `pkg/core/executor.go` | `Execute()` → `processItem()` | `InputSource` + modules | `bool` (found results) | | Insertion Points | `pkg/httpmsg/insertion_point.go` | `CreateAllInsertionPoints()` | Raw request bytes | `[]InsertionPoint` | | Module Dispatch | `pkg/modules/` | `ScanPer{Host,Request,InsertionPoint}()` | `*HttpRequestResponse` | `[]*ResultEvent` | | Result Emission | `pkg/core/executor.go` | `emitResult()` | `*ResultEvent` | DB write + output | | Output | `pkg/output/output.go` | `StandardWriter.Write()` | `*ResultEvent` | Console/JSON/HTML/file | | DB Persistence | `pkg/database/` | `SaveRecord()`, `SaveFinding()` | HTTP types / ResultEvent | `HTTPRecord`, `Finding` | ## Related Operating modes and how the subsystems fit together. Stage 11 in depth, multi-tenancy, repository pattern, cloud storage. Pick which phases run and how aggressively. Task docs for discovery, spidering, audit, and extension. # Architecture Overview Source: https://docs.vigolium.com/architecture/overview The Vigolium system at a glance, operating modes, the two scanning paradigms, and how the pieces fit together. This is the entry point to Vigolium's architecture documentation. This page covers the system at a glance, operating modes, the two scanning paradigms, and how the pieces fit together. The sibling documents drill into each subsystem. The deterministic Go scan pipeline, end to end. The AI agent engine, orchestrators, and olium runtime. Multi-tenancy, the database model, and cloud storage. The REST server, traffic ingestion, and the API surface. Vigolium is a high-fidelity web vulnerability scanner written in Go. It combines deterministic, module-based scanning with AI-driven agentic analysis to provide broad and deep coverage of web application security issues. The scanner ships 323 modules (207 active, 116 passive) covering injection flaws, misconfigurations, information disclosure, authentication issues, and more. Vigolium can operate as a CLI tool for one-off scans, as a persistent REST API server that ingests live traffic, or as a standalone ingestor client that forwards traffic to a running server. All scan data is project-scoped for multi-tenancy support. Module: `github.com/vigolium/vigolium`, requires Go 1.27+. ## Operating Modes | Mode | Binary | Description | | ------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | | **CLI Scanner** | `vigolium scan` | Run scans directly from the command line against targets, input files (OpenAPI, Postman, Burp, cURL, HAR), or source code paths. | | **Server Mode** | `vigolium server` | Launch a REST API server with Swagger UI. Ingest traffic, trigger scans, query findings, and run agent sessions over HTTP. | | **Ingestor Client** | `vigolium ingest` | Lightweight client that captures and forwards HTTP traffic to a running Vigolium server for analysis. | ## Scanning Paradigms ### Native Scan The native scan pipeline is fully deterministic, pure Go, no AI involvement. Requests flow through a fixed sequence of phases, each handling a distinct stage of reconnaissance or testing. **Phases (in order):** ``` Heuristics -> External Harvesting -> Spidering -> Discovery -> KnownIssueScan -> DynamicAssessment ``` | Phase | Purpose | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Heuristics | Lightweight fingerprinting and technology detection | | External Harvesting | Wayback Machine and other passive source enumeration | | Spidering | Active crawling, JS analysis, link and form extraction (alias: `spitolas`) | | Discovery | Endpoint and content discovery via wordlists (aliases: `deparos`, `discover`) | | KnownIssueScan | Nuclei templates and Kingfisher secret detection against discovered paths | | DynamicAssessment | Core vulnerability testing, injection, XSS, SSRF, etc.; user-supplied JS/YAML extensions also dispatch here (CLI aliases: `audit`, `dast`, `assessment`) | **Strategies** control which phases run and how aggressively: | Strategy | Behavior | | -------- | ---------------------------------------------------------------------------------- | | Lite | Fast surface-level scan; skips heavy crawling and discovery | | Balanced | Default. Runs all phases with sensible limits | | Deep | Exhaustive scanning with higher limits, broader wordlists, and external harvesting | ### Agentic Scan Agentic scanning uses AI agents to drive or augment the scanning process. Invoked via `vigolium agent `. All AI dispatch runs through the in-process **olium** engine (`pkg/olium/`); eleven providers are supported: `openai-codex-oauth`, `anthropic-api-key`, `anthropic-oauth`, `openai-api-key`, `openai-responses`, `anthropic-cli`, `anthropic-claude-sdk-bridge`, `anthropic-compatible`, `anthropic-vertex`, `google-vertex`, and `openai-compatible` (Ollama / OpenRouter / LM Studio / vLLM / …). | Mode | Command | Description | | ------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Query** | `vigolium agent query` | Single-shot prompt execution. Good for code review, endpoint discovery, secret detection. No network scanning. | | **Autopilot** | `vigolium agent autopilot` | Autonomous operator with Bash/file/web/browser and first-class Vigolium tools. Legacy mode reports directly; durable modes rotate context, persist resumable state, and can verify candidates before promotion. | | **Swarm** | `vigolium agent swarm` | Multi-phase pipeline where native Go handles heavy lifting and AI intervenes at checkpoints, planning attacks, triaging results, and generating custom JS scanner extensions. | | **Audit** | `vigolium agent audit` | Unified driver dispatcher. `--driver auto\|both\|audit\|piolium` drives the embedded vigolium-audit harness, the standalone piolium harness, or both side-by-side under one parent scan. | | **Piolium** | `vigolium agent audit --driver=piolium` | Direct access to the standalone piolium audit harness (requires a separate `~/.piolium` install). | | **Olium** | `vigolium agent olium` (or `vigolium ol`) | Direct interactive TUI access to the olium engine. Use `-p` for a non-interactive one-shot prompt. | All agent modes support `--source` for source-aware analysis and store session artifacts (plans, extensions, output) in a configurable sessions directory. ## Architecture at a Glance ``` +-----------------------+ | Input Sources | | curl / OpenAPI / Burp | | HAR / Postman / URL | +-----------------------+ | +-----------------+-----------------+ | | vigolium scan vigolium server | | v v +--------------+ +------------------+ | Scope Filter | | REST API (Fiber) | +--------------+ +------------------+ | | +-----------------+-----------------+ | +-----------------+-----------------+ | | Native Scan Agentic Scan | | +--------------------+ +------------------+ | Executor (Workers) | | Agent Engine | | Rate Limiter | | Prompt Templates | +--------------------+ +------------------+ | | +---------------------+ +--------------------+ | Module Registry | | Olium Providers | | 201 Active Modules | | openai-codex-oauth | | 116 Passive Modules | | anthropic-api-key | +---------------------+ | anthropic-oauth | | | openai-api-key | | | openai-responses | | | anthropic-cli | | | anthropic-compat. | | | claude-sdk-bridge | | +--------------------+ | | +-----------------+-----------------+ | v +------------------------+ | Results Store | | SQLite / PostgreSQL | | HTML / JSONL / Console | +------------------------+ ``` ## Architecture Documents Deep-dives into each subsystem live alongside this page: | Subsystem | Document | Covers | | -------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------- | | Native scan pipeline | [Native Scan](/architecture/native-scan) | CLI entry → input parsing → executor → modules → results → DB, all 12 stages | | Agentic scan engine | [Agentic Scan](/architecture/agentic-scan) | Subcommands, orchestrators, the engine seam, olium runtime, providers | | Data & persistence | [Data & Storage](/architecture/data-and-storage) | `project_uuid` multi-tenancy, repository pattern, data models, cloud storage | | Server & API | [Server & API](/architecture/server-and-api) | Fiber server, traffic ingestion, REST surface, agent run API | ## Where to Go Next | I want to... | Go to | | ------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | Get up and running quickly | [Quickstart](/getting-started/quickstart) | | Choose a scanning strategy | [Strategies](/native-scan/strategies) | | Learn about individual scan phases | [Phases](/native-scan/phases/discovery) (discovery, spidering, audit, extension, known-issue-scan) | | Explore agentic scanning | [Agent Mode](/agentic-scan/agent-mode) | | Use Autopilot / Swarm mode | [Autopilot](/agentic-scan/autopilot) · [Swarm](/agentic-scan/swarm) | | Use the olium engine directly (TUI / headless) | [Olium](/agentic-scan/olium) | | Run Vigolium as a server | [Server Mode](/server-mode/running-the-server) | | Configure scans and settings | [Configuration](/getting-started/configuration) | | Format and export results | [Output & Reporting](/getting-started/output-and-reporting) | | Write custom JS extensions | [Writing Extensions](/customization/writing-extensions) | | Browse the REST API | [API Overview](/api-overview) | | Manage projects (multi-tenancy) | [Projects API](/api-references/projects) | | Use cloud storage (gs\:// URLs, bundles, uploads) | [Storage API](/api-references/storage) | | Debug issues | [FAQ](/others/faq) | # Server & API Architecture Source: https://docs.vigolium.com/architecture/server-and-api The Fiber REST service, its process model, traffic ingestion paths, the REST surface, and the agent run API. Architecture series: [Overview](/architecture/overview) · [Native Scan](/architecture/native-scan) · [Agentic Scan](/architecture/agentic-scan) · [Data & Storage](/architecture/data-and-storage) · **Server & API** Vigolium runs as a long-lived service via `vigolium server`, a Fiber REST API that ingests traffic, triggers native and agentic scans, and serves results, all sharing the same project-scoped persistence layer as the CLI. This document explains the service's shape and request lifecycle. For curl-by-curl recipes see [Server & Ingestion](/getting-started/server-and-ingestion); for the full endpoint catalogue see [API Overview](/api-overview) and the [API Reference](/api-references/overview). *** ## 1. Process model ``` vigolium server ┌─────────────────────────────────────────────────────────────┐ │ Fiber HTTP server :9002 (0.0.0.0 default) │ │ middleware: CORS → auth (Bearer) → project resolve │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │ │ │ Ingestion │ │ Scan control │ │ Agent run API │ │ │ │ handlers │ │ handlers │ │ handlers_agent.go │ │ │ └──────┬──────┘ └──────┬───────┘ └─────────┬─────────┘ │ │ └──────────┬─────┴────────────────────┘ │ │ ▼ │ │ shared Repository (SQLite / PostgreSQL) │ └─────────────────────────────────────────────────────────────┘ ▲ ▲ │ optional transparent proxy │ vigolium ingest │ (--ingest-proxy-port :9003) │ (remote client) ``` The server is `pkg/server/` (Fiber). It owns no scan logic of its own, it wraps the same `internal/runner` native pipeline and the same `pkg/agent` orchestrators the CLI uses, so behavior is identical whichever entry point launches a scan. Three traffic sources feed the same `RecordWriter` → repository path: the `/api/ingest-http` endpoint, the optional transparent HTTP proxy, and the `vigolium ingest` client. ### Middleware chain Every request (except `/`, `/health`, `/metrics`, `/swagger/*`) passes: 1. **CORS**: `server.cors_allowed_origins`, `reflect-origin` (default, credentialed echo), `*` (wildcard, no credentials), explicit comma-separated allowlist, or empty (disabled). 2. **Auth**: `Authorization: Bearer `; key resolves `VIGOLIUM_API_KEY` env > `server.auth_api_key` config. `vigolium server -A` disables auth (development only). 3. **Project resolution**: `X-Project-UUID` header selects the tenant (default project if absent). Project scoping isolates *data*, not callers — one API key reaches every project. See [Data & Storage](/architecture/data-and-storage). *** ## 2. Traffic ingestion `POST /api/ingest-http` is the universal entry point. A single endpoint accepts many `input_mode`s, normalizes them into `HttpRequestResponse` items, and hands them to the async `RecordWriter`: | `input_mode` | Payload field | Source | | --------------------- | --------------------------------------------------------------------- | -------------------------------- | | `url` / `url_file` | `content` | One URL / newline-separated list | | `curl` | `content` or `content_base64` | A curl command string | | `burp_base64` | `http_request_base64` (+ optional `http_response_base64`, `url` hint) | Raw HTTP request | | `openapi` / `swagger` | `content` or `content_base64` | OpenAPI/Swagger spec | | `postman_collection` | `content_base64` | Postman collection | Large payloads should use the `*_base64` fields to avoid JSON escaping. The same parsers back the `vigolium ingest` CLI, which runs in two modes: **remote** (`-s http://server` → POSTs to `/api/ingest-http`) or **local** (`--server` omitted → fetches and writes straight to the DB). ### Transparent proxy `vigolium server --ingest-proxy-port 9003` opens a recording HTTP proxy. Plain HTTP traffic routed through it is captured into the DB; HTTPS `CONNECT` tunnels pass through **without** recording. This is the zero-instrumentation path for capturing traffic from arbitrary tools (`curl -x`, `httpx -proxy`, `nuclei -proxy`). *** ## 3. The REST surface Endpoints group by concern; each group has a dedicated page under the [API Reference](/api-references/overview). | Group | Representative endpoints | Purpose | | -------------- | --------------------------------------------------------------------------------------------------- | -------------------------------- | | Meta (no auth) | `GET /`, `/health`, `/metrics`, `/swagger/*`, `/server-info` | Liveness, Prometheus, OpenAPI UI | | Auth/user | `POST /api/auth/login`, `GET /api/user/info` | Token issue, identity | | Ingestion | `POST /api/ingest-http` | Traffic in (see §2) | | HTTP records | `GET/DELETE /api/http-records[/:uuid]` | Query/inspect captured traffic | | Findings | `GET /api/findings`, `PATCH /api/findings/:id/status` | Query/triage results | | Scan control | `POST /api/scan-url`, `/api/scan-request`, `/api/scans/run`, `/api/scans/:uuid/{stop,pause,resume}` | Trigger & manage native scans | | Scope / config | `GET/POST /api/scope`, `/api/config` | Live scope & config | | Projects | `GET/POST/PUT/DELETE /api/projects[/:uuid]`, `GET /api/projects/:uuid/stats` | Multi-tenancy management | | Storage | `/api/storage/{source,results,upload-source,presign}` | Cloud bundles (storage enabled) | | DB browse | `/api/db/tables/...` | Generic table inspection (admin) | | Agent | `POST /api/agent/run/{query,autopilot,swarm,audit}`, `/api/agent/chat/completions`, sessions/status | AI runs (see §4) | ### Asynchronous job pattern Scan and agent runs are long-lived, so the API is **launch-and-poll**, not request/response: 1. `POST /api/scans/run` or `/api/agent/run/*` → `202 Accepted` with a UUID (`409 Conflict` if one is already active, only one agent run at a time). 2. Poll `GET /api/scan/status` or `GET /api/agent/status/:id` until status leaves `running`. 3. Fetch artifacts: `GET /api/agent/sessions/:id/{logs,artifacts,artifacts/*}` (logs SSE-capable; artifact reads support nested paths and a `?max_bytes=` cap). Opting into `"stream": true` on a run endpoint switches to Server-Sent Events instead, `data:` lines carrying `{"type":"chunk|phase|done|error", …}`. Most consumers should prefer the async poll-and-tail flow to keep warm sessions and prompt caches stable. *** ## 4. Agent run API `pkg/server/handlers_agent.go` mirrors the `vigolium agent` subcommands over HTTP. It does not re-implement agent logic, it invokes the same orchestrators documented in [Agentic Scan](/architecture/agentic-scan), with one deliberate constraint: * **Server-default provider, BYOK per request.** The server falls back to `agent.olium.*` in `vigolium-configs.yaml` (which keeps warm sessions and prompt caches stable across requests), but every agent run endpoint also accepts per-request BYOK credentials (`api_key`, `oauth_token`, `oauth_cred_file`, `oauth_cred_json`) so callers can swap providers on a single request without editing YAML. * **Backward-compatible source field.** Request types expose `EffectiveSourcePath()` to accept both `source` and legacy `repo_path` JSON keys. * **Audit driver dispatch.** `POST /api/agent/run/audit` takes `driver: "auto"|"both"|"audit"|"piolium"` (default `"auto"` — run audit; fall back to piolium only if audit fails). Multi-driver modes run sequentially under one parent run, expose per-driver BYOK auth (`audit_auth`, `piolium_auth`), and multiplex SSE chunks with a `driver` field when streaming. * **OpenAI-compatible endpoint.** `POST /api/agent/chat/completions` exposes the in-process olium engine through the OpenAI Chat Completions wire format so existing SDKs (LangChain, LlamaIndex, custom proxies) can drive Vigolium agents. *** ## Related Startup flags, curl recipes per input mode. Full endpoint catalogue and request/response schemas. The orchestrators the agent endpoints invoke. Project scoping and the persistence layer the handlers share. # Customizing & Extending Vigolium Source: https://docs.vigolium.com/customization/extending-vigolium Covers every customization mechanism in Vigolium, from JS and YAML extensions to scanning profiles and the olium agent runtime. Vigolium is designed for extensibility. Whether you need to add a new vulnerability check, reshape scan behavior, or integrate AI-driven analysis, there are multiple extension points, each with different trade-offs. This guide covers every customization mechanism, explains when to use each one, and helps you pick the right approach for your use case. *** ## Table of Contents * [Extension Points at a Glance](#extension-points-at-a-glance) * [1. JavaScript Extensions](#1-javascript-extensions) * [2. YAML Extensions](#2-yaml-extensions) * [3. Custom Prompt Templates](#3-custom-prompt-templates) * [4. Scanning Profiles](#4-scanning-profiles) * [5. Scope Rules](#5-scope-rules) * [6. Pre-Hooks and Post-Hooks](#6-pre-hooks-and-post-hooks) * [7. Agent Providers (Olium)](#7-agent-providers-olium) * [8. Configuration Overrides](#8-configuration-overrides) * [Decision Matrix](#decision-matrix) *** ## Extension Points at a Glance | Extension Point | Language | Recompile? | Best For | | ------------------------------------------------- | ----------------------- | ---------- | ------------------------------------------------------------ | | [JavaScript Extensions](#1-javascript-extensions) | JavaScript | No | Custom active/passive checks with full API access | | [YAML Extensions](#2-yaml-extensions) | YAML | No | Declarative pattern matching, simple payload/matcher rules | | [Prompt Templates](#3-custom-prompt-templates) | Markdown + Go templates | No | AI-driven code review, endpoint discovery, custom analysis | | [Scanning Profiles](#4-scanning-profiles) | YAML | No | Reusable scan presets (speed, modules, phases) | | [Scope Rules](#5-scope-rules) | YAML | No | Target filtering (hosts, paths, status codes, content types) | | [Pre/Post Hooks](#6-pre-hooks-and-post-hooks) | JS or YAML | No | Request mutation, finding suppression, severity escalation | | [Agent Providers](#7-agent-providers-olium) | Olium provider config | No | Picking the LLM provider/model for AI subcommands | | [Config Overrides](#8-configuration-overrides) | YAML | No | Tuning concurrency, rate limits, database, notifications | *** ## 1. JavaScript Extensions JavaScript extensions are the most flexible way to add custom scanning logic without recompiling Vigolium. They run inside an embedded JS engine (Grafana Sobek) and have access to the full `vigolium.*` API, HTTP requests, database queries, parsing utilities, AI integration, and more. ### What you can build * **Active modules**: send payloads to insertion points (parameters, headers, cookies, paths) and analyze responses for vulnerabilities. * **Passive modules**: analyze captured HTTP traffic without generating new requests. * **Pre-hooks**: mutate requests before they reach scanner modules (inject auth headers, skip paths). * **Post-hooks**: filter, tag, or escalate findings after detection. ### Minimal example (active module) ```javascript theme={null} module.exports = { id: "reflected-param-scanner", name: "Reflected Parameter Scanner", type: "active", severity: "medium", confidence: "firm", scanTypes: ["per_insertion_point"], scanPerInsertionPoint: function(ctx, insertion) { var canary = "VGNM" + vigolium.utils.randomString(8); var resp = vigolium.http.send(insertion.buildRequest(canary)); if (resp && resp.body.indexOf(canary) !== -1) { return [{ matched: canary, url: ctx.request.url, name: "Reflected parameter: " + insertion.name, severity: "medium", request: insertion.buildRequest(canary), response: resp.raw }]; } return null; } }; ``` ### Available APIs | Namespace | Purpose | Key Methods | | ----------------- | ---------------------- | -------------------------------------------------------------- | | `vigolium.http` | Send HTTP requests | `get`, `post`, `request`, `send` | | `vigolium.scan` | Scan control | `listModules`, `isInScope`, `createFinding`, `startNewScan` | | `vigolium.db` | Database access | `records.query`, `findings.query`, `compareResponses` | | `vigolium.parse` | HTTP parsing | `url`, `request`, `response`, `headers`, `json` | | `vigolium.utils` | Encoding, hashing, I/O | `base64Encode`, `sha256`, `readFile`, `exec`, `detectAnomaly` | | `vigolium.ingest` | Import traffic | `url`, `curl`, `raw`, `openapi`, `postman` | | `vigolium.source` | Source code access | `list`, `readFile`, `listFiles`, `searchFiles` | | `vigolium.agent` | AI integration | `ask`, `generatePayloads`, `analyzeResponse`, `confirmFinding` | | `vigolium.config` | Extension variables | Read-only access to `extensions.variables` | Full TypeScript definitions: [`pkg/jsext/vigolium.d.ts`](https://github.com/vigolium/vigolium/blob/main/pkg/jsext/vigolium.d.ts) ### Setup ```yaml theme={null} # vigolium-configs.yaml dynamic-assessment: extensions: enabled: true extension_dir: ~/.vigolium/extensions/ variables: auth_token: "Bearer eyJ..." ``` Drop `.js` files into `~/.vigolium/extensions/` and verify with `vigolium extensions ls`. ### Pros * **No recompilation**: drop a file and scan. * **Full API access**: HTTP, database, AI, source code, parsing, and system utilities. * **AI-augmented scanning**: use `vigolium.agent.generatePayloads()` and `vigolium.agent.analyzeResponse()` for LLM-powered detection. * **Rapid iteration**: edit, save, rescan. * **Sandboxed execution**: file I/O constrained to `sandbox_dir`, `exec()` gated behind config. ### Cons * **Slower than Go**: interpreted JS engine adds overhead per invocation. * **No Go standard library**: limited to `vigolium.*` APIs, no arbitrary imports. * **Single-threaded per VM**: each extension instance runs in its own VM (thread-safe via pooling, but no parallelism within a single extension). * **Limited debugging**: no step-through debugger, `vigolium.log.*` is your main tool. ### When to use * You need a custom vulnerability check and don't want to recompile. * You want AI-augmented payload generation or response analysis. * You need database or source code access in your check logic. * You're building organization-specific checks (e.g., custom header validation, business-logic flaws). See the full guide: [Writing Extensions](/customization/writing-extensions) *** ## 2. YAML Extensions YAML extensions (`.vgm.yaml`) are a declarative alternative to JavaScript. They're ideal for simple payload-and-matcher rules where you don't need programmatic control flow. ### Minimal example (active module) ```yaml theme={null} id: error-pattern-detector name: Error Pattern Detector type: active severity: low confidence: firm scan_types: [per_request] payloads: - "'" - "\" OR 1=1--" matchers: - type: body regex: "(?i)(SQL syntax|mysql_fetch|ORA-\\d{5}|SQLSTATE\\[)" - type: body regex: "(?i)(Traceback \\(most recent call last\\)|at \\w+\\.java:\\d+)" matchers_condition: or finding: name: "Error-Based Information Leak" description: "Application returns verbose error messages that reveal implementation details" severity: low ``` ### YAML hook example (pre-hook) ```yaml theme={null} id: auth-header-injector name: Auth Header Injector type: pre_hook add_headers: Authorization: "Bearer ${AUTH_TOKEN}" X-Request-ID: "vgm-{{random}}" skip_when: url_contains: ["/health", "/metrics"] ``` ### YAML hook example (post-hook) ```yaml theme={null} id: suppress-low-on-static name: Suppress Low Findings on Static Assets type: post_hook drop_when: severity: [low, info] url_contains: ["/static/", "/assets/", ".css", ".js"] escalate: when_url_contains: ["/admin", "/api/v1/auth"] bump_severity: true tag: sensitive_endpoint ``` ### Supported features | Feature | Description | | -------------------- | -------------------------------------------------------------- | | `payloads` | List of strings injected per insertion point or request | | `matchers` | Body regex/contains, header checks, status codes, or inline JS | | `matchers_condition` | `or` (any matcher) or `and` (all matchers) | | `add_headers` | Pre-hook: headers to inject | | `skip_when` | Pre-hook: conditions to skip processing | | `drop_when` | Post-hook: conditions to discard findings | | `escalate` | Post-hook: bump severity, add tags | | `script` | Inline JS escape hatch for complex logic | ### Pros * **Zero coding**: pure declarative YAML. * **Fast to write**: a payload list + matcher regex is often all you need. * **Easy to audit**: non-technical team members can review rules. * **Same pipeline integration**: loaded alongside JS extensions, same lifecycle. ### Cons * **Limited logic**: no conditionals, loops, or state beyond what matchers offer. * **No API access**: no database queries, HTTP follow-ups, or AI calls (unless you use the `script` escape hatch, which is effectively JS). * **No multi-step checks**: can't chain requests or compare responses across steps. * **Coarser insertion control**: payload injection is straightforward but you can't dynamically generate payloads based on context. ### When to use * Simple signature-based detection (error strings, header patterns, status codes). * Quick pre-hook rules (add auth headers, skip static assets). * Post-hook filtering (suppress low-severity findings on static paths). * When non-developers need to contribute scanning rules. See the full guide: [Writing Extensions](/customization/writing-extensions) *** ## 3. Custom Prompt Templates Prompt templates drive Vigolium's agent mode. They're Markdown files with YAML frontmatter that define what an AI agent should analyze and how it should report results. Templates support Go template syntax and are automatically enriched with context from the database, module registry, and source code. ### Template format ````markdown theme={null} --- id: my-custom-review name: My Custom Review description: What this template does output_schema: findings # or: http_records variables: - SourceCode - Language - PreviousFindings --- You are a security engineer. Analyze the following code for {{.Language}} vulnerabilities. {{if .PreviousFindings}} Previous findings to verify: {{.PreviousFindings}} {{end}} Source code: ``` {{.SourceCode}} ``` Respond with JSON: {"findings": [...]} ```` ### Available template variables | Variable | Source | Description | | --------------------- | ---------------------------------------------------------- | --------------------------------------- | | `SourceCode` | Gathered from `--source`/`--files` | Concatenated source code | | `Language` | Auto-detected | Primary language (Go, Python, JS, etc.) | | `Framework` | Auto-detected (empty unless the engine identifies one) | Framework hint | | `FilePath` | Gathered | Primary file path | | `SourcePath` | `--source` flag | Source code root path | | `TargetURL` | `--target` flag | Target URL | | `Hostname` | Derived from target | Hostname for DB lookups | | `Endpoints` | Populated by the calling mode (swarm/autopilot plan input) | Pre-discovered endpoints | | `PreviousFindings` | Database (JSON) | Prior findings for context | | `DiscoveredEndpoints` | Database (JSON) | HTTP records from DB | | `ModuleList` | Module registry (JSON) | Available scanner modules | | `ScanStats` | Database (JSON) | Aggregate scan statistics | | `AvailableCommands` | Hardcoded reference | CLI commands the agent can invoke | | `Extra` | Filled in by the calling mode (e.g. `agent triage`) | Custom key-value pairs | Only variables listed in the frontmatter `variables` array trigger database queries, keeping prompts fast. ### Output schemas **`findings`**: for code review, vulnerability detection: ```json theme={null} { "findings": [{ "title": "SQL Injection in login handler", "severity": "critical", "confidence": "certain", "file": "auth/login.go", "line": 42, "snippet": "db.Query(\"SELECT * FROM users WHERE id=\" + userID)", "cwe": "CWE-89", "tags": ["sqli"] }] } ``` **`http_records`**: for endpoint discovery, API input generation: ```json theme={null} { "http_records": [{ "method": "POST", "url": "https://api.example.com/users", "headers": {"Content-Type": "application/json"}, "body": "{\"name\": \"test\"}", "notes": "Create user endpoint" }] } ``` ### Preset templates Vigolium ships with 15 built-in templates: | Template | Schema | Purpose | | ----------------------- | ------------- | -------------------------------------------- | | `security-code-review` | findings | General OWASP-focused code review | | `injection-sinks` | findings | Identify injection sinks (SQLi, cmd, SSRF) | | `auth-bypass` | findings | Authentication/authorization bypass patterns | | `secret-detection` | findings | Hardcoded secrets and credentials | | `endpoint-discovery` | http\_records | Extract API routes from source code | | `api-input-gen` | http\_records | Generate HTTP requests from endpoints | | `curl-command-gen` | http\_records | Generate curl commands for all routes | | `interactive-scan` | findings | Autopilot: analyze + run scans | | `targeted-retest` | findings | Autopilot: verify previous findings | | `attack-surface-mapper` | http\_records | Discover and cross-reference APIs | | `nextjs-security-audit` | findings | Next.js-specific security review | | `react-xss-audit` | findings | React XSS pattern analysis | | `auth-session-review` | findings | Auth and session management | | `cors-csrf-review` | findings | CORS/CSRF configuration review | | `build-config-audit` | findings | Build/deployment config security | ### Setup Place custom templates in `~/.vigolium/prompts/` or set `agent.templates_dir` in config. User templates override built-in ones by ID. ```bash theme={null} # Use a custom template vigolium agent query --prompt-template my-custom-review --source /path/to/source # Dry-run to see the rendered prompt vigolium agent query --prompt-template my-custom-review --source /path/to/source --dry-run ``` ### Pros * **No code**: Markdown files with template syntax. * **Context-aware**: automatic enrichment with database findings, endpoints, scan stats. * **Multiple AI backends**: works with any of the eight olium providers (Anthropic API/OAuth/CLI/Vertex, OpenAI API/Codex-OAuth, Google Vertex, OpenAI-compatible local models). * **Iterative refinement**: autopilot and pipeline modes pass prior findings back for verification. * **Two output modes**: emit findings for code review or HTTP records for endpoint discovery. ### Cons * **AI dependency**: requires a configured agent backend and API access. * **Non-deterministic**: LLM output varies between runs; false positives require tuning. * **Latency**: agent invocations are slower than pattern matching (seconds to minutes per run). * **Token costs**: large codebases consume significant tokens per analysis. ### When to use * Code-level security review that needs semantic understanding (not just pattern matching). * Generating HTTP test inputs from source code (route extraction, API fuzzing seeds). * Framework-specific audits (Next.js, React, Django, Spring) where templates can embed domain knowledge. * Iterative analysis where the agent refines findings across multiple passes. *** ## 4. Scanning Profiles Profiles are YAML files that overlay on top of the main configuration. They bundle scanning strategy, pace, phase settings, and module selection into a reusable preset. ### Format A profile is a subset of `vigolium-configs.yaml`. Only non-nil values override the base config. ```yaml theme={null} # ~/.vigolium/profiles/aggressive.yaml # description: Fast aggressive scan for CI/CD pipelines scanning_strategy: default_strategy: deep scanning_pace: concurrency: 100 rate_limit: 200 max_per_host: 20 audit: concurrency: 100 max_duration: 60m discovery: mode: files_and_dirs recursion: enabled: true max_depth: 8 spidering: max_depth: 0 # 0 = unlimited (the default is 6) headless: true strategy: aggressive audit: enabled_modules: active_modules: - all passive_modules: - all ``` ### Usage ```bash theme={null} # Use a named profile vigolium scan --target https://example.com --scanning-profile aggressive # Use a profile file path vigolium scan --target https://example.com --scanning-profile /path/to/profile.yaml ``` Profiles are resolved from `~/.vigolium/profiles/` or `public/presets/profiles/` by name. ### Pros * **Reusable presets**: define once, use across targets and teams. * **Composable**: overlay on top of base config; only override what you need. * **No code**: pure YAML. * **Team-friendly**: share profiles in version control for consistent scan policies. ### Cons * **Config-only**: can't add new scanning logic, only tune existing settings. * **No per-target logic**: same profile applies to all targets in a scan. * **Limited validation**: typos in field names silently ignored. ### When to use * You run different scan intensities for different contexts (CI/CD vs. full audit vs. quick check). * You want to enforce consistent scan settings across a team. * You need to toggle phases (e.g., skip discovery, only run passive modules). *** ## 5. Scope Rules Scope rules control what gets scanned. They filter at the host, path, status code, content type, and body level. You can define them in the config, via CLI flags, or programmatically from JS extensions. ### Configuration ```yaml theme={null} # vigolium-configs.yaml scope: applied_on_ingest: false # enforce at ingest time or scan time host: include: ["*.example.com", "api.example.com"] exclude: ["staging.example.com"] path: include: ["/api/*"] exclude: ["/api/health", "/api/metrics", "/static/*"] status_code: include: ["2xx", "3xx"] # exact, wildcard (2xx), range (400-499) exclude: ["404"] request_content_type: include: ["application/json*", "application/x-www-form-urlencoded*"] response_content_type: include: ["text/html*", "application/json*"] exclude: ["image/*", "font/*"] ignore_static_file: true # auto-skip .jpg, .png, .css, etc. ``` ### Scope from extensions ```javascript theme={null} // Check scope programmatically if (vigolium.scan.isInScope("api.example.com", "/users")) { // proceed } // Read current scope var scope = vigolium.scan.getScope(); // Modify scope at runtime vigolium.scan.setScope({ host: { include: ["*.example.com"], exclude: [] }, path: { include: ["/api/*"], exclude: [] } }); ``` ### Pros * **Precision targeting**: scan only what matters, skip noise. * **Multiple filter types**: host globs, path patterns, status codes, content types, body strings. * **Runtime adjustable**: extensions can modify scope during a scan. * **Safety net**: prevents accidental scanning of out-of-scope systems. ### Cons * **Config-only complexity**: complex scope rules can be hard to debug. * **No request-level conditions**: you can't scope by request header values or authentication state (use pre-hooks for that). ### When to use * Restricting scans to specific subdomains or API paths. * Excluding health checks, static assets, or third-party endpoints. * Bug bounty programs with defined scope boundaries. * Filtering by response characteristics (status codes, content types). *** ## 6. Pre-Hooks and Post-Hooks Hooks wrap the scanning pipeline. Pre-hooks transform requests before modules process them. Post-hooks filter or modify findings after detection. ### Pre-hook use cases | Use case | Implementation | | ------------------- | ----------------------------------------------------- | | Inject auth headers | YAML `add_headers` or JS returning `{headers: {...}}` | | Skip static assets | YAML `skip_when.url_contains` or JS returning `null` | | Add correlation IDs | JS generating unique IDs per request | | Rewrite URLs | JS modifying `ctx.request` before forwarding | ### Post-hook use cases | Use case | Implementation | | ------------------------------------- | -------------------------------------------- | | Suppress low-severity on static paths | YAML `drop_when` | | Escalate findings on admin endpoints | YAML `escalate.when_url_contains` | | Tag findings by business unit | JS adding metadata based on URL patterns | | AI-powered false positive filtering | JS calling `vigolium.agent.confirmFinding()` | ### JS pre-hook example ```javascript theme={null} module.exports = { id: "inject-session", name: "Session Injector", type: "pre_hook", execute: function(request) { return { headers: { "Cookie": "session=" + vigolium.config.session_token, "X-Correlation-ID": vigolium.utils.randomString(16) } }; } }; ``` ### JS post-hook example (AI false positive filter) ```javascript theme={null} module.exports = { id: "ai-fp-filter", name: "AI False Positive Filter", type: "post_hook", execute: function(result) { if (typeof vigolium.agent === "undefined") return result; var check = vigolium.agent.confirmFinding({ name: result.info.name, request: result.request, response: result.response, matched: result.matched }); if (!check.confirmed && check.confidence !== "low") { vigolium.log.info("Suppressed FP: " + result.info.name); return null; // drop the finding } return result; } }; ``` ### Pros * **Pipeline integration**: runs automatically on every request/finding. * **Composable**: multiple hooks chain sequentially. * **Both JS and YAML**: simple rules in YAML, complex logic in JS. * **Non-invasive**: doesn't modify module code. ### Cons * **Sequential overhead**: hooks run on the hot path; slow hooks slow everything. * **Order-dependent**: hook execution order matters but isn't always obvious. * **Pre-hooks can't see responses**: they only have access to the outbound request. ### When to use * You need to inject authentication into every request (pre-hook). * You want to suppress known false positives across all modules (post-hook). * You need to tag or route findings based on URL patterns (post-hook). * AI-powered confirmation of findings before reporting (post-hook). See the full guide: [Writing Extensions](/customization/writing-extensions) *** ## 7. Agent Providers (Olium) Every agent invocation in Vigolium is dispatched through the in-process **olium** runtime (`pkg/olium/`). There are no external SDK or ACP subprocess backends, instead you choose a *provider* (the LLM API olium dispatches to) and configure its credential. ### Built-in providers Default provider is `openai-compatible` with model `gemma4:latest` (a local Ollama endpoint), so a freshly initialized config works against `http://localhost:11434/v1` out of the box. Pick a hosted provider when you want a frontier model: | Provider | Typical model | Credential source | | ------------------------------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `openai-codex-oauth` | `gpt-5.5` | `oauth_cred_path` (`~/.codex/auth.json`, produced by `codex login`) | | `anthropic-api-key` | `claude-opus-4-7` | `llm_api_key` or `$ANTHROPIC_API_KEY` | | `anthropic-oauth` | `claude-opus-4-7` | `oauth_token` from `claude setup-token` (or `$ANTHROPIC_API_KEY`) | | `openai-api-key` | `gpt-5.5` | `llm_api_key` or `$OPENAI_API_KEY` | | `openai-responses` | `gpt-5.5` | `llm_api_key` or `$OPENAI_API_KEY` (public OpenAI Responses API, `/v1/responses`) | | `anthropic-cli` *(alias `anthropic-claude-cli`)* | `claude-opus-4-7` | shells out to the local `claude` binary on `$PATH` | | `anthropic-claude-sdk-bridge` | Claude Code default | logged-in Claude Code subscription via the `vigolium-audit bridge` sidecar (no key); `bridge_binary` / `--bridge-bin` override | | `anthropic-compatible` | via `custom_provider.model_id` | `custom_provider.base_url` (Anthropic Messages `/v1/messages` gateway / proxy) | | `anthropic-vertex` | `claude-opus-4-6` | GCP service-account JSON (or `$GOOGLE_APPLICATION_CREDENTIALS`) + project/location | | `google-vertex` | `gemini-2.5-pro` | same GCP creds; routes `gemini-*` models | | `openai-compatible` *(default)* | `gemma4:latest` | `custom_provider.base_url` (Ollama / OpenRouter / LM Studio / vLLM / Groq / …); `api_key` optional | ### Configuring a provider ```yaml theme={null} # vigolium-configs.yaml agent: olium: provider: anthropic-api-key model: claude-opus-4-7 llm_api_key: ${ANTHROPIC_API_KEY} reasoning_effort: medium # codex providers only max_tokens: 1000000 max_turns: 32 max_concurrent: 4 # 0/unset = default 4; negative = unbounded call_timeout_sec: 600 autopilot_mode: legacy # legacy | shadow | enforced ``` Override per-run from any agent subcommand: ```bash theme={null} vigolium agent query --prompt-template security-code-review --source ./app \ --provider anthropic-api-key --model claude-opus-4-7 vigolium agent autopilot -t https://example.com \ --provider anthropic-oauth --oauth-token "$ANTHROPIC_API_KEY" vigolium ol --provider openai-api-key --llm-api-key "$OPENAI_API_KEY" ``` > **Server-side workloads:** the REST API does **not** mirror these per-invocation flags. The server resolves the provider once from `agent.olium.*` and reuses it across requests so prompt caches stay stable. ### Tool registry The olium engine exposes eight built-in tools to the model: `bash`, `read_file`, `write_file`, `edit_file`, `ls`, `grep`, `glob`, `web_fetch`. Autopilot adds `halt_scan`, durable scratchpad tools, and a mode-dependent finding tool: `report_finding` in `legacy`/`shadow`, or verifier-gated `propose_candidate` in `enforced`. See [Olium Agent](/agentic-scan/olium) for the full tool reference. ### LLM config (for JS extensions) The `vigolium.agent.*` APIs available to JavaScript extensions dispatch through the same in-process **olium** engine that powers the `vigolium agent` subcommands. Configure the provider once under `agent.olium` — see [Olium Agent](/agentic-scan/olium) for the full provider/model options. > **Note:** The legacy `agent.llm` config block is **deprecated and ignored**. The JS extension agent API now resolves its provider from `agent.olium`; remove any `agent.llm` section from your config. ### Pros * **Eleven providers**: pick the credential model that fits (OAuth for ChatGPT Plus / Claude Max, API keys for raw API access — Chat Completions or the OpenAI Responses API — CLI shellout or the Claude Code Agent-SDK bridge for a logged-in Claude subscription, Vertex for GCP, OpenAI- or Anthropic-compatible for local/self-hosted models). * **Single in-process runtime**: no subprocess startup overhead, prompt caching is reused across phases that share an engine (Anthropic + Codex providers only). * **Per-invocation overrides**: every CLI subcommand exposes `--provider`, `--model`, `--oauth-cred`, `--oauth-token`, `--llm-api-key`. * **Global concurrency cap**: `max_concurrent` keeps tier-1 plans from hitting 429s under fan-out. * **Durable autopilot**: `autopilot_mode: shadow` rotates context while preserving direct reports; `enforced` promotes only independently verified candidates. `legacy` remains the default. ### Cons * **Provider lock-in to supported drivers**: only the eleven drivers above are recognised; arbitrary custom CLIs are not pluggable as backends anymore. * **Cost**: LLM API calls still have token costs; the `--intensity` preset (autopilot) caps the iteration budget — `quick` is the lightest, `deep` the heaviest. ### When to use * Always, every agent subcommand goes through olium. The choice you actually make is **which provider and model**, not whether to use olium. *** ## 8. Configuration Overrides The main `vigolium-configs.yaml` is the central control plane for all scan behavior. Every aspect of the scanner, phases, pace, modules, database, notifications, and more, is configurable. ### Key configuration sections | Section | What it controls | | -------------------- | ---------------------------------------------------------------------------------- | | `scanning_strategy` | Phase presets: `lite`, `balanced`, `deep` | | `scanning_pace` | Concurrency, rate limits, per-phase duration caps | | `dynamic-assessment` | Active/passive module selection, extension config | | `discovery` | Content discovery mode, recursion, wordlists | | `spidering` | Browser crawling depth, strategy, headless mode | | `scope` | Host/path/status/content-type filtering | | `database` | SQLite or PostgreSQL, connection settings | | `notify` | Alert routing: Telegram, Discord, severity filters | | `mutation_strategy` | Payload mutation modes, field-type defaults | | `oast` | Out-of-band testing server, blind XSS config | | `agent` | Olium provider, LLM config (JS extensions), audit harness, browser, context limits | | `storage` | S3-compatible cloud storage for source uploads and result archival | ### Environment variable expansion ```yaml theme={null} database: postgres: password: ${VIGOLIUM_DB_PASSWORD} agent: llm: api_key_env: ANTHROPIC_API_KEY ``` ### Pros * **Comprehensive**: controls every scanner behavior. * **Environment-aware**: variable expansion for secrets. * **Layered**: base config + profiles + CLI flags, in order of precedence. ### Cons * **No logic**: pure configuration, can't express conditional behavior. * **Silent failures**: unrecognized keys are ignored, not flagged. * **Single file**: large configs can become unwieldy. ### When to use * Tuning scan speed and resource usage for your infrastructure. * Selecting which modules run by default. * Configuring database, notifications, and OAST. * Setting organization-wide defaults. *** ## Decision Matrix Use this table to quickly pick the right extension mechanism: | I want to... | Use | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | Add a custom vulnerability check (simple patterns) | [YAML Extension](#2-yaml-extensions) | | Add a custom vulnerability check (complex logic) | [JS Extension](#1-javascript-extensions) | | Run AI code review with custom focus areas | [Prompt Template](#3-custom-prompt-templates) | | Create a reusable scan preset for my team | [Scanning Profile](#4-scanning-profiles) | | Restrict scanning to specific hosts/paths | [Scope Rules](#5-scope-rules) | | Inject auth headers into every request | [Pre-Hook](#6-pre-hooks-and-post-hooks) | | Suppress false positives across all modules | [Post-Hook](#6-pre-hooks-and-post-hooks) | | Use AI to confirm findings before reporting | [Post-Hook + agent API](#6-pre-hooks-and-post-hooks) | | Pick a different LLM provider/model | [Agent Providers (Olium)](#7-agent-providers-olium) | | Tune concurrency and rate limits | [Config Override](#8-configuration-overrides) or [Profile](#4-scanning-profiles) | | Generate HTTP test inputs from source code | [Prompt Template](#3-custom-prompt-templates) (with `http_records` schema) | | Build an organization-specific scanner pipeline | Combine: [Profile](#4-scanning-profiles) + [Scope](#5-scope-rules) + [Hooks](#6-pre-hooks-and-post-hooks) + [Extensions](#1-javascript-extensions) | # Writing Extensions Source: https://docs.vigolium.com/customization/writing-extensions Guide to writing custom scanning extensions for Vigolium in JavaScript, YAML, quick checks, and snippets. Extensions let you add custom scanning logic to Vigolium without modifying the core scanner. You can write them in **JavaScript** for full flexibility, in **YAML** for declarative pattern matching, or use lightweight **quick checks** and **snippets** for fast iteration. *** ## Table of Contents * [Overview](#overview) * [Setup](#setup) * [Extension Types](#extension-types) * [Writing a JavaScript Extension](#writing-a-javascript-extension) * [Active Module](#active-module-js) * [Passive Module](#passive-module-js) * [Pre-Hook](#pre-hook-js) * [Post-Hook](#post-hook-js) * [Writing a YAML Extension](#writing-a-yaml-extension) * [Active Module](#active-module-yaml) * [Passive Module](#passive-module-yaml) * [Pre-Hook](#pre-hook-yaml) * [Post-Hook](#post-hook-yaml) * [Quick Checks](#quick-checks) * [Snippets](#snippets) * [Context Objects Reference](#context-objects-reference) * [API Reference](#api-reference) * [Testing Your Extension](#testing-your-extension) * [Configuration Reference](#configuration-reference) * [Tips and Best Practices](#tips-and-best-practices) *** ## Overview Extensions plug into the scanner pipeline at four points: | Type | Runs when | Use for | | ----------- | --------------------------- | -------------------------------------------- | | `active` | During audit phase | Send payloads, detect vulnerabilities | | `passive` | Analyzing captured traffic | Inspect request/response without new traffic | | `pre_hook` | Before each request is sent | Modify requests, skip assets, inject headers | | `post_hook` | After a finding is emitted | Escalate severity, drop false positives | All four types are supported in JS, YAML, and (for active/passive) as quick checks or snippets. YAML is simpler for straightforward pattern matching. JS gives you full access to HTTP requests, regex, encoding utilities, the database API, OAST (out-of-band) testing, and optional AI-augmented analysis. Quick checks and snippets are even lighter, ideal for agent-generated or ad-hoc checks. *** ## Setup ### 1. Enable extensions in your config Add or uncomment the `extensions` block under `dynamic-assessment` in your `vigolium-configs.yaml`: ```yaml theme={null} dynamic-assessment: extensions: enabled: true extension_dir: ~/.vigolium/extensions/ # scan this dir for .js and .vgm.yaml files custom_dir: [] # explicit extra paths variables: auth_token: "Bearer eyJ..." # accessible as vigolium.config.auth_token limits: timeout: 30s max_memory_mb: 128 ``` ### 2. Place your extension file Drop any `.js` or `.vgm.yaml` file into your `extension_dir`. Vigolium discovers them automatically on the next scan. ### 3. Verify it loaded ```bash theme={null} vigolium extensions ls ``` *** ## Extension Types ### Module export contract (JS) Every JS extension must export a `module.exports` object. Required fields: | Field | Required | Description | | ------------- | ----------------------- | --------------------------------------------------------------------------- | | `id` | No (auto from filename) | Unique identifier | | `type` | **Yes** | `active`, `passive`, `pre_hook`, `post_hook` | | `name` | No | Display name | | `description` | No | What the extension does | | `severity` | For active/passive | `critical`, `high`, `medium`, `low`, `info`, `suspect` | | `confidence` | No | `tentative`, `firm`, `certain` | | `scanTypes` | For active/passive | `["per_insertion_point"]`, `["per_request"]`, `["per_host"]` | | `tags` | No | Classification tags for `--module-tag` filtering (e.g. `["custom", "xss"]`) | *** ## Writing a JavaScript Extension JS extensions run inside an embedded Sobek (ES2015+) VM. The global `vigolium` object provides all APIs. ### Active Module (JS) Active modules send modified requests to probe for vulnerabilities. Declare which scan granularity you need in `scanTypes`: * `per_insertion_point`: called once per parameter (query, body, header, cookie) * `per_request`: called once per request/response pair * `per_host`: called once per unique hostname **per\_insertion\_point example**: detect reflected input: ```javascript theme={null} // File: ~/.vigolium/extensions/reflected_param_scanner.js module.exports = { id: "reflected-param", name: "Reflected Parameter Scanner", type: "active", severity: "medium", confidence: "firm", tags: ["custom", "xss", "reflection"], scanTypes: ["per_insertion_point"], scanPerInsertionPoint: function(ctx, insertion) { // Generate a unique canary var canary = "VGNM" + vigolium.utils.randomString(8); // Build and send a request with the canary injected var req = insertion.buildRequest(canary); var resp = vigolium.http.send(req); if (!resp || !resp.body) return null; // Check if the canary appears in the response if (resp.body.indexOf(canary) !== -1) { return [{ matched: canary, url: ctx.request.url, name: "Reflected parameter: " + insertion.name, description: "Parameter '" + insertion.name + "' is reflected without encoding", severity: "medium" }]; } return null; } }; ``` **per\_request example**: detect error messages in existing responses: ```javascript theme={null} // File: ~/.vigolium/extensions/error_pattern_detector.js module.exports = { id: "error-pattern-detector", name: "Error Pattern Detector", type: "active", severity: "low", confidence: "firm", scanTypes: ["per_request"], scanPerRequest: function(ctx) { if (!ctx.response || !ctx.response.body) return null; var body = ctx.response.body; var patterns = [ { regex: /Traceback \(most recent call last\)/i, name: "Python traceback" }, { regex: /goroutine \d+ \[running\]/i, name: "Go panic stack trace" }, { regex: /SQLSTATE\[/i, name: "SQL error (SQLSTATE)" }, { regex: /Fatal error:.*on line \d+/i, name: "PHP fatal error" } ]; var findings = []; for (var i = 0; i < patterns.length; i++) { if (patterns[i].regex.test(body)) { findings.push({ matched: patterns[i].name, url: ctx.request.url, name: "Error pattern: " + patterns[i].name, description: "Response contains a " + patterns[i].name, severity: "low" }); } } return findings.length > 0 ? findings : null; } }; ``` **Return value for active/passive:** an array of finding objects, or `null` if nothing found. Each finding object: ```javascript theme={null} { matched: "...", // what triggered the finding (shown in output) url: "...", // full URL name: "...", // finding title description: "...", // detailed description severity: "medium" // overrides module severity if set } ``` *** ### Passive Module (JS) Passive modules analyze existing request/response pairs without making new requests. Add a `scope` field to limit to `"request"`, `"response"`, or `"both"` (default). ```javascript theme={null} // File: ~/.vigolium/extensions/sensitive_header_leak.js module.exports = { id: "sensitive-header-leak", name: "Sensitive Header Leak", type: "passive", severity: "info", confidence: "certain", scope: "response", scanTypes: ["per_request"], scanPerRequest: function(ctx) { if (!ctx.response || !ctx.response.headers) return null; var findings = []; var headers = ctx.response.headers; var poweredBy = headers["X-Powered-By"] || headers["x-powered-by"]; if (poweredBy) { findings.push({ matched: "X-Powered-By: " + poweredBy, url: ctx.request.url, name: "X-Powered-By header exposed", description: "Server technology revealed: " + poweredBy, severity: "info" }); } return findings.length > 0 ? findings : null; } }; ``` *** ### Pre-Hook (JS) Pre-hooks run before each request is sent to a module. Return the modified request, a headers-only patch, or `null` to skip the request entirely. ```javascript theme={null} // File: ~/.vigolium/extensions/add_auth_header.js module.exports = { id: "add-auth-header", name: "Auth Header Injector", type: "pre_hook", execute: function(request) { var token = vigolium.config.auth_token || ""; if (token === "") { return request; // pass through unchanged } // Return a headers patch — these are merged into the existing request return { headers: { "Authorization": "Bearer " + token, "X-Correlation-ID": vigolium.utils.randomString(12) } }; } }; ``` Return value options: | Return | Effect | | --------------------- | --------------------------------------- | | `request` (unchanged) | Pass through as-is | | `{ headers: {...} }` | Merge these headers into the request | | `{ raw: "GET /..." }` | Replace the entire raw request | | `null` | Skip this request (module won't see it) | **Skip static assets example:** ```javascript theme={null} // File: ~/.vigolium/extensions/skip_static_assets.js module.exports = { id: "skip-static-assets", type: "pre_hook", execute: function(request) { var path = request.path || ""; var skip = [".css", ".js", ".png", ".jpg", ".gif", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".map"]; for (var i = 0; i < skip.length; i++) { if (path.endsWith(skip[i])) return null; } return request; } }; ``` *** ### Post-Hook (JS) Post-hooks receive each emitted finding. Return the (possibly modified) result, or `null` to suppress the finding. ```javascript theme={null} // File: ~/.vigolium/extensions/tag_critical_domains.js module.exports = { id: "tag-critical-domains", name: "Critical Domain Tagger", type: "post_hook", execute: function(result) { if (!result || !result.url) return result; var url = result.url.toLowerCase(); var critical = ["payment", "admin", "auth", "checkout", "billing"]; for (var i = 0; i < critical.length; i++) { if (url.indexOf(critical[i]) !== -1) { var sev = result.info ? result.info.severity : "info"; var escalated = { info: "low", low: "medium", medium: "high", high: "critical" }[sev] || sev; return { url: result.url, matched: result.matched, info: { name: result.info.name + " [CRITICAL: " + critical[i] + "]", description: result.info.description, severity: escalated } }; } } return result; } }; ``` *** ## Writing a YAML Extension YAML extensions (`.vgm.yaml`) are a declarative alternative for common patterns. They require no programming knowledge and are compiled to the same internal module interface as JS extensions. ### Active Module (YAML) Use `rules` to define match-then-emit pairs. Each rule specifies a match condition and the finding to emit when it matches. ```yaml theme={null} # File: ~/.vigolium/extensions/error_patterns.vgm.yaml id: error-pattern-detector-yaml name: Error Pattern Detector (YAML) description: Detects stack traces and error messages in responses type: active severity: low confidence: firm tags: [custom, error-detection] scan_types: - per_request rules: - match: body_regex: "(?i)Traceback \\(most recent call last\\)" finding: name: "Error pattern: Python traceback" description: "Response body contains a Python traceback" severity: low - match: body_regex: "(?i)goroutine \\d+ \\[running\\]" finding: name: "Error pattern: Go panic stack trace" description: "Response body contains a Go panic stack trace" severity: low - match: body_regex: "(?i)SQLSTATE\\[" finding: name: "Error pattern: SQL error" description: "Response body contains a SQL SQLSTATE error" severity: low ``` **Top-level active fields:** | Field | Description | | -------------------- | ----------------------------------------------------------- | | `tags` | Classification tags for `--module-tag` filtering | | `scan_types` | `per_insertion_point`, `per_request`, `per_host` | | `payloads` | List of strings to inject (used with `per_insertion_point`) | | `matchers` | List of `MatcherDef`, all applied to the same finding | | `matchers_condition` | `or` (default) or `and`, how matchers combine | | `finding` | Single finding emitted when matchers pass | | `rules` | List of `{match, finding}` pairs, evaluated independently | Use `rules` when different patterns should emit different findings. Use `matchers` + `finding` when all conditions must be true together. **Matcher types:** ```yaml theme={null} matchers: # Check body contains a string - contains: "password" # Check body with regex - regex: "(?i)error|exception" # Check response header exists - type: header name: X-Powered-By # Check HTTP status code - type: status codes: [500, 502, 503] # Negate a condition - contains: "success" negate: true ``` *** ### Passive Module (YAML) Passive YAML modules use the same `rules` structure but do not send new requests: ```yaml theme={null} # File: ~/.vigolium/extensions/sensitive_headers.vgm.yaml id: sensitive-header-leak-yaml name: Sensitive Header Leak (YAML) type: passive severity: info confidence: certain scope: response # request | response | both scan_types: - per_request rules: - match: response_header: X-Powered-By finding: name: X-Powered-By header exposed description: "Server technology revealed via X-Powered-By header" matched: "{{matched}}" # interpolates the matched header value severity: info - match: response_header: Server regex: "[0-9]+\\.[0-9]+" # only match if value contains a version number finding: name: Server version disclosed description: "Server header exposes version information" matched: "{{matched}}" severity: low ``` **Rule match fields for `rules[].match`:** | Field | Description | | ----------------- | --------------------------------------------- | | `body_contains` | Response body contains string | | `body_regex` | Response body matches regex | | `response_header` | Response header name exists | | `regex` | Additional regex to apply to the header value | | `contains` | String the header value must contain | | `status` | List of HTTP status codes | *** ### Pre-Hook (YAML) Pre-hooks in YAML support header injection, extension skipping, and conditional skipping. **Inject headers:** ```yaml theme={null} # File: ~/.vigolium/extensions/add_auth.vgm.yaml id: add-auth-header-yaml name: Auth Header Injector (YAML) type: pre_hook # Skip this hook if the config variable is not set skip_when: config_empty: auth_token add_headers: Authorization: "Bearer {{config.auth_token}}" X-Correlation-ID: "{{rand(12)}}" ``` **Skip static files:** ```yaml theme={null} # File: ~/.vigolium/extensions/skip_static.vgm.yaml id: skip-static-assets-yaml name: Static Asset Skipper (YAML) type: pre_hook skip_extensions: - .css - .js - .png - .jpg - .gif - .svg - .ico - .woff - .woff2 - .ttf - .map ``` **Pre-hook YAML fields:** | Field | Description | | ------------------------ | ------------------------------------------------------ | | `add_headers` | Map of header name → value to inject | | `skip_extensions` | URL path suffixes that cause the request to be skipped | | `skip_when.config_empty` | Skip if this config variable is empty | | `skip_when.url_contains` | Skip if URL contains any of these strings | **Template functions available in header values:** | Template | Expands to | | --------------------- | -------------------------------------- | | `{{config.VAR_NAME}}` | User-defined config variable | | `{{rand(N)}}` | Random alphanumeric string of length N | *** ### Post-Hook (YAML) Post-hooks in YAML can escalate severity or drop findings based on URL patterns. **Escalate severity for critical paths:** ```yaml theme={null} # File: ~/.vigolium/extensions/critical_tagger.vgm.yaml id: tag-critical-domains-yaml name: Critical Domain Tagger (YAML) type: post_hook escalate: when_url_contains: - payment - admin - auth - checkout - billing tag: "CRITICAL" bump_severity: true # info→low, low→medium, medium→high, high→critical ``` **Drop low-severity findings on certain paths:** ```yaml theme={null} id: drop-noisy-findings type: post_hook drop_when: severity: - info url_contains: - /static/ - /assets/ ``` *** ## Quick Checks Quick checks are the lightest extension format, declarative JSON objects that define "send payload, check response" patterns with zero JavaScript. They're ideal for agent-generated checks and rapid iteration. ### Per Insertion Point Inject payloads into each parameter and check the response: ```json theme={null} { "id": "ssti-jinja2", "severity": "high", "scan": "per_insertion_point", "payloads": ["{{7*7}}", "${7*7}", "<%=7*7%>"], "match": {"body_contains": "49"} } ``` ### Per Request / Per Host Send specific requests and check responses: ```json theme={null} { "id": "debug-endpoint", "severity": "medium", "scan": "per_host", "requests": [ {"method": "GET", "path": "/.env"}, {"method": "GET", "path": "/debug/vars"} ], "match": {"status": 200, "body_regex": "(DB_PASSWORD|SECRET_KEY)"} } ``` ### Match Fields Match conditions use OR logic: | Field | Description | | ----------------- | ------------------------------- | | `body_contains` | Response body contains string | | `body_regex` | Response body matches regex | | `status` | HTTP status code equals value | | `header_contains` | Response header contains string | ### Rules * `id` must be lowercase with hyphens (e.g. `"ssti-jinja2"`) * `scan` is one of: `per_insertion_point`, `per_request`, `per_host` * `severity` is one of: `critical`, `high`, `medium`, `low`, `info` * Quick checks are automatically wrapped into full extension modules at runtime *** ## Snippets Snippets are a middle ground between quick checks and full extensions, you write just the **function body** (no boilerplate), and it gets wrapped in a module scaffold automatically. Use snippets when you need custom logic or `vigolium.*` API access. ### Format ```json theme={null} { "id": "idor-check", "severity": "high", "scan": "per_request", "body": "var related = vigolium.db.records.getRelated(ctx.record.uuid);\nvar cmp = vigolium.db.compareResponses(related);\nif (!cmp.all_similar) {\n return [{url: ctx.request.url, matched: 'Response variance', name: 'Potential IDOR'}];\n}\nreturn null;" } ``` ### Available Variables Inside the snippet body, you have access to: | Variable | Description | | --------------------- | ---------------------------------------------------------------------- | | `ctx` | Request/response context (`ctx.request`, `ctx.response`, `ctx.record`) | | `insertion` | Insertion point object (only for `per_insertion_point` scan type) | | `vigolium.http` | HTTP requests, sessions, batch, replay, sequence, auth testing | | `vigolium.db` | Database queries for records and findings | | `vigolium.utils` | Encoding, hashing, diff, JWT, CSS selectors, etc. | | `vigolium.parse` | URL, request, response, HTML parsing | | `vigolium.scan` | Module listing, scope, finding creation | | `vigolium.source` | Source code access and search | | `vigolium.agent` | AI-augmented analysis | | `vigolium.oast` | Out-of-band testing | | `vigolium.ingest` | Data ingestion | | `vigolium.payloads()` | Built-in payload wordlists | ### Rules * `id` must be lowercase with hyphens * `scan` is one of: `per_insertion_point`, `per_request`, `per_host` * `body` contains the function body as a string (newlines escaped as `\n`) * The return value follows the same convention as full extensions: array of findings or `null` *** ## Context Objects Reference ### `ctx`: passed to all active/passive module functions ```javascript theme={null} ctx.request.url // "https://example.com/api/users?id=1" ctx.request.method // "GET" ctx.request.path // "/api/users" ctx.request.hostname // "example.com" ctx.request.headers // { "Content-Type": "application/json", ... } ctx.request.raw // full raw HTTP request string ctx.response.status // 200 ctx.response.body // response body as string ctx.response.headers // { "X-Powered-By": "PHP/8.1", ... } ctx.response.raw // full raw HTTP response string ctx.response.title // HTML page title (if applicable) ``` ### `insertion`: second arg to `scanPerInsertionPoint` ```javascript theme={null} insertion.name // "id" (parameter name) insertion.baseValue // "1" (original value) insertion.type // "url_param" | "body_param" | "header" | "cookie" insertion.buildRequest(val) // returns raw request string with val injected ``` ### `ctx.record`: current HTTP record context ```javascript theme={null} ctx.record.uuid // database UUID of the current record ctx.record.annotate(patch) // update risk_score/remarks ctx.record.addRiskScore(delta) // increment risk score (can be negative, clamped to 0) ctx.record.addRemarks(remarks) // append remarks with deduplication ``` *** ## API Reference ### vigolium.log | Function | Description | | ------------ | ------------------- | | `info(msg)` | Log info message | | `warn(msg)` | Log warning message | | `error(msg)` | Log error message | | `debug(msg)` | Log debug message | ### vigolium.utils **Encoding:** | Function | Description | | ------------------------------------- | ------------------------- | | `base64Encode(s)` / `base64Decode(s)` | Base64 encode/decode | | `urlEncode(s)` / `urlDecode(s)` | URL encode/decode | | `htmlEncode(s)` / `htmlDecode(s)` | HTML entity encode/decode | **Hashing:** | Function | Description | | ----------- | ------------ | | `sha1(s)` | SHA-1 hash | | `sha256(s)` | SHA-256 hash | | `md5(s)` | MD5 hash | **Random:** | Function | Description | | ------------------- | -------------------------- | | `randomString(len)` | Random alphanumeric string | **Regex:** | Function | Description | | ---------------------------- | ---------------------------- | | `regexMatch(str, pattern)` | Test if string matches regex | | `regexExtract(str, pattern)` | Extract matches from string | **File I/O:** | Function | Description | | ----------------------- | --------------------------- | | `readFile(path)` | Read file contents | | `readLines(path)` | Read file as array of lines | | `writeFile(path, data)` | Write data to file | | `mkdir(path)` | Create directory | | `glob(pattern)` | Find files matching pattern | **URL utilities:** | Function | Description | | ------------------------- | --------------------------------------- | | `parse_url(url, format)` | Parse URL with format | | `pathToTemplate(path)` | Replace dynamic segments with `*` | | `hasDynamicSegment(path)` | Check for dynamic segments (IDs, UUIDs) | **Parameter utilities:** | Function | Description | | ------------------------ | ------------------------------------------------ | | `toSet(csv)` | Convert CSV string to `{key: true}` map | | `extractParamNames(str)` | Extract deduplicated param names from query/body | **Diff and similarity:** | Function | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `diff(a, b)` | Line-by-line comparison → `{added, removed, similarity}` | | `similarity(a, b)` | Jaccard similarity (0.0-1.0) on word tokens | | `diffResponses(a, b)` | Structural HTTP response comparison → `{status_match, body_similarity, header_diff, body_diff, length_diff, likely_same_content}` | **HTML:** | Function | Description | | --------------------------- | -------------------------------------------- | | `cssSelect(html, selector)` | CSS selector query → `[{text, attrs, html}]` | **Token extraction:** | Function | Description | | ------------------------------- | ------------------------------------------------------------------------------------- | | `extractToken(response, rules)` | Extract tokens from HTTP response using configurable rules (json/header/cookie/regex) | **JWT:** | Function | Description | | --------------------------- | ---------------------------------------------------------------- | | `jwtDecode(token)` | Decode JWT without verification → `{header, payload, signature}` | | `jwtEncode(payload, opts?)` | Forge JWT (HS256/HS384/HS512/none) | | `jwtExpired(token)` | Check if JWT is expired | **Multipart:** | Function | Description | | ------------------- | ------------------------------------------------------ | | `multipart(fields)` | Build multipart/form-data body → `{body, contentType}` | **Anomaly detection:** | Function | Description | | -------------------------- | -------------------------------------------------- | | `detectAnomaly(responses)` | Score responses by divergence → `[{index, score}]` | **Other:** | Function | Description | | -------------------------------------- | ---------------------------------------------------------------------------- | | `sleep(ms)` | Sleep for milliseconds | | `exec(cmd)` | Execute shell command (requires `allow_exec`) → `{stdout, stderr, exitCode}` | | `getEnv(name)` / `setEnv(name, value)` | Environment variables | | `jsonExtract(json, path)` | Extract value from JSON by path | ### vigolium.parse | Function | Description | | --------------- | ----------------------------------------------------------------------------------------------- | | `url(str)` | Parse URL → `{scheme, host, hostname, port, path, query, fragment, params, segments, template}` | | `request(raw)` | Parse raw HTTP request → `{method, path, query, version, headers, body, host, params, cookies}` | | `response(raw)` | Parse raw HTTP response → `{status, statusText, version, headers, body, cookies, contentType}` | | `headers(str)` | Parse header block → `{name: value}` | | `cookies(str)` | Parse Cookie header → `{name: value}` | | `query(str)` | Parse query string → `{name: value}` | | `json(str)` | Parse JSON string → native value | | `form(body)` | Parse URL-encoded form → `{name: value}` | | `html(str)` | Parse HTML → `{forms, links, scripts, meta}` | ### vigolium.http **Basic requests:** | Function | Description | | ------------------------------------- | ------------------------------------------------------------------- | | `get(url, opts?)` | HTTP GET | | `post(url, body, opts?)` | HTTP POST | | `request(opts)` | Full control (method, url, headers, body) | | `send(rawRequest)` | Send raw HTTP request string | | `buildRequest(rawRequest, overrides)` | Clone and modify a raw request (method, path, headers, body, query) | **Sessions:** | Function | Description | | ---------------------- | -------------------------------------------------------------------- | | `session(opts?)` | Create persistent session with shared cookie jar and default headers | | `login(opts)` | Send credentials, extract auth tokens, return authenticated session | | `sessionPool(configs)` | Create named session pool from config map | | `followAuth(opts)` | Execute OAuth2 flow (client\_credentials, password, code grants) | Session objects expose: `get()`, `post()`, `request()`, `send()`, `setHeader()`, `removeHeader()`, `getHeaders()`, `getCookies()`, `setCookie()`, `cloneAs()`, `onRequest()`, `onResponse()`, `setAutoRefresh()`. **Batch and replay:** | Function | Description | | -------------------------------- | ---------------------------------------------------------------------- | | `batch(requests, opts?)` | Send multiple requests in parallel (configurable concurrency) | | `replay(rawRequest, variations)` | Replay request with multiple variations (header overrides, body swaps) | **Multi-step workflows:** | Function | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | | `sequence(steps)` | Execute request sequence with variable extraction (`{{varName}}`), conditional execution, fallback steps, and repeat loops | **Auth testing:** | Function | Description | | ------------------ | ------------------------------------------------------------------------------------ | | `authTest(opts)` | Test IDOR/BOLA by replaying requests across sessions with different privilege levels | | `csrf(url, opts?)` | Extract CSRF token from page (form, meta, header, cookie sources) | **Retry and caching:** | Function | Description | | ----------------------- | -------------------------------------------------------------------------------------- | | `retry(request, opts?)` | Retry with configurable backoff (max retries, retry\_on status codes, until predicate) | | `cache(opts?)` | Enable response caching with TTL and max entries | | `clearCache()` | Clear all cached responses | | `cachedGet(url, opts?)` | GET with cache | | `cachedRequest(opts)` | Full request with cache | **GraphQL:** | Function | Description | | --------------------------- | --------------------------------------------------- | | `graphql(url, opts)` | Send GraphQL query/mutation → `{data, errors, raw}` | | `graphqlSchema(url, opts?)` | Fetch introspection schema | ### vigolium.scan | Function | Description | | -------------------------------- | ---------------------------------------- | | `listModules()` | List all registered modules | | `listModuleTags()` | List all unique module tags | | `listModulesByTag(tag)` | List modules with a specific tag | | `isInScope(host, path)` | Check if host/path is in scope | | `getScope()` / `setScope(scope)` | Get/set scope configuration | | `createFinding(finding)` | Persist a finding | | `getCurrentScan()` | Get current scan info | | `startNewScan(opts)` | Start a new scan | | `scanRecords(opts)` | Queue scan for existing records by UUIDs | ### vigolium.ingest | Function | Description | | ------------------------------- | --------------------------------- | | `url(url)` | Ingest a single URL | | `urls(content)` | Ingest multiple URLs from content | | `curl(command)` | Ingest from curl command | | `raw(rawRequest, rawResponse?)` | Ingest raw HTTP request/response | | `openapi(spec, opts?)` | Ingest from OpenAPI spec | | `postman(collection)` | Ingest from Postman collection | ### vigolium.source | Function | Description | | -------------------------------- | ------------------------------ | | `list(hostname?)` | List source repos | | `get(id)` | Get source repo by ID | | `getByHostname(hostname)` | Get repos by hostname | | `readFile(hostname, path)` | Read source file | | `listFiles(hostname, glob?)` | List source files | | `searchFiles(hostname, pattern)` | Search source files by pattern | ### vigolium.agent (AI-augmented) | Function | Description | | ------------------------ | ---------------------------------------------------------------------------------------------- | | `complete(opts)` | Full control: model, messages, schema, temperature → `{content, model, tokens_in, tokens_out}` | | `ask(prompt, opts?)` | Single prompt → text response | | `chat(messages, opts?)` | Conversation → text response | | `generatePayloads(opts)` | Generate context-aware security payloads by type, context, technology, WAF | | `analyzeResponse(opts)` | Analyze HTTP exchange for vulnerability → `{vulnerable, confidence, evidence, details}` | | `confirmFinding(opts)` | Verify true positive → `{confirmed, confidence, reasoning, false_positive_indicators}` | | `run(opts)` | Run an inline olium agent turn (in-process; uses the same provider as `vigolium agent olium`) | ### vigolium.oast (Out-of-Band Testing) | Function | Description | | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `enabled()` | Check if OAST service is active | | `payload(targetURL?, paramName?, injectionType?)` | Generate unique OAST callback URL → `{url}` | | `poll(timeoutMs?)` | Wait then return all OAST interactions → `[{protocol, unique_id, remote_address, target_url, parameter_name, module_id, interacted_at}]` | ### vigolium.db **Records:** | Function | Description | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `records.query(filters?)` | Query HTTP records with filters (hostname, path, methods, status\_codes, source, search, fuzzy, min\_risk\_score, limit, offset, sort) | | `records.get(uuid)` | Get single record by UUID | | `records.getRelated(uuid, opts?)` | Get records with same path template/hostname | | `records.annotate(uuid, patch)` | Update risk\_score/remarks | | `records.grouped(opts?)` | Group by path template for IDOR detection → `[{template, method, records, param_values}]` | **Findings:** | Function | Description | | ---------------------------- | ----------------------------------------------------------- | | `findings.query(filters?)` | Query findings (severity, module\_name, scan\_uuid filters) | | `findings.get(id)` | Get finding by ID | | `findings.getByRecord(uuid)` | Get findings for an HTTP record | | `findings.create(finding)` | Persist a new finding | **Comparison:** | Function | Description | | --------------------------- | ------------------------------------------------------------------------------------- | | `compareResponses(records)` | Anomaly detection across record set → `{all_similar, scores, variant_count, summary}` | ### vigolium.payloads(type) Returns built-in payload wordlists by vulnerability type. Types: `"xss"`, `"sqli"`, `"ssti"`, `"ssrf"`, `"lfi"`, `"path_traversal"`, `"xxe"`, `"cmdi"`, `"open_redirect"`, `"crlf"`. ```javascript theme={null} var payloads = vigolium.payloads("xss"); // ["", "", ...] ``` ### vigolium.config Read-only config values from the `variables` block in `vigolium-configs.yaml`: ```javascript theme={null} var token = vigolium.config.auth_token; var domain = vigolium.config.collaborator_domain || "oast.pro"; ``` *** ## Testing Your Extension ### Run only the extension phase The fastest way to test your extension against already-ingested traffic without running a full scan: ```bash theme={null} # Run only extensions against existing scan data vigolium scan --config vigolium-configs.yaml --only extension # Alias: ext works too vigolium scan --config vigolium-configs.yaml --only ext ``` This skips discovery, spidering, and standard audit modules, only your extensions run against traffic already in the database. ### Test against a live target To ingest fresh traffic and immediately run only extensions: ```bash theme={null} # Ingest a URL list and run extensions only vigolium scan -u targets.txt --only extension --config vigolium-configs.yaml # Ingest a single URL vigolium scan -u https://example.com --only extension --config vigolium-configs.yaml ``` ### Use a one-off config with a custom extension path You don't need to copy files to `~/.vigolium/extensions/`. Use `custom_dir` to point directly at your file: ```bash theme={null} # Point to your extension file via config or inline vigolium scan -u https://example.com \ --only extension \ --config ./my-test-config.yaml ``` With `my-test-config.yaml`: ```yaml theme={null} dynamic-assessment: extensions: enabled: true custom_dir: - ./my_extension.js - ./my_extension.vgm.yaml ``` ### Verify your extension loads Before running a scan, check your extension is discovered and parsed correctly: ```bash theme={null} # List all loaded extensions vigolium extensions ls # Filter by your extension's ID vigolium extensions ls my-extension-id # Show full description and confirmation criteria vigolium extensions ls --verbose # Filter by type vigolium extensions ls --type active vigolium extensions ls --type passive vigolium extensions ls --type pre_hook vigolium extensions ls --type post_hook ``` ### Browse the built-in API reference ```bash theme={null} # List all available vigolium.* API functions vigolium extensions docs # Filter to a specific function or namespace vigolium extensions docs http vigolium extensions docs randomString vigolium extensions docs regexMatch ``` ### Install preset examples to learn from ```bash theme={null} # Install all presets to ~/.vigolium/extensions/ vigolium extensions preset # Install a single preset vigolium extensions preset reflected_param_scanner ``` *** ## Configuration Reference Full `extensions` block options in `vigolium-configs.yaml`: ```yaml theme={null} dynamic-assessment: extensions: # Enable the extension engine. Default: false enabled: true # Directory scanned for .js and .vgm.yaml files # Default: ~/.vigolium/extensions/ extension_dir: ~/.vigolium/extensions/ # Additional explicit script paths (loaded in addition to extension_dir) custom_dir: - /path/to/my_scanner.js - /path/to/my_passive.vgm.yaml # Variables accessible as vigolium.config.* in scripts # Values support ${ENV_VAR} expansion variables: auth_token: "eyJhbGci..." collaborator_domain: "collab.example.com" api_key: "${MY_API_KEY}" # Resource limits per VM invocation limits: timeout: 30s # Maximum execution time max_memory_mb: 128 # Memory cap per VM # Allow extensions to run shell commands (exec) and set env vars # Default: false — enable only for trusted extensions allow_exec: false # Restrict file I/O to this directory (readFile, writeFile, glob) sandbox_dir: /tmp/vigolium-sandbox ``` *** ## Tips and Best Practices **Return `null`, not `[]`**: returning an empty array is treated the same as `null`, but `null` is the conventional no-finding signal. **Check for nil before accessing properties:** ```javascript theme={null} if (!ctx.response || !ctx.response.body) return null; ``` **Use `vigolium.utils.randomString` for canaries** to avoid collisions between concurrent extension invocations. **Keep pre-hooks fast**: they run on every request before any module sees it. Avoid HTTP calls inside pre-hooks. **YAML vs JS vs quick check decision guide:** * Use **quick checks** when you need simple payload-and-match patterns with no logic * Use **snippets** when you need `vigolium.*` API access but don't want full boilerplate * Use **YAML** when you need regex/header/status matching with a fixed finding output * Use **JS** when you need: conditional logic, multiple HTTP requests, encoding/decoding, database lookups, session management, or AI-augmented analysis **Scope your passive module**: set `scope: "response"` if you only need response data. This avoids unnecessary invocations. **Use `vigolium.config.*`** for secrets and environment-specific values instead of hardcoding them: ```javascript theme={null} var target = vigolium.config.collaborator_domain || "oast.pro"; ``` **Use built-in payloads** instead of hardcoding wordlists: ```javascript theme={null} var payloads = vigolium.payloads("xss"); ``` **Enable response caching** for extensions that make repeated baseline requests: ```javascript theme={null} vigolium.http.cache({ ttl_ms: 30000 }); var baseline = vigolium.http.cachedGet(url); ``` **Use sessions for multi-request flows**: sessions persist cookies and headers: ```javascript theme={null} var session = vigolium.http.session({ headers: { "Authorization": "Bearer " + token } }); session.get(url1); session.post(url2, body); // cookies from url1 are sent automatically ``` **Avoid hardcoding the extension id** if you plan to distribute extensions, the filename without extension is used as the default ID, which is usually fine. **Test incrementally**: start with `--only extension` and a small known dataset so your module's `console.log` output is easy to read. # Agentic Scanning Source: https://docs.vigolium.com/getting-started/agentic-scan Drive vulnerability scanning with AI, autopilot (autonomous operator), swarm (AI-guided pipeline), or query (single-shot prompts). All dispatched through the in-process olium runtime. Vigolium's agent mode runs vulnerability scans with an LLM in the loop. Three subcommands cover the spectrum from single-shot prompts to fully autonomous pentests: * **`vigolium agent query`**: one-shot prompt against code or a target. Code review, endpoint discovery, secret hunt. No scanning loop. * **`vigolium agent swarm`**: multi-phase pipeline where native Go does the heavy lifting and AI intervenes at checkpoints (planning attacks, triaging results, generating custom JS extensions). * **`vigolium agent autopilot`**: one autonomous operator with full tool access. It can run as a legacy conversation or as durable, resumable sections with candidate verification. All three dispatch through the in-process **olium** runtime, one unified provider interface, one conversation state. There are no subprocess SDK backends. Autopilot and Swarm are still maturing. We greatly appreciate feedback on false positives and bug reports. *** ## Prerequisites * A working olium provider (Claude / OpenAI / Codex / local). See [Setting Up the Agent](/getting-started/setup-agent). * Optional: a source-code path or git URL for source-aware scanning (`--source`). Smoke-test the provider before launching a real scan: ```bash theme={null} vigolium ol -p 'what model are you running' ``` If that returns a model name, you're wired up. *** ## Autopilot, autonomous AI pentest Hand the agent a target and walk away. It opens a shell, reads files, runs the vigolium CLI, decides which modules to invoke, and iterates until it calls `halt_scan` or hits the max-turns / max-duration cap. ```bash theme={null} # Pure black-box vigolium agent autopilot -t https://example.com # Natural-language prompt — target, source, focus auto-extracted vigolium agent autopilot "scan VAmPI source at ~/src/VAmPI on localhost:3005" # Source-aware — pairs blackbox runtime checks with whitebox code reading vigolium agent autopilot -t https://example.com --source ~/src/your-app # Focus on a vulnerability class vigolium agent autopilot -t https://example.com --prompt "focus on auth bypass and IDOR" # Authenticated scan (browser tooling is always available) vigolium agent autopilot -t https://app.example.com --intensity deep \ --prompt "log in as admin/admin123, then test every protected route" # Mine prior project/Burp traffic and supply application docs vigolium agent autopilot -t https://app.example.com \ --burp-bridge-url http://127.0.0.1:9009 \ --knowledge-base ./app-docs # Pipe a curl command in curl -s https://example.com/api/users | vigolium agent autopilot ``` ### Intensity presets | Preset | Max commands | Timeout | Audit mode (with `--source`) | Browser | | -------------------- | :----------: | :-----: | :--------------------------: | :-----: | | `quick` | 150 | 1h | `lite` | on | | `balanced` (default) | 500 | 6h | `balanced` | on | | `deep` | 1500 | 12h | `deep` | on | ```bash theme={null} vigolium agent autopilot -t https://example.com --intensity deep ``` To enable durable context rotation and verification, set `agent.olium.autopilot_mode: enforced` in `vigolium-configs.yaml`. You can then continue an interrupted run with: ```bash theme={null} vigolium agent autopilot --resume \ --prompt "continue with the remaining authorization checks" ``` Reach for autopilot when the target is fuzzy, JS-heavy, auth-walled, or you want creative / exploratory testing. See [Autopilot](/agentic-scan/autopilot) for the full flag reference. *** ## Swarm, AI-guided multi-phase scan The agent **directs** the native scanner instead of becoming it. A state-machine pipeline runs: normalize → auth → source-analysis → code-audit → discover → plan (AI) → extension → native scan → triage (AI) → optional rescan loop. ```bash theme={null} # Swarm against a single URL vigolium agent swarm --input "https://example.com/api/users?id=1" # Against a raw HTTP request file, with triage vigolium agent swarm --input ./request.txt --triage # Full-scope sweep with discovery + triage vigolium agent swarm --input https://example.com --discover --triage # Source-aware: AI extracts routes, plans modules, generates JS extensions vigolium agent swarm -t https://example.com --source ./backend --code-audit # Target a specific vulnerability class vigolium agent swarm -t https://example.com --vuln-type sqli \ --prompt "also test authentication bypass" ``` Reach for swarm when you have a known-good request, want bespoke fuzzing tailored to it, or need a verification loop on findings. See [Swarm](/agentic-scan/swarm) for the full pipeline reference. *** ## Source-aware scanning (`--source`) Both `autopilot` and `swarm` accept `--source `. When set, **vigolium-audit runs first** (foreground), prepares a frozen whitebox context bundle, then the autonomous scan launches against that context. ```bash theme={null} # Autopilot with auto-audit (lite mode, default) vigolium agent autopilot -t https://example.com --source ~/src/your-app # Override the audit depth vigolium agent autopilot -t https://example.com --source ~/src/your-app --audit=deep # Disable the audit prep vigolium agent autopilot -t https://example.com --source ~/src/your-app --audit=off # Focus on a PR's changed code vigolium agent autopilot -t https://example.com --source ./app \ --diff https://github.com/org/app/pull/42 # Last N commits vigolium agent autopilot -t https://example.com --source ./app --last-commits 5 ``` `--source` accepts local paths, git URLs (cloned with `--depth 1` by default), or archive files. For a standalone source audit without a scan, see [Agentic Security Audit](/getting-started/agentic-security-audit). *** ## Picking between autopilot and swarm | You want... | Pick | | -------------------------------------------------------------------- | ------------------------------ | | Hands-off black-box scanning with a real browser + custom extensions | **autopilot** | | Structured, repeatable results with optional verification loops | **swarm** | | AI-crafted payloads against one specific request | **swarm** (single-input) | | Creative pentest-style exploration | **autopilot** | | CI/PR gate (fast, deterministic) | **swarm --intensity quick** | | Overnight deep assessment | **autopilot --intensity deep** | If you have **both** source code and a target URL, both work. `swarm --source --target ... --code-audit --triage` gives the most structured output; `autopilot --source ...` gives the agent more freedom. *** ## Sessions and findings Every agent run writes a session directory under `~/.vigolium/agent-sessions//` with the checkpoint, plan, rendered prompts, captured output, and any generated extensions. Browse past runs with: ```bash theme={null} vigolium agent session # list recent vigolium agent session --tail # live-tail the active run vigolium agent session --full # full detail ``` Findings ingest into the same `findings` table as native scans, tagged by source (`autopilot`, `swarm`, `audit`, `piolium`). Query them with: ```bash theme={null} vigolium finding list --source autopilot vigolium finding list --source swarm ``` *** ## Next steps * [Autopilot](/agentic-scan/autopilot), autonomous operator: full flag reference, intensity presets, halt conditions. * [Swarm](/agentic-scan/swarm), multi-phase pipeline: phases, extension generation, triage loop. * [Agent Mode](/agentic-scan/agent-mode), the full reference for every `vigolium agent` subcommand. * [Agentic Security Audit](/getting-started/agentic-security-audit), whitebox source-code audit (vigolium-audit + piolium). * [Setting Up the Agent](/getting-started/setup-agent), provider/credential setup. # Agentic Security Audit Source: https://docs.vigolium.com/getting-started/agentic-security-audit Multi-phase AI-driven whitebox source-code audit, vigolium-audit (embedded, Claude/Codex) and piolium (Pi-native), with a unified driver that runs both. Vigolium's agentic security audit is a multi-phase whitebox source-code audit driven by frontier LLMs. It reads your code, builds an architecture model, runs SAST tools, debates findings in adversarial chambers, and cold-verifies them before emitting structured results into the Vigolium database, alongside any native scanner findings. Two harnesses cover the audit surface: * **`vigolium agent audit --driver=audit`**: embedded driver that drives the `claude` or `codex` CLI under the hood. Up to 12 phases at `deep`. No extra install. * **`vigolium agent audit --driver=piolium`**: Pi-native driver. Up to 17 phases at `deep`. Requires the `pi` runtime and the `piolium` extension. Supports any provider Pi supports, including local models. Both produce findings in the same on-disk schema and DB shape; the unified `vigolium agent audit` dispatcher can run either harness alone, both side-by-side, or auto-pick based on what is available. vigolium agent audit running in the terminal *** ## Prerequisites * A working olium provider (Claude / OpenAI / Codex / local). See [Setting Up the Agent](/getting-started/setup-agent). * For the embedded audit driver, either the `claude` or `codex` CLI on `$PATH` — the embedded harness drives whichever one matches your configured olium provider (anthropic-\* → claude, openai-\* → codex), or pass `--agent claude|codex` to override. * For `piolium`, `pi` on `$PATH` plus `pi install git:git@github.com:vigolium/piolium.git`. Verify your setup with the smoke test: ```bash theme={null} vigolium agent audit --driver=audit --source . --mode lite ``` If that streams phase output and writes findings to the DB, you're wired up. *** ## Quick Start, `vigolium agent audit` The unified driver, fastest path to a first audit. By default (`--driver=auto`) it runs the embedded vigolium-audit harness and only falls back to piolium if audit is unavailable. `vigolium audit` is a top-level alias for `vigolium agent audit` — every flag and example below works with the shorter form too. ```bash theme={null} # Lite (3 phases, CI-friendly) vigolium agent audit --source ~/src/your-app --mode lite # Balanced (9 phases, default — adds SAST + adversarial review) vigolium agent audit --source ~/src/your-app # Deep (12 phases — commit archaeology, cold-verify, variant hunting) vigolium agent audit --source ~/src/your-app --mode deep # Use Codex instead of Claude for the embedded audit driver vigolium agent audit --source ~/src/your-app --mode balanced --agent codex ``` vigolium-audit running through the Claude CLI vigolium-audit running through the Codex CLI `--source` is required, the audit reads code, not network traffic. It accepts a local path, a git URL (cloned shallow by default), or an archive (`.zip`/`.tar.gz`/`.tar.bz2`/`.tar.xz`). ### Bring your own credentials By default the audit leg inherits `agent.olium.*`. To supply credentials for a single run — a CI job, a client engagement, a key you don't want in your config — use one of three mutually exclusive flags. Each takes a literal value, `$ENV_NAME`, or `@path`: ```bash theme={null} # API key for the resolved agent (claude → ANTHROPIC_API_KEY, codex → OPENAI_API_KEY) vigolium agent audit --source ~/src/your-app --api-key '$ANTHROPIC_API_KEY' # Anthropic OAuth bearer token (Claude only — from `claude setup-token`) vigolium agent audit --source ~/src/your-app --oauth-token @~/.claude/setup-token # OAuth credential file, Codex `~/.codex/auth.json` shape vigolium agent audit --source ~/src/your-app --agent codex --oauth-cred-file '$CODEX_AUTH' ``` For a piolium run, `--oauth-cred-file` is staged under the pi agent directory with backup-and-restore, so your own `pi` auth is put back afterwards. ### Run controls | Flag | Default | What it does | | ------------------------ | --------------- | ----------------------------------------------------------------------------------------------- | | `--show-thinking` | off | Render the agent's internal thinking blocks in the live stream (verbose — many lines per phase) | | `--preflight-timeout` | `30s` | Per-driver preflight timeout, applied to both `pi` and `claude` | | `--plm-phase-retries` | piolium default | Piolium per-phase retry count | | `--plm-command-retries` | piolium default | Piolium per-command retry count | | `--plm-longshot-timeout` | piolium default | Piolium per-file kill timer in longshot mode, in ms | vigolium-audit standalone run output *** ## Audit modes | Mode | Phases | When to use | | -------------------- | :----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lite` | 3 | CI/PR gates, routine triage. Quick recon, secrets scan, fast SAST. | | `balanced` (default) | 9 | Daily regression scans. Adds intelligence, knowledge-base, deep probe, FP review, PoC. | | `deep` | 12 | Pre-release / compliance audits. Adds commit archaeology, patch bypass, adversarial debate, cold verification, variant hunting. | | `knowledge-base` | 3 | Build **only** the reusable application model + attack surface (auth model, roles, unauthenticated surface, SBOM) — no SAST, findings, or PoCs. Run before a scan, or chain it as `--modes knowledge-base,deep`. Audit-only. | | `revisit` | 10 | Second offensive pass on the same code. Reuses prior KB; surfaces findings round one missed. | | `confirm` / `merge` | 7 | Boot-and-verify existing findings (`confirm`) or normalize results from multiple inputs (`merge`). | | `diff` | 1 | Incremental audit limited to changes since the last audited commit. | | `longshot` | 3 | Hail-mary file-by-file hunt (also available as a piolium mode). | | `reinvest` | 3 | Cross-agent re-verification of CRITICAL/HIGH findings using a *different* agent platform. Audit-only. | | `refresh` | varies | Routing convenience: revisit if a prior KB exists, otherwise a fresh deep pass. Audit-only. | | `mock` | — | Wire-up test, emits sample output without spending tokens. Audit-only. | | `smoke` | — | Piolium-only smoke test. | Run `vigolium agent audit --list-modes` to print the full mode graph and time estimates for the embedded audit binary. **`knowledge-base` is a prep mode, not a scan.** It writes a reusable application model (`knowledge-base-report.md`, `unauthenticated-surface.md`, `sbom.json`) under `vigolium-results/attack-surface/` and reports **no findings**. Use it to front-load context once — then run `lite`/`balanced`/`deep` against the same source (or chain it in one run with `--modes knowledge-base,deep`) so the scan reuses that model instead of rebuilding it. *** ## Multi-round audits and the `confirm` mode LLM-driven audits are not deterministic. Two runs of the same `balanced` audit on the same commit will not produce the exact same finding set, an agent's reasoning, tool-call ordering, and which insights it chases all vary between runs. On top of that, the agent has no built-in concept of *your* product, what looks like a privilege escalation in isolation is often a documented feature, an internal admin tool, or a deliberate trust boundary. The practical workaround is to **run the audit in multiple rounds** rather than treating a single run as the final word: 1. **Round 1, broad pass** — run `balanced` or `deep` against the source. Expect a mix of real bugs, plausible-but-wrong reasoning, and design-intent false positives. 2. **Round 2, revisit** — run `--mode revisit` on the same source. It reuses round one's knowledge base and explicitly hunts for what the first pass missed, often surfaces variants and missed sinks without re-reporting the same findings. 3. **Round 3, confirm with intent** — run `--mode confirm` against the prior `vigolium-results/` directory and feed it the **product context** the agent couldn't infer: which endpoints are intentionally public, which "auth bypass" is a documented dev-only flag, which "SSRF" is the metadata-service probe your own infra needs, etc. The agent re-reads each finding against that context, drops the design-intent false positives, and locks in what's left. vigolium-audit confirm mode dispatching with codex ```bash theme={null} # Round 1 — broad pass, writes vigolium-results/ into the session dir vigolium agent audit --source ~/src/your-app --mode balanced # Round 2 — revisit, reuses the KB and hunts for misses vigolium agent audit --source ~/src/your-app --mode revisit # Round 3 — confirm pass with your own intent / feature context vigolium agent audit --source ~/src/your-app --mode confirm \ --context ./audit-intent.md ``` `audit-intent.md` is a free-form note you write describing what's a feature vs a bug for this codebase. A useful skeleton: ```markdown theme={null} # Intent / known-design notes - `/admin/*` routes are gated by mTLS at the edge; the in-app `is_admin` check is defense-in-depth, not the primary boundary. - `GET /debug/metadata` SSRF-flavored calls are intentional, used by the sidecar. - Tokens stored in `localStorage` is a deliberate tradeoff for the embedded SDK, not a vuln. - Anything under `internal/migrations/` is non-routable in prod; ignore findings there. ``` The confirm pass treats this file as ground truth when adjudicating prior findings, what survives is much more likely to be a real bug worth triaging. *** ## Piolium, the Pi-native alternative `piolium` runs the same multi-phase audit through the **Pi coding-agent runtime** instead of Claude/Codex. Use it when: * You're on an OpenAI key (GPT-5.x, Codex) and want quality comparable to a Claude-Opus run. * You're using Gemini or another Vertex/Bedrock-hosted non-Anthropic model. * You want to swap providers (`--pi-provider` / `--pi-model`) without changing the pipeline. * You need `longshot` mode, file-by-file hail-mary hunting (piolium only). ```bash theme={null} # Install pi + the piolium extension (one-time) bun install -g @earendil-works/pi-coding-agent pi install git:git@github.com:vigolium/piolium.git # Run a balanced audit vigolium agent audit --driver=piolium --source ~/src/your-app # Deep (17 phases, the most thorough audit available) vigolium agent audit --driver=piolium --source ~/src/your-app --mode deep # Override pi's provider/model for a single run vigolium agent audit --driver=piolium --source ~/src/your-app \ --pi-provider vertex-anthropic --pi-model claude-opus-4-6 ``` See [Agent Mode](/agentic-scan/agent-mode) for the full flag reference and the `--plm-*` passthroughs. *** ## Running both, `--driver=both` `vigolium agent audit` is the unified dispatcher. By default (`--driver=auto`) it runs the embedded audit harness and only falls back to piolium if audit is unavailable. Pass `--driver=both` to run audit and piolium back-to-back against the same source tree under one parent `AgenticScan`, with per-driver child rows and a post-pass project-wide findings dedup. ```bash theme={null} # Auto (default): audit, fall back to piolium only if audit is unavailable vigolium agent audit --source ~/src/your-app # Both drivers, sequential vigolium agent audit --driver both --source ~/src/your-app # Force a single driver vigolium agent audit --driver piolium --source ./backend --mode lite vigolium agent audit --driver audit --source ./backend --agent claude # Both drivers, deep intensity, against a remote git URL vigolium agent audit --driver both --source git@github.com:org/repo.git --intensity deep ``` vigolium agent audit with both drivers When `--driver=auto` or `--driver=both`, mode must be in the shared set (`lite` / `balanced` / `deep` / `revisit` / `confirm` / `merge`). Driver-specific modes (`longshot` / `smoke` for piolium; `mock` / `reinvest` / `refresh` for audit) require `--driver=piolium` or `--driver=audit`. *** ## Where findings go Findings land in the same `findings` table as the rest of Vigolium, tagged by source: ```bash theme={null} # All audit findings (both drivers) vigolium finding list --source piolium,audit # Just the embedded vigolium-audit harness vigolium finding list --source audit # Just piolium vigolium finding list --source piolium ``` Session artifacts (state, raw findings, reports) land under: ``` ~/.vigolium/agent-sessions// ├── vigolium-results/ # vigolium-audit output (audit-state.json + findings/ + reports) ├── piolium-audit/ # piolium output (same schema) ├── audit-stream.jsonl └── runtime.log ``` *** ## Raw output in the source tree, `--keep-raw` / `--clean-raw` During a run vigolium-audit writes its working output (raw scanner output, draft findings, intermediate workspaces) to `/vigolium-results/`, and vigolium syncs a copy into the session directory above. * **`--keep-raw` is on by default** — the `/vigolium-results/` copy is retained after the run so you can review it or re-import it. (Audit leg only; no effect on piolium.) * **`--clean-raw`** removes `/vigolium-results/` from the source tree after the run. The session-directory copy is always kept either way. `--keep-raw` and `--clean-raw` together is an error. The REST `keep_raw` default is unchanged (off) — this default flip is CLI-only. *** ## One-shot report, `-S` / `--stateless` `-S`/`--stateless` runs the entire audit against a throwaway temporary database (your main DB is left untouched, mirroring `vigolium scan -S`) and, when it finishes, auto-renders a self-contained HTML report from the run's findings — no separate `vigolium import` step. ```bash theme={null} # Audit and produce a standalone HTML report; nothing persisted to the main DB vigolium audit --source ~/src/your-app -S # Override the report destination (default: vigolium-result/vigolium-audit-report.html) vigolium audit --source ~/src/your-app -S -o reports/my-app-{ts}.html ``` The report is written to `vigolium-result/vigolium-audit-report.html` by default; `-o`/`--output` overrides it and supports `gs:///` upload and the `{ts}` timestamp placeholder. `-S` cannot be combined with `--interactive`. Without `-S`, findings persist to your database as usual; build a report afterward with `vigolium import --format html -o report.html` or `vigolium export`. ### Bundle report + raw results, `--output-dir` `--output-dir ` (stateless-only) collects a `-S` run's artifacts into **one folder**: the HTML report (as `/vigolium-audit-report.html`) **and** a copy of the raw `vigolium-results/` tree(s). One driver lands flat at `/vigolium-results/`; multiple drivers are namespaced under `//vigolium-results/`. The source-tree copy is left in place (per `--keep-raw`). ```bash theme={null} # One self-contained folder: HTML report + a copy of the raw scanner output vigolium audit --source ~/src/your-app -S --output-dir audit-out-{ts} ``` A relative `-o`/`--output` nests under ``; an absolute path or `gs://` URL escapes it. Both `{ts}` and `{project-uuid}` expand in ``. Running `-S` without `--output-dir` warns that raw output stays under `/vigolium-results/`; passing `--output-dir` without `-S` is ignored with a warning. *** ## In-pipeline audit (alongside autopilot / swarm) When you pair `--source` with `vigolium agent autopilot` or `vigolium agent swarm`, vigolium-audit (or piolium, when locally available) runs first, its findings feed into the operator's frozen context bundle before the autonomous scan starts. ```bash theme={null} # Source-aware autopilot — vigolium-audit runs first (balanced mode) vigolium agent autopilot -t https://example.com --source ~/src/your-app --audit=balanced # Swarm with a background lite audit vigolium agent swarm -t https://example.com --source ./src --audit ``` See [Agentic Scanning](/getting-started/agentic-scan) for the full autopilot/swarm flow. *** ## Next steps * [Agent Mode](/agentic-scan/agent-mode), full reference for every `vigolium agent` subcommand. * [Setting Up the Agent](/getting-started/setup-agent), provider/credential setup. * [Agentic Scanning](/getting-started/agentic-scan), autopilot and swarm modes. # Using Vigolium with Burp Suite Source: https://docs.vigolium.com/getting-started/burp-suite Install the Vigolium Burp Suite extension, forward proxy traffic into a running Vigolium server, launch native and agentic scans from Burp, and use the bidirectional live bridge to sync traffic in both directions. The **Vigolium Burp Suite extension** turns Burp into a front-end for your Vigolium server. As you browse, requests and responses are forwarded to Vigolium, native and agentic scans are launched straight from Burp's context menus, and findings stream back into both Burp and the [Web UI](/getting-started/web-ui) because everything is stored in the same Vigolium database. The extension does **not** run the scanner inside Burp's JVM. It forwards traffic to the Vigolium server and polls it for findings, so scans use your Vigolium server resources and keep running even after Burp is closed. Using **Caido** instead? The [Vigolium Caido plugin](/getting-started/caido-plugin) offers the same workflows — dispatch, proxy forwarding, sitemap snapshots, and the live bridge — over the same bridge protocol, with `--caido-bridge-url` as an alias for `--burp-bridge-url`. Vigolium Burp extension Findings Records tab showing findings, request and response evidence, and live scan status ## Download the extension The extension is distributed as a pre-built JAR from the dedicated repository: Grab the latest **`burp-vigolium.jar`** and browse the source at [github.com/vigolium/burp-vigolium](https://github.com/vigolium/burp-vigolium). Download [`burp-vigolium.jar`](https://github.com/vigolium/burp-vigolium/blob/main/burp-vigolium.jar) directly, or build it from source: ```bash theme={null} git clone https://github.com/vigolium/burp-vigolium cd burp-vigolium ./gradlew spotlessCheck test shadowJar # → build/libs/burp-vigolium.jar ``` The bidirectional live bridge and Target Site map snapshots require **`burp-vigolium` v0.2.0 or later**. Earlier builds can still forward per-request traffic and launch scans, but do not expose the loopback bridge listener. The installed version is shown in the upper-right corner of the **Settings** view. ## Install and connect The extension needs a running server to talk to. Start one (add `--mirror-fs ` to also write traffic to disk): ```bash theme={null} # With API authentication (recommended) export VIGOLIUM_API_KEY=my-secret-key vigolium server # Or, local development only — no auth vigolium server -A ``` Retrieve the API key to paste into the extension: ```bash theme={null} vigolium config ls server.auth_api_key --force ``` In Burp, go to **Extensions → Installed → Add**. Set **Extension type: Java**, choose the downloaded `burp-vigolium.jar`, and click **Next**. A **Vigolium** tab appears. Open **Vigolium → Settings**, enter the **Server URL** (default `http://127.0.0.1:9002`) and **API Key** on the Server Connection row, then click **Test Connection** to confirm the server is reachable. Use a Burp context-menu action or a keyboard shortcut to dispatch traffic. Findings stream into the **Findings Records** tab and the Web UI as they are produced. The installed extension version is shown in the upper-right corner of the **Settings** view. Release builds read it from the JAR manifest; local IDE runs display `development`. ## Three dispatch workflows Right-click a request in **Proxy History**, **Target Site map**, **Repeater**, or any supported Burp request view — the actions are available directly, without opening a nested menu: | Action | Default shortcut | What it does | | -------------------------------- | ---------------- | ----------------------------------------------------------------------- | | **Send to Vigolium** (ingestion) | `Ctrl+Alt+V` | Stores the selected request/response in Vigolium via `/api/ingest-http` | | **Send to Native Scan** | `Ctrl+Alt+N` | Starts a deterministic module-based scan of the selected request | | **Send to Agentic Scan** | `Ctrl+Alt+A` | Starts an AI-guided (swarm) scan of the selected request | Vigolium preserves headers, cookies, body fields, query parameters, and path segments as scanner input, so a request sent from Repeater is scanned exactly as you shaped it. ## Forward proxy traffic automatically Instead of dispatching requests one at a time, enable **Proxy Mode** to forward everything flowing through Burp Proxy into Vigolium. Combine it with **In-scope only** so static assets and out-of-scope hosts don't pollute the database, and use the **Proxy Filter Rules** to allow or deny by file extension, HTTP method, or host. Pair Proxy Mode with a passive-only server (`vigolium server -S --passive-only`) to continuously analyze browsed traffic — secrets, security headers, cookie flags, disclosure — without sending any active requests. See [Server & Ingestion](/getting-started/server-and-ingestion). ## What you get inside Burp The **Vigolium** panel is organized into six tabs: | Tab | Purpose | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | **Findings Records** | Searchable, sortable findings with per-finding evidence tabs, request/response editors, an expandable description, and **Copy Finding as Markdown** | | **HTTP Records** | Filterable, sortable request/response records synchronized with Vigolium | | **Scanning Records** | Native and agentic scan history with pagination, auto-refresh, and scan logs | | **Bridge** | Target Site map snapshots, the live loopback listener, Proxy forwarding, and filter rules | | **Settings** | Extension version, server connection, scan options, request statistics, and configurable hotkeys | | **Logs** | Timestamped activity log (INFO / WARN / ERROR) | Vigolium Burp extension HTTP Records tab showing filterable request and response records with inline editors Selecting a finding opens its primary request and response in Burp's message editors; additional evidence appears as adjacent tabs so you can compare without a drop-down. The orange **Copy Finding as Markdown** action copies the full finding — metadata, description, matched URLs, requests, responses, and extra evidence — ready to paste into a report or a coding agent. Vigolium Burp extension Settings tab for server connection, scan options, shortcuts, and request statistics ### Keyboard shortcuts Every action is rebindable under **Settings → Keyboard Shortcuts**. The defaults: | Action | Default shortcut | | -------------------------- | ---------------- | | Send to ingestion | `Ctrl+Alt+V` | | Send to native scan | `Ctrl+Alt+N` | | Send to agentic scan | `Ctrl+Alt+A` | | Snapshot Target Site map | `Ctrl+Alt+S` | | Refresh active record view | `Ctrl+Alt+R` | `Ctrl+Alt+R` is contextual — while focus is inside Findings Records, HTTP Records, Native Scans, or Agentic Scans, it activates that view's Refresh button. ## Target Site map snapshots Beyond per-request dispatch, the extension can upload your whole **Target Site map** to Vigolium in one shot. Open **Vigolium → Bridge → Target Site Map Snapshot** and either run a snapshot immediately (`Ctrl+Alt+S`) or enable periodic snapshots (disabled by default; five-minute interval when on). Use **In-scope only** to exclude out-of-scope traffic. Snapshots are **incremental** within the current Burp session and **idempotent** on the server: requests and available responses upload in bounded chunks, and unchanged records are never duplicated. ## Bidirectional live bridge The **live bridge** connects Vigolium's CLI and server to Burp in *both* directions over an opt-in, loopback-only listener — query live Burp Proxy history from Vigolium, or copy Vigolium traffic back into Burp's Target Site map. The same protocol backs the [Caido plugin](/getting-started/caido-plugin), so `--burp-bridge-url` and its `--caido-bridge-url` alias are one flag. Which vendor is listening is **discovered, not declared**: each reply carries an `implementation` identity that Vigolium maps onto the record's `source` label (`burp` or `caido`), and the merged `/api/http-records` response names it in `X-Vigolium-Bridge-Source`. A listener too old to report one is read as `burp`. Vigolium Burp extension Bridge tab showing the live bridge listener URL, proxy interception, and proxy filter rules Open **Vigolium → Bridge**, select **Enable live bridge**, and set the listener URL (default `http://127.0.0.1:9009`). The extension starts an embedded HTTP server on that loopback address; it refuses non-loopback binds. Optionally enable **In-scope items only** to keep bridge searches within Burp's Target scope. Pass the listener URL to any bridge-aware command with the `--burp-bridge-url` flag — this is the recommended way, since each command stays explicit and self-contained: ```bash theme={null} vigolium traffic --burp-bridge-url http://127.0.0.1:9009 ``` Only when you'd rather not repeat the flag across many commands in the same shell, export the env var once instead: ```bash theme={null} export VIGOLIUM_BURP_BRIDGE_URL="http://127.0.0.1:9009" # optional fallback — the --burp-bridge-url flag overrides it ``` Every command below takes `--burp-bridge-url http://127.0.0.1:9009`; if you exported `VIGOLIUM_BURP_BRIDGE_URL`, drop the flag. ### Read live Burp history Merge Burp's Proxy history into a Vigolium traffic view without persisting anything. Live rows are labelled `source: burp`, and the usual filters, sorting, pagination, and JSON output all apply: ```bash theme={null} # Browse Burp's live history alongside your database records vigolium traffic --burp-bridge-url http://127.0.0.1:9009 # Only the live Burp portion of the merged view vigolium traffic --burp-bridge-url http://127.0.0.1:9009 --source burp ``` Point a running server at the listener to do the same for the API and Web UI — `GET /api/http-records` then returns Burp's live rows merged with stored ones: ```bash theme={null} vigolium server --burp-bridge-url http://127.0.0.1:9009 ``` ### Persist Burp traffic into the database Two ways in, depending on how much you want: ```bash theme={null} # The traffic your active filters selected (add --all for every match, not just this page) vigolium traffic --burp-bridge-url http://127.0.0.1:9009 --save-to-vigolium-db # One-shot bulk import of the Proxy history for one host vigolium import --burp-bridge-url http://127.0.0.1:9009 --host acme.test ``` **Name what you want — an unfiltered `import -B` is refused (exit 2) since v0.4.5.** Your Proxy history is everything you have browsed this session, and importing it wholesale drags every unrelated host you visited — with its cookies and tokens — into the database. Where that database is shared with a coding agent, that is a cross-engagement leak. Use the same filters `traffic -B` takes: `--host` (wildcards), `--path`, `--method`, `--status`, `--search`, `--exclude-search`, `--from`/`--to`, `-n`. When you genuinely do want everything, say so with `--all-hosts` (add `--yes` to skip the pre-flight count confirmation). ```bash theme={null} vigolium import -B http://127.0.0.1:9009 --host '*.acme.test' --method POST vigolium import -B http://127.0.0.1:9009 --all-hosts --yes ``` Bridge imports are **idempotent**: new requests are inserted, changed responses refresh the existing row, and unchanged traffic is skipped. Because the bridge is an import source in its own right, `--burp-bridge-url` can't be combined with a path argument or `--glob-db` in the same `import` invocation. `traffic -B --save-to-vigolium-db` no longer silently truncates the write at 100 records. An untyped `-n` is a *listing* default and must not bound an import, so the import runs unlimited; a typed `-n` is honored as an explicit budget and warns when the cap actually bit. ### Push Vigolium traffic back into Burp The reverse direction — copy stored records into Burp's **Target Site map** so you can work them by hand: ```bash theme={null} # Copy the DB traffic your filters selected into Burp's Site map vigolium traffic --burp-bridge-url http://127.0.0.1:9009 --save-to-burp # Send a replayed request + its fresh response straight into the Site map vigolium replay --record-uuid --burp-bridge-url http://127.0.0.1:9009 --save-to-burp ``` `--save-to-burp` and `--save-to-vigolium-db` are mutually exclusive — pick a direction per command. ### Seed an agentic scan from Burp Autopilot can pull live history across the bridge **before** it builds its prior-context brief and runs the pre-scan, so the agent starts from the traffic you already browsed instead of rediscovering it: ```bash theme={null} vigolium agent autopilot -t https://example.com \ --burp-bridge-url http://127.0.0.1:9009 ``` The bridge listener is **disabled by default** and requires no credentials because it only binds to a validated loopback address, rejects unexpected `Host`/`Origin` headers, and uses temporary result references that expire when the listener restarts or the extension unloads. Disabling the setting or unloading the extension stops the listener immediately. ## Send traffic through Burp's engine The read/write bridge above moves stored traffic between the two tools. The **send bridge** goes one step further: it lets `replay`, `fuzz`, and `finding` issue a request through **Burp's own HTTP stack** — so the exact bytes you assembled hit the wire (a hand-crafted `Content-Length`, a smuggling prefix, an unusual method) instead of being normalised by Go's client — and stage the exchange into **Repeater** or **Organizer** for manual follow-up. All of these flags require `--burp-bridge-url` (the same loopback listener as above; env fallback `VIGOLIUM_BURP_BRIDGE_URL`). They are **opt-in** — without them, each command's send path is byte-for-byte unchanged. The listener is preflighted once up front, so an unavailable bridge is a single clear error rather than one failure per request. Requires a **`burp-vigolium`** build that exposes the `/api/burp-bridge/{send,repeater,organizer}` endpoints (the installed version is shown in the extension's **Settings** view). If a target is out of scope and Burp is set to "in-scope only", the send returns a clean error instead of silently dropping. ### Send exact bytes with `--send-via-burp` Go's HTTP client normalises what you hand it: it recomputes `Content-Length`, re-frames the request, and rejects some malformed constructions outright. That's fine for ordinary traffic and fatal for the attacks that depend on the malformation. `--send-via-burp` hands the raw bytes to Burp's stack instead, so what you assembled is what reaches the wire: ```bash theme={null} # Replay a stored record through Burp's engine vigolium replay --record-uuid abc12345 \ --burp-bridge-url http://127.0.0.1:9009 --send-via-burp # Send a hand-crafted raw request byte-for-byte vigolium replay --raw-request-file exact.txt \ --burp-bridge-url http://127.0.0.1:9009 --send-via-burp ``` A target-side failure is reported per request and doesn't abort a batch, so one dead host won't kill a fuzzing loop. ### Choose the wire protocol with `--http-mode` `auto` (the default) negotiates normally, which can renegotiate HTTP/2 and re-frame your request. For request smuggling and desync work, pin HTTP/1 so the framing you wrote survives: ```bash theme={null} vigolium replay -i req.txt --burp-bridge-url http://127.0.0.1:9009 \ --send-via-burp --http-mode http1 ``` Accepted values: `auto`, `http1`, `http2`, `http2_ignore_alpn`. `--send-timeout` bounds a single send (max 2 minutes; the bridge's own 30s default otherwise) on `replay` and `fuzz`. ### Stage a request in Repeater `--to-repeater` opens the request in a Burp **Repeater** tab, ready for manual iteration. `--repeater-tab ` names the tab (default `vigolium`): ```bash theme={null} # Replay a record and open it in a tab named 'desync' vigolium replay --record-uuid abc12345 --burp-bridge-url http://127.0.0.1:9009 \ --to-repeater --repeater-tab desync # Open a finding's request, re-issued through Burp so the tab holds a fresh response vigolium finding --id 42 --to-repeater --send-via-burp \ --burp-bridge-url http://127.0.0.1:9009 ``` Burp caps Repeater at roughly **30 tabs per minute**. For a large selection, prefer the Organizer (`--push-to-burp`, one item per finding) over `--to-repeater`. ### Store an exchange in the Organizer `--to-organizer` stores the request **and** its response as a Burp **Organizer** item — the right destination for a batch you want to triage later, since it has no tab-rate limit: ```bash theme={null} vigolium replay --record-uuid abc12345 --burp-bridge-url http://127.0.0.1:9009 \ --to-organizer --notes 'IDOR candidate' --highlight orange ``` `--notes` longer than 200 characters is **truncated with an ellipsis and warned about**, never rejected — losing a note's tail beats losing the evidence. `--highlight` accepts `none`, `red`, `orange`, `yellow`, `green`, `cyan`, `blue`, `pink`, `magenta`, or `gray`. ### Push fuzz matches to the Organizer Fuzz through Burp's engine and let every **matched** anomaly land in the Organizer automatically — Burp re-issues each one, so the stored item carries its own response: ```bash theme={null} vigolium fuzz 'https://acme.test/api?id=FUZZ' -w ~/wordlists/ids.txt \ --match-status-code 200 \ --burp-bridge-url http://127.0.0.1:9009 --send-via-burp --matches-to-organizer ``` Only results that survive your matchers and excludes are pushed, so tighten `--match-*` / `--exclude-*` before pointing a big wordlist at it. See [`vigolium fuzz`](/others/cli-references#fuzz) for the full matcher set. ### Push findings to the Organizer `--push-to-burp` hands a finding's evidence request and response to the Organizer, colour-coded by severity, one item per finding. It honours the usual `finding` selectors — a fuzzy term, `--id`, `--severity`, `--min-severity`: ```bash theme={null} # Every XSS finding's evidence, into the Organizer vigolium finding xss --push-to-burp --burp-bridge-url http://127.0.0.1:9009 # High-and-above, re-issued through Burp to capture fresh responses vigolium finding --min-severity high --push-to-burp --send-via-burp \ --burp-bridge-url http://127.0.0.1:9009 ``` ### Flag reference | Flag | Commands | What it does | | ------------------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `--send-via-burp` | `replay`, `fuzz`, `finding` | Issue the request through Burp's HTTP stack (exact bytes preserved) instead of Go's client | | `--http-mode` | `replay`, `fuzz`, `finding` | Wire protocol for `--send-via-burp`: `auto` (default), `http1`, `http2`, `http2_ignore_alpn`. Use `http1` for request smuggling / desync | | `--send-timeout` | `replay`, `fuzz` | Per-request response timeout, max 2 minutes (bridge default 30s) | | `--to-repeater` | `replay`, `finding` | Stage the request in a Burp Repeater tab (`--repeater-tab ` on `replay`, default `vigolium`) | | `--to-organizer` | `replay` | Store the request + response in Burp's Organizer (`--notes <=200 chars>`, `--highlight `) | | `--matches-to-organizer` | `fuzz` | Push each **matched** result's request into Burp's Organizer (Burp re-issues it) for triage | | `--push-to-burp` | `finding` | Push the selected finding(s)' evidence request+response into Burp's Organizer, severity-coloured | ## Under the hood The extension calls the standard Vigolium API (authenticated with `Authorization: Bearer {API_KEY}`): | Method | Endpoint | Description | | ------ | ------------------------------------------------------------------------- | -------------------------------------------- | | `GET` | `/health` | Test the server connection | | `POST` | `/api/ingest-http` | Store selected or forwarded Burp traffic | | `POST` | `/api/scan-request` | Start a native scan for selected traffic | | `POST` | `/api/agent/run/swarm` | Start an agentic scan for selected traffic | | `POST` | `/api/scan-all-records` | Scan all stored HTTP records | | `POST` | `/api/burp/sitemap/snapshot` | Upload an idempotent Site map snapshot chunk | | `GET` | `/api/findings`, `/api/http-records`, `/api/scans`, `/api/agent/sessions` | List findings, records, and scan runs | The loopback bridge listener is a separate, unauthenticated, local-only transport (`/api/burp-bridge/{search,inspect,sitemap,send,repeater,organizer}`). For lower-level ingestion details, see [Server ingestion](/server-mode/ingestion); for the full extension reference and source, see [github.com/vigolium/burp-vigolium](https://github.com/vigolium/burp-vigolium). # Using Vigolium with Caido Source: https://docs.vigolium.com/getting-started/caido-plugin Install the Vigolium Caido plugin, forward proxy traffic into a running Vigolium server, launch native and agentic scans from Caido, and use the bidirectional live bridge to sync traffic in both directions. The **Vigolium Caido plugin** turns Caido into a front-end for your Vigolium server. As you browse, requests and responses are forwarded to Vigolium, native and agentic scans are launched straight from Caido's context menus and command palette, and findings stream back into both Caido and the [Web UI](/getting-started/web-ui) because everything is stored in the same Vigolium database. The plugin does **not** run the scanner inside Caido. It forwards traffic to the Vigolium server and polls it for findings, so scans use your Vigolium server resources and keep running even after Caido is closed. It is the Caido counterpart to the [Burp Suite extension](/getting-started/burp-suite) and speaks the **same bridge protocol**, so every `vigolium --burp-bridge-url` command works against Caido with no CLI changes. Vigolium also accepts **`--caido-bridge-url`** as an alias for the same flag — either spelling works on every command and both resolve to the same value. Vigolium Caido plugin Findings tab showing findings, severity filters, and request and response evidence ## Download the plugin The plugin is distributed as a pre-built package from the dedicated repository: Grab the latest **`caido-vigolium.zip`** and browse the source at [github.com/vigolium/caido-vigolium](https://github.com/vigolium/caido-vigolium). Download [`caido-vigolium.zip`](https://github.com/vigolium/caido-vigolium/blob/main/caido-vigolium.zip) directly, or build it from source: ```bash theme={null} git clone https://github.com/vigolium/caido-vigolium cd caido-vigolium pnpm install pnpm build # → ./caido-vigolium.zip (and dist/plugin_package.zip) ``` Bridge traffic is labelled by vendor (`source: caido`) from **Vigolium v0.3.12** onward. Detection is wire-only with no fallback probe, so an older Vigolium reads every bridge record as `burp` — silently. Rows imported from Caido before that release stay labelled `burp` and cannot be reclassified after the fact. ## Install and connect The plugin needs a running server to talk to. Start one (add `--mirror-fs ` to also write traffic to disk): ```bash theme={null} # With API authentication (recommended) export VIGOLIUM_API_KEY=my-secret-key vigolium server # Or, local development only — no auth vigolium server -A ``` Retrieve the API key to paste into the plugin: ```bash theme={null} vigolium config ls server.auth_api_key --force ``` In Caido, go to **Plugins → Install Package → From File** and choose the downloaded `caido-vigolium.zip`. A **Vigolium** entry appears in the sidebar. A frontend plugin is enabled **per user**: check that both *Vigolium* and *Vigolium Backend* are ticked under **Plugins → Installed**, then reload the window — the frontend script is only evaluated on a full page load. Open **Vigolium → Settings**, enter the **Server URL** (default `http://127.0.0.1:9002`) and **API key**, then select **Test connection** to confirm the server is reachable. Both values are stored in the plugin's own database in Caido Data. Use a context-menu action, a keyboard shortcut, or the command palette to dispatch traffic. Findings stream into the **Findings** tab and the Web UI as they are produced. Vigolium Caido plugin Settings tab for server connection, scan options, request statistics, and keyboard shortcuts ## Three dispatch workflows Right-click a request in **Search**, **Sitemap**, **Replay**, or either message pane — the actions are available directly, without opening a nested menu: | Action | macOS | Windows / Linux | What it does | | ------------------------ | ----- | --------------- | ----------------------------------------------------------------------- | | **Send to ingestion** | `⌘⌃V` | `Ctrl+Alt+V` | Stores the selected request/response in Vigolium via `/api/ingest-http` | | **Send to native scan** | `⌘⌃N` | `Ctrl+Alt+N` | Starts a deterministic module-based scan of the selected request | | **Send to agentic scan** | `⌘⌃A` | `Ctrl+Alt+A` | Starts an AI-guided (swarm) scan of the selected request | Every action is also a command: open the palette with `⌘K` / `Ctrl+K` and search for **Vigolium**. Vigolium preserves headers, cookies, body fields, query parameters, and path segments as scanner input, so a request sent from Replay is scanned exactly as you shaped it. Under **Settings → Scan options** you can pin a comma-separated module list and a timeout (`30s`, `2m`); leaving either blank uses the server default. **Scan all HTTP records** submits every stored record with those options. ## Forward proxy traffic automatically Instead of dispatching requests one at a time, turn on **Proxy forwarding** (Bridge tab) to send every proxied exchange into Vigolium ingestion. It always starts **off** when the plugin loads. Combine it with **In-scope only** so out-of-scope hosts don't pollute the database, and use the filter rules to allow or deny by file extension, HTTP method, or host — the defaults already drop static assets and `OPTIONS`/`HEAD`. Pair proxy forwarding with a passive-only server (`vigolium server -S --passive-only`) to continuously analyze browsed traffic — secrets, security headers, cookie flags, disclosure — without sending any active requests. See [Server & Ingestion](/getting-started/server-and-ingestion). ## What you get inside Caido The **Vigolium** panel is organized into six tabs: | Tab | Purpose | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Findings** | Searchable, sortable findings with per-finding evidence tabs, request/response editors, an expandable description, **Copy as Markdown**, and **Export JSON** | | **HTTP Records** | Filterable, sortable request/response records synchronized with Vigolium, with per-record **Send to Replay** / **Scan** / **Delete** | | **Scanning** | Native and agentic scan history with pagination, refresh controls, and scan logs | | **Bridge** | Sitemap snapshots, the live loopback listener, proxy forwarding, and filter rules | | **Settings** | Server connection, scan options, request statistics, and the shortcut reference | | **Logs** | Timestamped activity log (INFO / WARN / ERROR) | Vigolium Caido plugin HTTP Records tab showing filterable request and response records synchronized with Vigolium Selecting a finding opens its primary request and response in Caido's message editors; additional evidence appears as adjacent tabs so you can compare without a drop-down. **Copy as Markdown** copies the full finding — metadata, description, matched URLs, requests, responses, and extra evidence — ready to paste into a report or a coding agent. ### Keyboard shortcuts | Action | macOS | Windows / Linux | | -------------------------- | ----- | --------------- | | Send to ingestion | `⌘⌃V` | `Ctrl+Alt+V` | | Send to native scan | `⌘⌃N` | `Ctrl+Alt+N` | | Send to agentic scan | `⌘⌃A` | `Ctrl+Alt+A` | | Snapshot Sitemap | `⌘⌃S` | `Ctrl+Alt+S` | | Refresh active record view | `⌘⌃R` | `Ctrl+Alt+R` | Rebind them under Caido's **Settings → Shortcuts**. The refresh shortcut is contextual — it refreshes whichever record view is currently showing. The Burp extension's `Ctrl+Alt+…` bindings carry over on Windows and Linux. macOS uses `⌘⌃` instead, because `Alt` there is the Option dead-key — the OS turns `Alt+V` into `√` before Caido sees it. ## Sitemap snapshots Beyond per-request dispatch, the plugin can upload the project's whole **Sitemap** to Vigolium in one shot. Open **Vigolium → Bridge → Sitemap snapshot** and either run a snapshot immediately (`⌘⌃S` / `Ctrl+Alt+S`) or enable periodic snapshots (disabled by default; five-minute interval when on). Use **In-scope only** to exclude out-of-scope traffic. Snapshots are **incremental** within the current session and **idempotent** on the server: requests and available responses upload in bounded chunks, and unchanged records are never duplicated. ## Bidirectional live bridge The **live bridge** connects Vigolium's CLI and server to Caido in *both* directions over a loopback-only listener — query live Caido traffic from Vigolium, or copy Vigolium traffic back into Caido's Sitemap. Vigolium Caido plugin Bridge tab showing the live bridge listener URL, sitemap snapshot controls, and proxy forwarding filter rules The bridge is **on by default** so the bridge flags work as soon as the plugin is installed. Open **Vigolium → Bridge** to turn it off, change the listener URL (default `http://127.0.0.1:9009`), or enable **In-scope items only** to keep bridge searches within the project's scope. The plugin refuses any non-loopback bind address. Pass the listener URL to any bridge-aware command with the `--caido-bridge-url` flag (`--burp-bridge-url` and `-B` are the same flag) — this is the recommended way, since each command stays explicit and self-contained: ```bash theme={null} vigolium traffic --caido-bridge-url http://127.0.0.1:9009 ``` Only when you'd rather not repeat the flag across many commands in the same shell, export the env var once instead: ```bash theme={null} export VIGOLIUM_BURP_BRIDGE_URL="http://127.0.0.1:9009" # optional fallback — the flag overrides it ``` Every command below takes `--caido-bridge-url http://127.0.0.1:9009`; if you exported `VIGOLIUM_BURP_BRIDGE_URL`, drop the flag. ### Read live Caido traffic Merge Caido's traffic into a Vigolium traffic view without persisting anything. Live rows are labelled `source: caido`, and the usual filters, sorting, pagination, and JSON output all apply: ```bash theme={null} # Browse Caido's live traffic alongside your database records vigolium traffic --caido-bridge-url http://127.0.0.1:9009 # Only the live Caido portion of the merged view vigolium traffic --caido-bridge-url http://127.0.0.1:9009 --source caido ``` Point a running server at the listener to do the same for the API and Web UI — `GET /api/http-records` then returns Caido's live rows merged with stored ones, and the response carries `X-Vigolium-Bridge-Source: caido`: ```bash theme={null} vigolium server --caido-bridge-url http://127.0.0.1:9009 ``` ### Persist Caido traffic into the database Two ways in, depending on how much you want: ```bash theme={null} # The traffic your active filters selected (add --all for every match, not just this page) vigolium traffic --caido-bridge-url http://127.0.0.1:9009 --save-to-vigolium-db # One-shot bulk import of ALL bridge-visible traffic vigolium import --caido-bridge-url http://127.0.0.1:9009 ``` Bridge imports are **idempotent**: new requests are inserted, changed responses refresh the existing row, and unchanged traffic is skipped. Persisted rows keep `source: caido`, so they stay distinguishable after Caido closes. Because the bridge is an import source in its own right, the flag can't be combined with a path argument or `--glob-db` in the same `import` invocation. ### Push Vigolium traffic back into Caido The reverse direction — copy stored records into Caido's **Sitemap** so you can work them by hand: ```bash theme={null} # Copy the DB traffic your filters selected into Caido's Sitemap vigolium traffic --caido-bridge-url http://127.0.0.1:9009 --save-to-burp # Send a replayed request + its fresh response straight into the Sitemap vigolium replay --record-uuid --caido-bridge-url http://127.0.0.1:9009 --save-to-burp ``` `--save-to-burp` and `--save-to-vigolium-db` are mutually exclusive — pick a direction per command. (The flag keeps its `burp` spelling because it names the shared bridge protocol, not the vendor.) ### Seed an agentic scan from Caido Autopilot can pull live traffic across the bridge **before** it builds its prior-context brief and runs the pre-scan, so the agent starts from the traffic you already browsed instead of rediscovering it: ```bash theme={null} vigolium agent autopilot -t https://example.com \ --caido-bridge-url http://127.0.0.1:9009 ``` The bridge listener requires no credentials because it only binds to a validated loopback address, rejects unexpected `Host`/`Origin` headers, and uses temporary result references that expire when the listener restarts. Turning the setting off or uninstalling the plugin stops the listener immediately. ### Caido projects Caido scopes traffic to the **selected project**, which Burp has no equivalent of. Two consequences: * A bridge search only ever returns the active project's traffic. The active project is reported on `/health` and shown in the Bridge tab. * Switching project expires all outstanding search references, exactly as restarting the listener does. The Vigolium client already treats an unknown reference as "search again". ## Send traffic through Caido's engine The read/write bridge above moves stored traffic between the two tools. The **send bridge** goes one step further: it lets `replay`, `fuzz`, and `finding` issue a request through **Caido's own HTTP stack** — so the exact bytes you assembled hit the wire instead of being normalised by Go's client — and stage the exchange into **Replay** for manual follow-up. All of these flags require the bridge URL and are **opt-in**: without them, each command's send path is byte-for-byte unchanged. The listener is preflighted once up front, so an unavailable bridge is a single clear error rather than one failure per request. ```bash theme={null} # Replay a stored record through Caido's engine vigolium replay --record-uuid abc12345 \ --caido-bridge-url http://127.0.0.1:9009 --send-via-burp # Open it in a Replay session named 'desync' vigolium replay --record-uuid abc12345 --caido-bridge-url http://127.0.0.1:9009 \ --to-repeater --repeater-tab desync # Fuzz through Caido and collect every matched result in a named Replay collection vigolium fuzz 'https://acme.test/api?id=FUZZ' -w ~/wordlists/ids.txt \ --match-status-code 200 \ --caido-bridge-url http://127.0.0.1:9009 --send-via-burp --matches-to-organizer # Push a finding's evidence into a Replay collection for manual confirmation vigolium finding xss --push-to-burp --caido-bridge-url http://127.0.0.1:9009 ``` The flags are the ones documented in the [Burp send-bridge reference](/getting-started/burp-suite#send-traffic-through-burps-engine) — `--send-via-burp`, `--http-mode`, `--send-timeout`, `--to-repeater`, `--to-organizer`, `--matches-to-organizer`, `--push-to-burp`. Behaviour is matched wherever Caido allows it; where it cannot be, the plugin picks the closest equivalent rather than pretending: | Burp | Caido | | ---------------------------------------- | ------------------------------------------------------------------- | | Organizer (flat list, notes + highlight) | Named **Replay collection** — `--notes` becomes the collection name | | Repeater tab | **Replay session**, renamed to `--repeater-tab` | | `--http-mode` negotiation | Validated and echoed, but Caido's raw send is always HTTP/1.1 | | Target Site map | **Sitemap**, backed by the project's request store | | One global session | Per-project traffic (see [Caido projects](#caido-projects)) | ## How traffic is labelled Which proxy is listening is **discovered, not declared**: the plugin reports `implementation: "vigolium-caido-bridge"` on every `/health`, `/search`, and `/inspect` reply, and Vigolium maps that through a closed allowlist onto the `source` column it stamps on each record. So `--source caido` selects exactly the traffic this plugin served, and the Source column reads `caido` rather than `burp`. Traffic **pushed** to the Vigolium server (rather than pulled over the bridge) carries `X-Vigolium-Source: caido` and lands with the same label, so one filter covers both directions. Two consequences worth knowing: * **Record UUIDs keep the `burp:` prefix** regardless of vendor. It is a routing token meaning "this record lives behind the bridge, not in the database", not a provenance label — vendor lives in `source`. * **A Vigolium older than v0.3.12 reads every bridge record as `burp`**, because detection is wire-only with no fallback probe. ## Under the hood The plugin calls the standard Vigolium API (authenticated with `Authorization: Bearer {API_KEY}`): | Method | Endpoint | Description | | ------ | ------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `GET` | `/health` | Test the server connection | | `POST` | `/api/ingest-http` | Store selected or forwarded Caido traffic (with `X-Vigolium-Source: caido`) | | `POST` | `/api/scan-request` | Start a native scan for selected traffic | | `POST` | `/api/agent/run/swarm` | Start an agentic scan for selected traffic | | `POST` | `/api/scan-all-records` | Scan all stored HTTP records | | `POST` | `/api/burp/sitemap/snapshot` | Upload an idempotent Sitemap snapshot chunk | | `GET` | `/api/findings`, `/api/http-records`, `/api/scans`, `/api/agent/sessions` | List findings, records, and scan runs | The loopback bridge listener is a separate, unauthenticated, local-only transport. Its endpoint paths keep the `burp-bridge` prefix so no CLI change is required: | Method | Bridge endpoint | Purpose | | ------ | ---------------------------- | ------------------------------------------------------------------ | | `GET` | `/health` | Listener health, capabilities, scope mode, active project | | `POST` | `/api/burp-bridge/search` | Search the project's traffic | | `POST` | `/api/burp-bridge/inspect` | Retrieve request/response data for a temporary search reference | | `POST` | `/api/burp-bridge/sitemap` | Add a Base64-encoded request/response item to Caido's Sitemap | | `POST` | `/api/burp-bridge/repeater` | Open a request (or a search reference) in Caido Replay | | `POST` | `/api/burp-bridge/send` | Issue a request through Caido's HTTP stack and return the response | | `POST` | `/api/burp-bridge/organizer` | Store a request + response pair in a named Replay collection | For lower-level ingestion details, see [Server ingestion](/server-mode/ingestion); for the Burp Suite equivalent, see [Using Vigolium with Burp Suite](/getting-started/burp-suite); for the full plugin reference and source, see [github.com/vigolium/caido-vigolium](https://github.com/vigolium/caido-vigolium). # Vigolium Cheat Sheet Source: https://docs.vigolium.com/getting-started/cheat-sheet A one-page, copy-paste reference for the workflows you reach for most: live traffic mirroring, replaying through Burp or Caido, passive/secret scanning of forwarded traffic, importing external data, parallel fan-out, resuming, spec-driven scans, single-request scans, content discovery, filtering to one vulnerability class or technology, browsing recorded traffic in the database, triaging findings with a coding agent, reproducing an exploit, setting up the AI agent, exporting stored data to a browsable filesystem tree, and inspecting or editing config. A fast, copy-paste reference for Vigolium's most common real-world workflows. Each block is self-contained, adjust hosts, ports, and file paths to your environment. For the full explanation behind any command, follow the cross-links into the rest of the docs. Every command here works against the open-source binary. `vigolium -h` prints the full flag list for any subcommand. *** ## 1. Mirror ingested traffic + findings to a live filesystem tree Run the ingestion server with `--mirror-fs` to write every saved HTTP record and finding to a **flat, browsable directory tree** as it is persisted, in addition to the database. This pairs perfectly with the **Vigolium Burp extension**: as you browse, requests/responses and findings land on disk where your coding agent can read them with plain `ls` / `grep` / `jq`. ```bash theme={null} # Mirror ingested traffic + findings to a live filesystem tree. # Writes /traffic and /findings as records arrive. vigolium server --mirror-fs output-dir -A ``` What you get under `output-dir/`: | Path | Contents | | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `traffic//.req` | Raw request (leading `@target ://` line, then the request verbatim, replayable by stripping line 1) | | `traffic//.resp.headers` | Status line + response headers | | `traffic//.resp.body` | Response body (gzip-decoded so it greps clean) | | `traffic//index.jsonl` | Append-only, jq-friendly map of ids → method/url/status/content-type | | `findings//.md` | Each finding, cross-linked to its `.req` file | Mirroring never blocks the database save path (it runs on a background writer) and resumes per-host id numbering across server restarts. Config equivalent: `server.mirror_fs_path`. Add `-S`/`--scan-on-receive` to also scan the traffic as it arrives (see [section 3](#3-passive-secret-scan-on-forwarded-traffic)). Read it back from an agent or shell: ```bash theme={null} # List every request Vigolium has ingested for a host ls output-dir/traffic/example.com/ # Find all findings mentioning "token" grep -ril token output-dir/findings/ # Pull the method/url/status of every record as JSON jq . output-dir/traffic/example.com/index.jsonl ``` *** ## 2. Replay ALL stored traffic through Burp Suite Re-send stored records through an intercepting proxy so you can inspect and manipulate them manually in Burp. Keep concurrency low (`-c`) so you don't overwhelm the proxy. ```bash theme={null} # Replay every stored record in your project DB through Burp vigolium replay --all --proxy http://127.0.0.1:8080 -c 5 ``` Replay directly from a standalone export (project scoping off, nothing written to your project DB): ```bash theme={null} # BULK: replay every record from a standalone .sqlite export vigolium replay -S --db scan.sqlite --all --proxy http://127.0.0.1:8080 -c 5 ``` `--all` lifts the default `-n/--limit` cap (100). Instead of `--all` you can narrow the bulk set with a positional fuzzy term (`vigolium replay admin`) or the full `traffic`-style selectors — `--search`/`--exclude-search`, `--host`, `--method`, `--status`, `--path`, `--source`, `--body`/`--exclude-body`, `--from`/`--to`, and `--sort`/`--asc`/`--offset` (see [Bulk replay](/others/cli-references#bulk-replay)). `-S/--stateless` reads records from a `.jsonl` export **or** a standalone `.sqlite` with project scoping off, it never writes to your project DB. *** ## 3. Passive / secret scan on forwarded traffic When you forward traffic into Vigolium from another source (the Burp extension, a proxy, or the `vigolium ingest` client), start the **server in scan-on-receive mode**. Use `--passive-only` to run passive modules **only**, no active scan traffic is sent, and secret detection is included. ```bash theme={null} # Passive-only, continuously scan forwarded traffic as it arrives. # No active requests are sent; secret detection runs. vigolium server -S --passive-only -A # Same, but also mirror everything to disk for your agent to read vigolium server -S --passive-only --mirror-fs output-dir -A # Record + passively scan HTTP(S) through the transparent ingest proxy vigolium server -S --passive-only --ingest-proxy-port 9003 -A ``` Scan-on-receive modes at a glance: | Flag | Behavior | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `-S` / `--scan-on-receive` | Continuously scans new records with the **dynamic-assessment** phase (active + passive modules). | | `-S --passive-only` | Passive modules only, **no active traffic**. Includes secret detection, security headers, cookie flags, disclosure, etc. | | `-S --full-native-scan-on-receive` | Runs the **full native pipeline** (discovery + spidering + dynamic-assessment) on received records. | Server mode always enables **every** passive module. `--passive-only` simply zeroes the active modules so the scanner analyzes forwarded request/response pairs without sending any new requests, ideal for scanning traffic you've captured elsewhere. Point the Burp extension (or `vigolium ingest`) at the server's ingestion endpoint and findings appear as traffic flows in. See [Server & Ingestion](/getting-started/server-and-ingestion). *** ## 4. Import external scan data into the database Use `vigolium import` to pull existing data, **JSONL exports**, **audit output folders**, and **archives**, into your database. It auto-detects the input by inspecting the path. ```bash theme={null} # JSONL export (http_record + finding envelopes, e.g. from `vigolium export --format jsonl`) vigolium import data.jsonl # Audit output folder (contains audit-state.json + findings-draft/) vigolium import ./audit-output/ # Merge an external vigolium scan DB into the default database vigolium import other-vigolium-scan.sqlite # Compressed archive of either of the above vigolium import bundle.tar.gz # also .tgz, .zip # Remote object in cloud storage (downloaded, then imported) vigolium import gs:/// # Import and emit a branded HTML report in one step vigolium import ./audit-output/ --format html -o report.html --report-title "My Report" ``` Imported findings are scoped to the active project (`--project-uuid` / `VIGOLIUM_PROJECT_UUID`). `import` initializes the schema on a fresh `--db` path, so it's safe as the first command against a brand-new database. Supported JSONL types are `http_record` and `finding`; other envelope types are counted and skipped. ### Merging a standalone SQLite database `vigolium import` detects a SQLite result database by its magic header (works for `.sqlite`, `.sqlite3`, `.db`, or no extension) and performs a **lossless, idempotent SQLite→SQLite merge** of the scan-result tables (http\_records, findings, scans, agentic\_scans, oast\_interactions, projects), deduping on natural keys: ```bash theme={null} # Merge an external scan DB into the default database vigolium import other-vigolium-scan.sqlite # Merge into a specific destination DB vigolium import other-vigolium-scan.sqlite --db team.sqlite # Merge several scans at once — pass them positionally… vigolium import --db combined.sqlite scan-a.sqlite scan-b.sqlite scan-c.sqlite # …or expand a glob with --glob-db (idempotent, so re-runs are no-ops) vigolium import --db combined.sqlite --glob-db 'scans/*.sqlite' # --glob-db also works for JSONL exports (use one format per run) vigolium import --glob-db '*.jsonl' ``` If you'd rather **read** a colleague's export without merging it into your own DB, open it in place with project scoping off. Point `-S` at a single file with `--db`, or read across **many** files at once with `--glob-db` (they're merged into one throwaway scratch DB, so `--glob-db` implies `-S`): ```bash theme={null} # A single standalone export vigolium finding -S --db scan.sqlite vigolium traffic -S --db scan.sqlite # Across a whole directory of exports (.sqlite and/or .jsonl) vigolium finding --glob-db 'scans/*.sqlite' vigolium traffic --glob-db 'scans/*.sqlite' vigolium export --glob-db 'scans/*.sqlite' --format jsonl -o all.jsonl ``` **Large globs are fine.** `--glob-db` is routinely pointed at hundreds of files — a per-host bug-bounty run of \~854 databases, 16 GB, 290k HTTP records. At that size the *merge*, not the query, is the whole cost: it runs before the first `WHERE`, so anything copied in is resident before any filter narrows it. **v0.4.4** made that bounded: * The scratch database is a temp **file**, not `:memory:` — its working set pages against the OS cache instead of competing with the process heap (an `export` that previously reached 18.7 GB RSS and 149s of kernel paging without finishing). * `finding` no longer copies every matched file's record corpus just to hydrate the findings on screen (20.4 GB). `--raw` / `--burp` / `--markdown` / `--with-records` / `--push-to-burp` / `--to-repeater` fetch evidence afterwards by UUID from each finding's own source file. * `traffic --save-to-burp --glob-db` streams file-by-file instead of merging — it's the one mode that can't drop the bodies, since they're what gets sent (16 GB → 417 MB peak). * A single-format `export` streams envelopes instead of materializing the whole set (6.1 GB). * Scratch databases stranded by a killed run are reaped on the next open. Merged rows keep their **original** `project_uuid`, so imported data stays scoped to whatever project it was scanned under. Importing the same database twice adds nothing the second time (dedup on natural keys). Requires a SQLite destination — a Postgres destination returns a clear error. To merge many **parallel** scans into one shared DB during scanning instead, use `--db-isolate` (see [section 5](#5-scan-a-large-list-of-targets-in-parallel)). *** ## 5. Scan a large list of targets in parallel Point Vigolium at a large target list and use `-P/--parallel N` to scan several hosts at once. Each target runs in its own **isolated child process**, so there is no cross-contamination between workers, and each child keeps its own `--concurrency`, meaning real in-flight requests are roughly `N × --concurrency`. `-P` requires one of two output strategies so results never collide: * **`--stateless --split-by-host`** — each target runs against its own temporary database and writes a separate per-host output file (`base-.`). Nothing is persisted. Best for stateless, fire-and-forget batches. * **`--db-isolate`** — each worker scans into a private temporary SQLite database, then merges its results into the shared `--db` (or the default DB) at the end. This lets many parallel scans share one database without write contention, and you export one unified report from the merged DB afterward. ```bash theme={null} # Stateless fan-out: per-host JSONL + HTML files, 3 targets at a time vigolium scan -T list-of-targets.txt -P 3 \ --stateless --split-by-host \ --format jsonl,html --output prefix-output \ --discovery-wordlist ~/Tools/contents/fast.txt # Shared-DB fan-out: 4 targets at a time merged into one local.db, one unified output vigolium scan -T list-of-targets.txt -P 4 \ --db-isolate --db local.db \ --format jsonl,html --output report \ --discovery-wordlist ~/Tools/contents/fast.txt ``` `--db-isolate` is SQLite-only and cannot be combined with `--stateless` (they are two different ways to avoid write contention). Pressing `Ctrl-C` during a `-P` batch is treated as an operator stop: un-started and cut-short targets are reported as "not scanned" rather than failures. *** ## 6. Resume a large parallel scan A stateless parallel fan-out (`-S -T --split-by-host -P`) writes a tiny line-cursor manifest, `.progress.json`, tracking the targets that completed cleanly. If the batch is interrupted (Ctrl-C, a crash, a CI timeout), re-run it with `--resume` to skip the finished targets and scan only the remainder, Vigolium also prints a copy-pasteable resume command on Ctrl-C/failure: ```bash theme={null} # Original run vigolium scan -T targets.txt -P 4 --stateless --split-by-host --format jsonl -o results # Resume only the targets that didn't finish vigolium scan -T targets.txt -P 4 --stateless --split-by-host --format jsonl -o results --resume ``` Run `vigolium scan --resume` bare, with no other flags, and it auto-discovers the `*.progress.json` in the current directory and relaunches the saved run from it (pass `-o ` to disambiguate when several manifests exist). `--resume` currently applies only to the parallel fan-out (`-S -T --split-by-host -P > 1`). Resuming a plain sequential scan re-runs it in full. *** ## 7. Scan from an OpenAPI / Swagger / WSDL / Postman / Burp / HAR input Feed a spec or capture with `-i ` and select the format with `-I `. Auto-discovery/crawling is skipped, Vigolium scans exactly the endpoints defined in the input. ```bash theme={null} # OpenAPI / Swagger spec vigolium scan --stateless -i api.yaml -I openapi \ -t https://api.example.com --format jsonl -o results # WSDL / SOAP service (or a live .svc/.asmx URL, which fetches its own WSDL) vigolium scan --stateless -i service.wsdl -I wsdl \ -t https://soap.example.com --format jsonl -o results # Postman collection vigolium scan --stateless -i collection.json -I postman \ -t https://api.example.com --format jsonl -o results # Burp Suite XML export vigolium scan --stateless -i export.xml -I burpxml --format jsonl -o results # HAR capture vigolium scan --stateless -i traffic.har -I har --format jsonl -o results # Nuclei JSONL vigolium scan --stateless -i nuclei.jsonl -I nuclei --format jsonl -o results # Burp Suite scope export — the JSON a bug-bounty program hands you vigolium scan --stateless -T scope.json --format jsonl -o results ``` `burpscope` parses a Burp **project-config scope export** (`{"target":{"scope":{"include":[…],"exclude":[…]}}}` — not the binary `.burp` project file) and expands its include rules into seed URLs. It is content-sniffed, so `-T scope.json` works with no `-I`. A wildcard host like `^.*\.example\.com$` is reported rather than scanned — it names a set, not a server, and synthesizing the apex would hit a host the scope never listed. `http://` candidates collapse into their `https://` twins, and exclude rules are applied before anything is emitted. **`-i/--input` and `-T/--target-file` are not interchangeable.** `-T` reads its file as one target URL per line, so pointing it at a spec makes every line of that file a target (and under `-S`, one child scan per line). Specs and exports go through `-i`. The two exceptions are the formats that genuinely *are* target lists: `urls` and `burpscope`. Format aliases: `openapi`/`swagger`, `wsdl`/`soap`/`svc`, `postman`, `burpxml`/`burp`/`burp-xml`, `burpraw`/`raw`, `burpscope`/`burp-scope`, `har`/`http-archive`, `nuclei`/`nuclei-output`, `deparos`. Run `vigolium scan --list-input-mode` for the full list. Use `-t/--target` to supply the base URL when a spec carries only paths. *** ## 8. Scan a single request, no crawling or discovery `scan-request` runs scanner modules against exactly one raw HTTP request, keeping all its parameters, cookies, and headers, with no crawling or discovery. ```bash theme={null} # From a file containing a raw HTTP request vigolium scan-request -i request.txt # From stdin printf 'GET /api/users?id=1 HTTP/1.1\r\nHost: example.com\r\n\r\n' \ | vigolium scan-request # From a curl command (auto-detected) echo "curl -X POST -d 'user=admin' https://example.com/login" \ | vigolium scan-request ``` Override the host when the request file has only a path: ```bash theme={null} vigolium scan-request -i request.txt --target https://staging.example.com ``` ### Piping from stdin Both `scan-url` and `scan-request` auto-detect the stdin format, plain URL, curl command, or raw HTTP request: ```bash theme={null} # Plain URL echo 'https://example.com/search?q=test' | vigolium scan-url # Curl command echo "curl -H 'Content-Type: application/json' -d '{\"id\":1}' https://example.com/api" \ | vigolium scan-url # Raw HTTP request (keeps the cookie + body verbatim) printf 'POST /api/login HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: session=abc123\r\n\r\nuser=admin&pass=secret' \ | vigolium scan-request # Scan whatever is on your clipboard (macOS) pbpaste | vigolium scan-url -j ``` ### Fuzz one request with a custom payload set Where `scan-request` runs opinionated, confirmation-backed **modules**, `vigolium fuzz` is a low-level **primitive**: it injects a payload set into an exact position of one request and streams raw response signals (status, size, words, lines, time, reflection, baseline-delta) with match/exclude gating — no findings, no verdicts. It's built to be driven by a coding agent. Reach for it when you need a custom wordlist, an exact insertion point, or wordlist-scale discovery the modules can't express; reach for `scan-request -m ...` when you want a confirmed detection. ```bash theme={null} # Wordlist into a FUZZ marker, keep only 200s (builtin lists: fuzz, dir-short/long, file-short/long) vigolium fuzz 'https://example.com/api/FUZZ' -w dir-short --match-status-code 200 # A built-in payload class into one parameter (classes: xss, sqli, ssti, ssrf, lfi, cmdi, xxe, …) vigolium fuzz 'https://example.com/item?id=1' --point URL_PARAM:id --class sqli,xss # Raw request on stdin, inline payloads, drop 404s and auto-calibrated catch-all noise cat req.txt | vigolium fuzz -p "' OR 1=1--" -p '' --exclude-status-code 404 # Agent handle: JSONL to stderr, ONE summary object (ranked anomalies + a ready confirm query) to stdout vigolium fuzz 'https://example.com/api?id=FUZZ' -w ~/wordlists/ids.txt -j # CI/agent gate: exit non-zero (3) if anything matches vigolium fuzz 'https://example.com/admin/FUZZ' -w dir-short --match-status-code 200,301 --fail-on-match ``` **Don't know what "interesting" looks like yet?** `-a/--anomaly` replaces hand-written matchers. Each response is scored against the baseline *and* against the run's own population, so a status change that every payload triggers reads as the endpoint's normal behaviour rather than a signal: ```bash theme={null} vigolium fuzz 'https://example.com/api/FUZZ' -w dir-short --anomaly vigolium fuzz 'https://example.com/api/FUZZ' -w dir-short --anomaly --anomaly-threshold high ``` Every result carries `anomaly_score` and `anomaly_reasons` (leaked SQL/stack error, 4xx→2xx flip, size/time outlier, rare status, unencoded reflection, …). It reports **where to look**, never a verdict — confirm the lead with `scan-request -m ...`. Three more things worth knowing: ```bash theme={null} # Numeric matchers take predicates, not just equality: N, N-M, >N, >=N, 1000' --match-status-code '!404' # Credential PAIRS across two markers (sniper | batteringram | pitchfork | clusterbomb) vigolium fuzz 'https://example.com/login' -X POST -d 'u=FUZZ&p=FUZZ2' \ --mode pitchfork -w users.txt:FUZZ -w passwords.txt:FUZZ2 # Pre-flight: print the exact bytes each payload would send, with ZERO network traffic vigolium fuzz 'https://example.com/api/FUZZ' -w dir-long --dry-run ``` `fuzz` also accepts curl's **long-form** flags (`--cookie`, `--data-urlencode`, `--form`, `--compressed`, `--resolve`, …), so a request pasted from DevTools works with minimal editing. Long forms only: the short spellings are taken by vigolium meanings (`-u` record UUID, `-i` input, `-w` wordlist). See the [Fuzz CLI reference](/others/cli-references#fuzz) for the full flag set (positions, matchers, anomaly scoring, attack modes, calibration, and sending payloads through Burp's engine). *** ## 9. Target one vulnerability class or technology Every scan command (`scan`, `scan-url`, `scan-request`, `run`, `ingest`) accepts two module filters. Use `-m/--modules` to enable a subset by **fuzzy match on module ID/name**, or `--module-tag` to select by **tag** (repeatable, OR-combined). Both default to "all" when omitted; when you pass both, the results are **merged** (union). ```bash theme={null} # Only run the XSS modules against a single URL vigolium scan-url -t 'https://example.com/search?q=1' -m xss # Technology-scoped scans by tag — GraphQL, Adobe AEM, or IIS only vigolium scan -t https://example.com --module-tag graphql vigolium scan -t https://example.com --module-tag aem vigolium scan -t https://example.com --module-tag iis # Combine tags (OR): run every AEM or GraphQL module vigolium scan -t https://example.com --module-tag aem --module-tag graphql # Pin an exact module by its ID (repeatable) vigolium scan -t https://example.com -m graphql-scan vigolium scan-url -t 'https://example.com/p?id=1' -m xss-light-url-params -m sqli ``` Discover what's available before you filter: ```bash theme={null} # List modules whose id/name/description/tag matches a term vigolium module ls xss vigolium module ls aem # Dump every unique tag you can pass to --module-tag vigolium module ls --tags # Full descriptions + confirmation criteria for matched modules vigolium module ls graphql -v ``` By default a module only fires when the target's detected tech stack matches it (so the GraphQL, AEM, and IIS modules stay dormant on unrelated hosts). If auto-detection misses the stack, add `--no-tech-filter` to run the selected modules regardless (this is auto-enabled by `--intensity=deep`). `-m` matches loosely (`-m xss` selects every XSS module); pass a full ID like `-m xss-light-url-params` to pin exactly one. See [Modules](/native-scan/modules-reference). *** ## 10. Run only specific phases (or skip some) Use `--only` to run a subset of phases, or `--skip` to exclude phases from an otherwise-full scan. Both accept comma-separated phase names and aliases. ```bash theme={null} # Phase isolation: run only one phase vigolium scan -t https://example.com --only discovery vigolium scan -t https://example.com --only known-issue-scan vigolium scan -t https://example.com --only dynamic-assessment # Skip specific phases (aliases like kis = known-issue-scan are accepted) vigolium scan -t https://example.com --skip discovery,spidering,kis ``` Canonical phase names and their accepted aliases: | Phase | Aliases | | ------------------------------- | ----------------------------- | | `discovery` | `deparos`, `discover` | | `spidering` | `spitolas` | | `known-issue-scan` | `cve`, `kis`, `known-issues` | | `dynamic-assessment` | `audit`, `dast`, `assessment` | | `extension` | `ext` | | `external-harvest`, `ingestion` | — | `dynamic-assessment` is the canonical name for the module-based vulnerability-scanning phase (formerly `audit`). The same phase names work as the argument to `vigolium run `, e.g. `vigolium run cve`. *** ## 11. Run a JavaScript extension only Load one or more custom JS extensions with `--ext` (repeatable) and restrict the run to the extension phase with `--only extension`, no built-in modules, just your script. ```bash theme={null} # Run only your extension against the target vigolium scan -t https://example.com --only extension --ext custom-check.js # Load several extensions vigolium scan -t https://example.com --only extension \ --ext custom-check.js --ext another-check.js ``` `--ext` is a global flag, so it works on any scan command. Drop `--only extension` to run your extension **alongside** the built-in modules. See [Writing Extensions](/customization/writing-extensions) for the `vigolium.*` API. *** ## 12. Content discovery only Run **only** the discovery/fuzzing phase, no crawling, no vulnerability modules. The most direct way is `vigolium run discover`, the phase runner: it executes a single named phase and nothing else. Point it at a wordlist with `--discovery-wordlist`. ```bash theme={null} # Discovery-only against a whole host (brute-force paths from a wordlist) vigolium run discover -S -t https://example.com --discovery-wordlist ~/Tools/contents/fast.txt # Fuzz a SPECIFIC insertion point with an inline FUZZ marker in the URL — # each word replaces FUZZ (here: /api//users) vigolium run discover -S --discovery-wordlist ~/Tools/contents/fast.txt \ -t 'https://example.com/api/FUZZ/users' ``` The **`FUZZ` marker** works anywhere in the target path: put it where you want the wordlist injected. With no marker, vigolium fuzzes off the target's directory (equivalent to appending `/FUZZ`). You can get the same discovery-only behavior on the full `scan` command with `--only discovery --discover`: ```bash theme={null} # Same idea via `scan`: run every phase-gate off except discovery vigolium scan -t https://example.com --only discovery \ --discover --discovery-wordlist ~/.vigolium/wordlists/fuzz.txt # Discovery as part of a full scan (fuzz, then keep going into vuln scanning) vigolium scan -t https://example.com --discover --discovery-wordlist ~/.vigolium/wordlists/fuzz.txt ``` Separately from path fuzzing, `scan` can sweep **alternate HTTP(S) ports** on the target hosts so a service on a non-standard port is discovered and scanned. The sweep runs at `--intensity deep` or with `--follow-subdomains`; `--port-sweep-ports` overrides which ports it probes (comma-separated, in addition to the standard `80`/`443`): ```bash theme={null} # Discover and scan services on alternate ports (deep intensity) vigolium scan -t https://example.com --intensity deep --port-sweep-ports 80,443,8080,8443 ``` `vigolium run ` runs one phase in isolation (`discover` is an alias for `discovery`; see [section 10](#10-run-only-specific-phases-or-skip-some) for the full phase/alias list). `-S/--stateless` keeps it throwaway (nothing written to your project DB); combine with `--format fs` or `-o` to persist the discovered surface for later scans. `--discovery-wordlist` enables on-the-fly fuzzing; the `FUZZ` marker pins the exact insertion point. See [Discovery phase](/native-scan/phases/discovery). *** ## 13. Add custom HTTP headers Inject headers, such as auth tokens or cookies, with `-H/--header` (repeatable). Works on `scan`, `scan-url`, and `scan-request`. ```bash theme={null} # One or more custom headers on every request vigolium scan -t https://example.com \ -H 'Authorization: Bearer eyJhbGciOi...' \ -H 'X-Api-Key: secret' \ -H 'Cookie: session=abc123' ``` `-H/--header` — like the other repeatable value flags (`-t/--target`, `-T/--target-file`, `--auth`, `--auth-file`, `--spec-header`, `--cookie`) — takes each value **verbatim**: commas are **literal**, so a comma-bearing header/cookie value or a `?ids=1,2,3` URL survives intact. Pass multiple values by **repeating the flag**, never by comma-joining them. For richer authenticated scans (login flows, token refresh, multi-step sessions), use an auth session file instead, see [Authentication](/native-scan/authentication) and the `vigolium auth` command. *** ## 14. Control scan speed & duration Cap the wall-clock time with `--scanning-max-duration`, and tune throughput with `-c/--concurrency`, `-r/--rate-limit`, and `--max-per-host`. ```bash theme={null} # Override max scan duration vigolium scan -t https://example.com --scanning-max-duration 2h # Adjust concurrency and rate limit vigolium scan -T targets.txt -c 100 --rate-limit 200 --max-per-host 5 ``` | Flag | Default | Effect | | ------------------------- | ------------------- | -------------------------------------------------------------------------------------------------- | | `--scanning-max-duration` | `0` (use config) | Hard cap on total scan wall-clock time (e.g. `30m`, `1h`, `2h`) | | `-c` / `--concurrency` | `50` | Number of concurrent scan workers | | `-r` / `--rate-limit` | `100` | Maximum HTTP requests per second (global) | | `--max-per-host` | `50` | Maximum concurrent requests to any single host | | `--no-waf-pacing` | `false` (pacing on) | Disable proactive WAF/CDN pre-throttling (reactive back-off after a confirmed block still applies) | Lower `--rate-limit` and `--max-per-host` to stay gentle on fragile targets; raise `-c` and `--rate-limit` to go faster against robust infrastructure. Under `-P/--parallel`, each child keeps its own `--concurrency`, so real in-flight requests are roughly `P × --concurrency`. **WAF-aware pacing.** The first time earlier-phase traffic fingerprints a host behind a recognized CDN/WAF edge (CloudFront, Cloudflare, Akamai, Imperva/Incapsula, Sucuri, or Azure Front Door — detected from headers on ordinary `200`s), the per-host rate limiter **proactively** drops that host's concurrency to `MaxPerHost/4` and ramps back up on healthy responses, so an aggressive active phase can't burst the edge into a rate-based block that hides findings. A one-time-per-host `[waf-pacing-armed]` notice prints the drop (e.g. `40→10`) to stderr and the session log. Pass `--no-waf-pacing` to turn off this proactive pre-throttle — the reactive back-off after a *confirmed* WAF block still applies. Global flag on `scan` / `scan-url` / `scan-request` / `run` / `ingest`. *** ## 15. Inspect & change any config value Vigolium settings live in `~/.vigolium/vigolium-configs.yaml`. Read them with `vigolium config view` (alias for `config ls`) and write them with `vigolium config set ` using dot-notation — no need to hand-edit the YAML. ```bash theme={null} # View everything, or filter by a substring / fuzzy key match vigolium config view vigolium config view notify vigolium config view database.sqlite # Filter with a glob pattern (matches the full key or any dot-segment) vigolium config view 'oast*' vigolium config view 'kno*' # → known_issue_scan.* # Reveal redacted secrets (tokens, API keys) in plaintext with --show-secrets vigolium config view notify --show-secrets ``` Set values with dot-notation keys — the three forms below are equivalent, so you can copy a line straight out of `config view` output: ```bash theme={null} vigolium config set notify.enabled true vigolium config set database.driver postgres vigolium config set server.service_port 8080 # List-valued keys take comma-separated values vigolium config set notify.severities high,critical # 'key = value' and 'key=value' also work (paste-friendly) vigolium config set 'oast.server_url = your-oast-domain.com' ``` `config view` sorts keys and prints the active config file path at the bottom. Sensitive values (API keys, tokens, credentials) show as `[redacted]` unless you pass `--show-secrets`, which reveals them in plaintext and prints a warning to stderr (the generic `-F/--force` no longer reveals secrets). `config set` validates the key and writes it back to the same file. Reset everything to clean defaults with `vigolium config clean`. See [Configuration](/getting-started/configuration) for the full key reference. *** ## 16. Set a custom OAST domain Out-of-band callback detection (SSRF, blind RCE, OOB SQLi, etc.) uses an [interactsh](https://github.com/projectdiscovery/interactsh) server, `oast.pro` by default. Point Vigolium at your own server with the `oast.server_url` / `oast.token` config keys: ```bash theme={null} # Point Vigolium at your own interactsh / OAST server vigolium config set oast.server_url your-oast-domain.com vigolium config set oast.token your-oast-token ``` Or set it directly in `~/.vigolium/vigolium-configs.yaml`: ```yaml theme={null} oast: enabled: true server_url: your-oast-domain.com token: your-oast-token ``` Config values support `${VAR}` / `${VAR:-default}` expansion, so you can keep the domain and token in environment variables instead of writing them into the file: ```yaml theme={null} oast: server_url: ${VIGOLIUM_OAST_DOMAIN:-oast.pro} token: ${VIGOLIUM_OAST_TOKEN} ``` OAST is enabled by default (`oast.enabled: true`). The token is optional (only needed by servers that require auth). See the full field list, poll interval, grace period, and blind-XSS payload source, in [Configuration → `oast`](/getting-started/configuration#oast). *** ## 17. Generate a static HTML report Add `--format html` with `-o/--output` to render a self-contained, ag-grid HTML report. You can produce it **during a scan**, or after the fact from data already stored or imported. ```bash theme={null} # During a scan (combine formats, e.g. jsonl,html) vigolium scan -t https://example.com --format html -o report.html # From data already in the database vigolium export --format html -o report.html --report-title "Acme Q3 Scan" # From an imported audit / JSONL / SQLite, in one step vigolium import ./audit-output/ --format html -o report.html # One consolidated report across many standalone scan files (no import needed) vigolium export --glob-db 'scans/*.sqlite' --format html -o report.html ``` Brand and filter the report with the same flags across `export` / `import`: ```bash theme={null} vigolium export --format html -o report.html \ --report-title "Acme External Scan" \ --report-target https://acme.example.com \ --severity critical,high \ --search sqli ``` Need several formats? `--format` is a comma-separated list, and the database is read **once** for all of them: ```bash theme={null} vigolium export --format html,markdown,bundle -o acme-q3 # → acme-q3.html acme-q3.md acme-q3.tar.gz ``` With more than one format, `-o` is a shared **base path** (each format appends its own extension) and becomes required — one `-o` can't name three files. A single format still uses `-o` verbatim. `{ts}` expands once for the whole run, so a slow `pdf` render lands on the same timestamp as its siblings, and if one format fails the others are still written. HTML requires `-o/--output`. Other report formats: `report`, `pdf` (rendered via headless Chrome), and `markdown` (alias `md`). The output path accepts `gs:///` URLs and a `{ts}` timestamp placeholder. See [Output & Reporting → HTML](/getting-started/output-and-reporting#html). *** ## 18. Notify (webhook) when a scan completes The `webhook` notify provider fires **one POST per scan** when it reaches a terminal state (completed or failed), regardless of severity, perfect for CI pipelines and dashboards. ```bash theme={null} # Fire a webhook POST when each scan finishes vigolium config set notify.enabled true vigolium config set notify.provider webhook vigolium config set notify.webhook.url https://hooks.example.com/vigolium ``` Or configure it in `~/.vigolium/vigolium-configs.yaml`: ```yaml theme={null} notify: enabled: true provider: webhook # webhook | telegram | discord ("" = all configured) webhook: url: https://hooks.example.com/vigolium authorization: "Bearer xxx" # optional Authorization header timeout_sec: 10 severities: [high, critical] # gate the per-finding telegram/discord alerts ``` `webhook` fires once per scan regardless of severity; `telegram` and `discord` instead fire **per finding**, gated by `notify.severities`. Telegram/Discord also honor env-var fallbacks (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, `DISCORD_WEBHOOK_URL`), so you can keep tokens out of the config file. See [Configuration → `notify`](/getting-started/configuration#notify). *** ## 19. Update Vigolium `vigolium update` re-runs the official installer to fetch the latest release binary, then refreshes the local nuclei-templates checkout used by the known-issue scan. ```bash theme={null} # Update the binary + nuclei templates to the latest release vigolium update # Only refresh nuclei templates (skip the binary) vigolium update --skip-binary # Only reinstall the binary (skip templates) vigolium update --skip-templates # Check the version you're running vigolium version ``` `vigolium update` runs the same installer as `curl -fsSL https://vigolium.com/install.sh | bash`, installing to `~/.local/bin/vigolium` (checksum-verified). **Homebrew, npm, and Docker** installs upgrade through their own tooling instead: `brew upgrade vigolium`, `npm update -g @vigolium/vigolium`, or `docker pull j3ssie/vigolium:latest`. Vigolium also checks for a newer release on startup (at most once/day); silence it with `VIGOLIUM_DISABLE_UPDATE_CHECK=1`, or auto-apply and re-exec with `VIGOLIUM_AUTO_UPDATE=1`. *** ## 20. How do I use Burp Suite with Vigolium? The **Vigolium Burp Suite extension** turns Burp into a front-end for a running Vigolium server: as you browse, requests/responses forward into Vigolium (and, with `--mirror-fs`, onto disk), you launch native and agentic scans straight from Burp's right-click menu, and findings stream back into both Burp and the Web UI. It doesn't run the scanner inside Burp's JVM — it forwards traffic and polls the server, so scans keep running even after Burp closes. ```bash theme={null} # The ingestion server the extension talks to (add --mirror-fs to also write traffic to disk) export VIGOLIUM_API_KEY=my-secret-key vigolium server --mirror-fs output-dir ``` Grab `burp-vigolium.jar` from [github.com/vigolium/burp-vigolium](https://github.com/vigolium/burp-vigolium), then **Extensions → Installed → Add → Extension type: Java**. A **Vigolium** tab appears. Open **Vigolium → Settings**, enter the **Server URL** (default `http://127.0.0.1:9002`) and **API Key**, then click **Test Connection**. What the extension can do: | Feature | How | | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | **Send one request** to ingestion / native scan / agentic scan | Right-click in Proxy History or Repeater — or the `Ctrl+Alt+V` / `Ctrl+Alt+N` / `Ctrl+Alt+A` hotkeys | | **Forward everything** through Burp Proxy automatically | **Bridge → Proxy Interception → Turn ON** (pair with **In-scope only** + filter rules) | | **Snapshot the whole Target Site map** into Vigolium | **Bridge → Target Site Map Snapshot** (`Ctrl+Alt+S`, or auto every N minutes) | | **Bidirectional live bridge** — read Burp history from the CLI, or push Vigolium traffic back into Burp's Site map | Enable **Bridge → Enable live bridge** (loopback `http://127.0.0.1:9009`), then drive it from the CLI | | **Send exact bytes through Burp's engine**, or stage traffic into Repeater / Organizer | `replay` / `fuzz` / `finding` with `--send-via-burp`, `--to-repeater`, `--to-organizer`, or `--push-to-burp` (all need `--burp-bridge-url`) | | **Do all of the above from Caido** | Install the [Caido plugin](/getting-started/caido-plugin) — same protocol, same flags, `--caido-bridge-url` alias | Once the loopback bridge is enabled in Burp, drive it from Vigolium with the `--burp-bridge-url` flag (export `VIGOLIUM_BURP_BRIDGE_URL` only if you'd rather not repeat the flag): ```bash theme={null} # Merge live Burp Proxy history into the local traffic view vigolium traffic --burp-bridge-url http://127.0.0.1:9009 # Persist the traffic selected by the active filters into Vigolium's DB vigolium traffic --burp-bridge-url http://127.0.0.1:9009 --save-to-vigolium-db # Copy the selected Vigolium DB traffic back into Burp's Target Site map vigolium traffic --burp-bridge-url http://127.0.0.1:9009 --save-to-burp # Save a mutated replay + its fresh response straight into Burp's Site map vigolium replay --finding-id 42 --burp-bridge-url http://127.0.0.1:9009 --save-to-burp # Send a replay through Burp's OWN HTTP engine — exact bytes (--http-mode http1 for desync) vigolium replay -i req.txt --burp-bridge-url http://127.0.0.1:9009 --send-via-burp --http-mode http1 # Push a finding's evidence request+response into Burp's Organizer for manual confirmation vigolium finding xss --burp-bridge-url http://127.0.0.1:9009 --push-to-burp ``` Pairs naturally with [section 1 (live filesystem mirror)](#1-mirror-ingested-traffic-findings-to-a-live-filesystem-tree) and [section 3 (passive/secret scan on forwarded traffic)](#3-passive-secret-scan-on-forwarded-traffic): run `vigolium server -S --passive-only --mirror-fs output-dir -A` and the extension feeds it while findings and readable request/response files appear in real time. **Full walkthrough** — install, connect, the three dispatch workflows, proxy forwarding, site-map snapshots, and the live bridge: [Using Vigolium with Burp Suite](/getting-started/burp-suite). ### Using Caido instead The [**Vigolium Caido plugin**](https://github.com/vigolium/caido-vigolium) does everything above from Caido: install `caido-vigolium.zip` via **Plugins → Install Package → From File**, set the server URL + API key under **Vigolium → Settings**, and dispatch with `⌘⌃V` / `⌘⌃N` / `⌘⌃A` (`Ctrl+Alt+…` on Windows/Linux) or the `⌘K` palette. The live bridge is on by default on the same loopback port, so every bridge command works unchanged — `--caido-bridge-url` is an alias for `--burp-bridge-url`: ```bash theme={null} # Merge live Caido traffic into the local traffic view, then keep only the Caido rows vigolium traffic --caido-bridge-url http://127.0.0.1:9009 vigolium traffic --caido-bridge-url http://127.0.0.1:9009 --source caido # One-shot persist of everything the bridge exposes vigolium import --caido-bridge-url http://127.0.0.1:9009 # Issue exact bytes through Caido's HTTP stack, staged into a Replay session vigolium replay -i req.txt --caido-bridge-url http://127.0.0.1:9009 --send-via-burp --to-repeater ``` Bridge rows are labelled by the vendor that actually answered (`source: caido` vs `burp`), so one `--source` filter separates them — including traffic the plugin *pushed* to the server, which sets `X-Vigolium-Source: caido`. Record UUIDs keep the `burp:` prefix for both, since that names the bridge route rather than the vendor. **Full walkthrough:** [Using Vigolium with Caido](/getting-started/caido-plugin). *** ## 21. Triage findings with a coding agent (Claude, Codex) Vigolium is built to be shelled out to by an LLM/coding agent. The `finding` and `traffic` read commands emit **compact, token-aware** output under `-j/--json` (headers kept, bodies bounded to \~1–2 KB with `body_size`/`body_sha256`/`body_truncated`, binary bodies stubbed, findings windowed to a `response_evidence` snippet), so an agent can survey dozens of findings without blowing its context window and pull full detail only for the ones worth confirming. Teach your agent to drive Vigolium by installing the **`vigolium-scanner` skill**. The copy embedded in your binary always matches the CLI version, so prefer the native installer: ```bash theme={null} # Install the embedded skill (recommended — version-matched to your binary) vigolium skills install --agent claude --scope project # --agent claude|codex|agents, --scope project|global vigolium skills # list what's bundled # Or pull the latest from the repo (github.com/vigolium/skills) bunx skills add vigolium/skills --skill vigolium-scanner --agent --yes ``` A two-pass triage loop that keeps tokens in check: ```bash theme={null} # Pass 1 — cheap survey: rank many findings, only the fields you need. vigolium finding --min-severity high --json --compact \ --fields id,severity,module_id,url,matched_at # Pass 2a — deep-read one finding as a self-contained, parseable bundle # (finding + linked request/response embedded, bodies still bounded). vigolium finding --id 42 --json --with-records # Pass 2b — deep-read as Markdown (verbatim request/response in fenced http blocks, # plus the What/Exploit/Fix prose) — often the nicest format for an agent or a # human to judge a single finding. Window it with -S --compact so a big page # doesn't flood the context. vigolium finding -S --db ./scan.jsonl --id 42 --markdown --compact ``` Output-shaping flags on `finding` / `traffic` / `db ls`: | Flag | Effect | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-j`/`--json` | machine-readable compact output (the entry point) | | `--compact` | metadata only, drop bodies — cheapest survey pass | | `--fields a,b,c` | project to just these top-level keys, cuts tokens hard | | `--with-records` | *(finding)* embed the linked HTTP records → self-contained triage bundle | | `--full-body` | complete decoded bodies (use when writing an exploit — see [section 22](#22-show-how-to-exploit-a-finding)); also un-compacts `--markdown` response bodies | | `--markdown` | render the finding with request/response in fenced `http` blocks (response bodies compacted to a preview by default) | | `--tree` *(finding / traffic)* | host/path hierarchy view; recurring findings collapse by title + severity into one `×N` node | | `--pick 2-4` *(finding)* | narrow to specific 1-based positions after filters + sort (`2`, `1,3`, `2-4`); composes with `--raw`/`--burp`/`--markdown`/`--json` | | `--search a --search b` | repeatable, AND-combined — each added term further narrows the match | | `--exclude-search x` | *(finding / traffic)* repeatable inverse of `--search` — drop a row if **any** term appears (also `--exclude-header` / `--exclude-body`) | | `--severity crit,high` / `--sev h,c` | *(finding; `--severity` on `db ls`)* explicit severity **set** — single-letter shorthands + unambiguous prefixes (`crit`, `me`, `info`) | | `--min-severity high` | severity **floor** — `high` expands to `high,critical` (contrast the explicit `--severity` set; `--severity` wins if both are set) | | `--record-kind finding,candidate` | *(finding / `db list`)* filter by record kind — `finding` (default), `candidate`, `observation` (comma-separated) | | `--module-type active,passive` | *(finding / `db list`)* filter by producing module type — `active`, `passive`, `nuclei`, `agent`, `source-tools`, `oast`, `extension` | | `--agentic-scan ` | only findings from a given agent run (expands to the whole run tree) | Under `--json`, bodies are **always** bounded regardless of `-S`. As of v0.2.1, `--markdown` also **compacts response bodies by default** — the response renders as a window around the finding's `matched_at`/evidence (or a leading preview) rather than a whole page, regardless of `-S`/`--compact`; pass `--full-body` to render bodies whole. The request is always shown whole (it carries the payload). On an interactive terminal the Markdown is syntax-highlighted; piped/redirected output stays plain, greppable Markdown. An agent run (`agent autopilot|swarm|audit`) under `--json` even prints a ready-made follow-up command in its summary: `vigolium finding --agentic-scan --json --with-records`. Full guide: [Using Vigolium in your agent](/agentic-scan/using-vigolium-in-your-agent). ### The JSON envelope Since **v0.4.5** every `-j/--json` command emits the *same* outer object, so one parser handles all of them. Don't write key fallbacks. ```jsonc theme={null} { "schema_version": 1, "command": "traffic", "project_uuid": "…", "db_path": "/home/me/.vigolium/database-vgnm.sqlite", "total": 39, "offset": 0, "limit": 100, "items": [ … ], "query": "vigolium replay -u ", "generated_at": "2026-09-04T10:11:12.345Z", "generated_at_ms": 1788453072345 } ``` | Field | Why it is there | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema_version` | Gate on it. Bumped on any breaking field change | | `items` | **Canonical** row array. Each command also writes its historical key (`records`, `findings`, `scans`, `rows`, `stats`) as a *deprecated alias* pointing at the same slice — parse `items`, not the alias | | `db_path` | The database this command actually opened. **Assert it**: the open order ends at one shared default file, so a silent fall-through mixes engagements | | `query` | A ready follow-up command for the obvious next step. Run it rather than composing your own | | `generated_at` / `_ms` | RFC3339 with **exactly 3** fractional digits, plus an epoch-millisecond sibling. Both compare safely against a JS `toISOString()`; the old microsecond form sorted `…785113Z` *before* `…785Z` | `db stats -j` uses this envelope too — it used to be the documented exception that dumped its raw struct, and a contract with a hole in it is one consumers special-case forever. Check the contract once at startup, so drift is a startup failure rather than a parse error in production: ```bash theme={null} vigolium version --json # {version, commit, schema_version, db_schema_version} ``` *** ## 22. Show how to exploit a finding Once a finding is confirmed, pull the exact request that triggered it, then re-fire it — verbatim, or with an iterated payload — to build a reproducible proof-of-concept. Start by fetching the finding with full, decoded bodies so you have the complete request to work from: ```bash theme={null} # Self-contained bundle with COMPLETE bodies — everything needed to write an exploit. vigolium finding --id 42 --json --with-records --full-body # Or as a report-ready Markdown PoC (request/response in fenced http blocks). # --markdown compacts response bodies by default; add --full-body for the whole page. vigolium finding --id 42 --markdown --full-body > finding-42.md ``` Reproduce the finding by replaying its linked record: `vigolium replay --finding-id` re-sends the exact request behind a finding. To iterate a payload at one insertion point, drive [`vigolium fuzz`](#fuzz-one-request-with-a-custom-payload-set); to override the exact bytes, use `--raw-request`/`--raw-request-file`: ```bash theme={null} # Re-fire the exact request that produced finding 42, human-readable summary. vigolium replay --finding-id 42 --pretty # Prove the injection: iterate one insertion point with a payload class or wordlist. # (Grab the linked record UUID from `vigolium finding --id 42 --json --with-records`.) vigolium fuzz -u --point URL_PARAM:id --class sqli # Override the reproduction request's exact bytes (e.g. a hand-built PoC). vigolium replay --finding-id 42 --pretty --raw-request-file exploit.txt # Send the reproduction through Burp so you can inspect/step it manually. vigolium replay --finding-id 42 --proxy http://127.0.0.1:8080 # Point the same request at a staging host, carrying an auth session. vigolium replay --finding-id 42 --pretty \ --target https://staging.example.com --auth-session my-session ``` If you mirrored traffic to disk ([section 1](#1-mirror-ingested-traffic-findings-to-a-live-filesystem-tree)) or used `--format fs`, each `.req` file is **directly replayable**: its leading `@target ://` line names the destination, so strip line 1 to get raw HTTP you can hand to `curl`, an agent, or feed back into `vigolium replay --raw-request-file ` (pass the target from that first line with `-t`). With the [`vigolium-scanner` skill](#21-triage-findings-with-a-coding-agent-claude-codex) installed, you can ask your agent in plain language — *"confirm finding 42 is exploitable and give me a PoC"* — and it will run the `finding`, `fuzz`, and `replay` commands above, iterate the payload, and compare responses for you. Use `vigolium fuzz` for payload / insertion-point work; use `--raw-request`/`--raw-request-file`/`-i` to replace the whole request when you need full control. Only test targets you are authorized to assess. *** ## 23. Run an exact set of modules (passive-only, JS beautify) [Section 9](#9-target-one-vulnerability-class-or-technology)'s `-m/--modules` is a **fuzzy** filter that only narrows the **active** modules (passive modules always stay "all"). When you want to run a **precise** set — including passive modules on their own — reach for `--module-id` (exact match against **both** the active and passive registries, repeatable) and `--passive-only`. ```bash theme={null} # Run EXACTLY these modules and nothing else (repeatable, or comma-separated) vigolium scan-url -S -t https://example.com --module-id js-beautify --module-id secret-detect vigolium scan-url -S -t https://example.com --module-id js-beautify,secret-detect # JavaScript beautify only — unminify/unpack a bundle into readable modules vigolium scan-url -S -t https://example.com/app.min.js --module-id js-beautify # Passive modules only — no active traffic is sent (secrets, headers, disclosure, ...) vigolium scan-url -S -t https://example.com --passive-only # Passive-only, narrowed to a specific passive module vigolium scan-url -S -t https://example.com --passive-only --module-id secret-detect ``` `-m` vs `--module-id`, side by side: | Flag | Matching | Registries it selects from | Repeatable | | ------------------ | ---------------------------- | ------------------------------------- | ---------- | | `-m` / `--modules` | fuzzy (substring on id/name) | **active** only (passive stays "all") | yes | | `--module-id` | **exact** module ID | **both** active **and** passive | yes | `--module-id` requires an **exact** id — an unknown value warns (`does not match any known module`) rather than silently matching nothing, so reach for `-m` when you want loose matching. Find exact ids with `vigolium module ls `. `--passive-only` and `--no-passive` are mutually exclusive (nothing would run). Available on `scan`, `scan-url`, and `scan-request`. For fuzzy `-m` / `--module-tag` filtering, see [section 9](#9-target-one-vulnerability-class-or-technology). *** ## 24. Scan a URL and print findings to the console For a quick single-URL scan where you just want the results on screen, add `--print-finding`. After the scan it renders each finding to stdout as **Markdown** — description, matched evidence, and the raw request/response in fenced `http` blocks — exactly like `vigolium finding --markdown` ([section 21](#21-triage-findings-with-a-coding-agent-claude-codex)), with no follow-up command and nothing persisted. Pair it with `-S` (throwaway database) and `--silent` (drop the banner and progress noise) for clean output. ```bash theme={null} # Quick scan → findings printed inline as Markdown, nothing persisted vigolium scan-url -S -t https://example.com -m js-beautify -m secret-detect --silent --print-finding # Same, but pin the EXACT modules (--module-id also restricts passive; see section 23) vigolium scan-url -S -t https://example.com \ --module-id js-beautify --module-id secret-detect \ --silent --print-finding # Passive-only sweep of one URL, printed to the console vigolium scan-url -S -t https://example.com --passive-only --silent --print-finding # Also dump the scan's HTTP traffic — as a host/path tree, or raw request/response pairs vigolium scan-url -S -t https://example.com --silent --print-traffic-tree vigolium scan-url -S -t https://example.com --silent --print-traffic ``` `--print-finding` works on `scan`, `scan-url`, `scan-request`, and `run`. It prints **nothing** when the scan finds nothing, so `--silent --print-finding` yields either the findings or an empty screen — ideal for a fast triage loop. The Markdown is identical to `vigolium finding --markdown` ([section 21](#21-triage-findings-with-a-coding-agent-claude-codex)); to save it, redirect stdout (`... --print-finding > findings.md`). Enabling it routes the scan through the full runner so each finding's linked request/response is available to render, which is why it pairs naturally with `-S`. The same commands take **`--print-traffic-tree`** (host/path hierarchy, like `traffic --tree`) and **`--print-traffic`** (raw request/response pairs, like `traffic --raw`) to dump the run's traffic after it finishes — set both to print the tree first, then the raw pairs. *** ## 25. View HTTP traffic in the database Everything Vigolium scans, ingests, or records lands in the database. Browse it with `vigolium traffic` (aliases `tf`, and `vigolium db ls http_records` — `db ls` takes a positional table name; the old `--table` form is deprecated). With no flags it shows the 100 most recent records, newest first; add filters, search, or a display format to narrow and shape the output. ```bash theme={null} # Browse recent traffic (newest first, capped at 100) vigolium traffic # List EVERY stored record (lift the -n/--limit cap), or page through vigolium traffic --all vigolium traffic -n 50 --offset 100 # Host/path hierarchy tree vigolium traffic tree ``` Search and filter to find the records you care about: ```bash theme={null} # Fuzzy search across the full request/response — URL, path, headers, and body (positional term or --search) vigolium traffic admin vigolium traffic --search "/api/users" # Search within header names/values, or request/response body content vigolium traffic --header "Authorization" vigolium traffic --body "password" # Exclude (inverse of --search/--header/--body): drop rows where the term appears vigolium traffic --search api --exclude-search "/health" # keep api traffic, drop the health checks vigolium traffic --exclude-search .css --exclude-search .js # repeatable — drop if ANY term matches vigolium traffic --exclude-header Authorization # drop rows carrying an Authorization header # Filter by host, status, method, path, source, or date range vigolium traffic --host "*.example.com" vigolium traffic --status 401,403,500 --method POST vigolium traffic --source ingest-proxy vigolium traffic --from 2026-01-01 --to 2026-02-01 # Relative offsets and named days work too (aliases: --since / --until) vigolium traffic --since 2d # last two days vigolium traffic --since today # since local midnight vigolium traffic --since '1h30m ago' vigolium finding --since yesterday --min-severity high # Combine filters: POSTs to the API that returned 200 and leaked a token vigolium traffic --host api.example.com --method POST --body "token" --status 200 ``` `--from`/`--to` (aliases `--since`/`--until`) work the same on `finding`, `traffic`, `db ls`, `db export`, and `replay`. Bare dates and wall-clock times resolve in your **local** zone, and a bare date on the upper bound snaps to end-of-day — so `--from 2026-08-01 --to 2026-08-01` is the whole of Aug 1, not an empty range. See [Date filtering](/others/cli-references#date-filtering). Choose how each record is rendered: ```bash theme={null} # Burp Suite-style colored request/response, or full raw HTTP vigolium traffic --burp --host example.com -n 5 vigolium traffic --raw --host example.com -n 5 # Pick / drop columns vigolium traffic --columns host,method,path,status,size vigolium traffic --exclude-columns source,words # Machine-readable JSON for scripting / jq (compact, token-aware bodies) vigolium traffic -j vigolium traffic --host api.example.com --status 200 -j ``` Read a colleague's standalone export without touching your own DB by pointing `-S` at the file (a `.jsonl` export or a standalone `.sqlite`), or read across many at once with `--glob-db`: ```bash theme={null} vigolium traffic -S --db scan.sqlite vigolium traffic --glob-db 'scans/*.sqlite' ``` All filter flags (`--host`, `--status`, `--method`, `--path`, `--source`, `--search`, `--header`, `--body`, `--exclude-search`, `--exclude-header`, `--exclude-body`, `--from`/`--to`) stack, and `--sort` + `--asc` control ordering. `--search` spans the **full request/response** — URL, path, headers, and body (for `finding` it also covers module metadata and linked records); repeated `--search` terms AND-narrow, while repeated `--exclude-search` terms drop a row if **any** matches. Add `--replay` to re-send the matched records instead of listing them (see [section 2](#2-replay-all-stored-traffic-through-burp-suite)). The compact `-j/--json` output is the same agent-friendly serializer described in [section 21](#21-triage-findings-with-a-coding-agent-claude-codex). Prune the database when you're done — a selector is always required, so a bare `db clean` is rejected: ```bash theme={null} vigolium db clean --scan-uuid my-scan # delete one scan's records vigolium db clean --host api.example.com # delete by host vigolium db clean --all --force # empty every data table vigolium db reset --force # delete + recreate the DB file (SQLite; VACUUMs automatically) ``` *** ## 26. Set up the AI agent (Codex or Claude CLI) Vigolium's AI features (autopilot, swarm, source-code audit, query) all run through one in-process runtime called **olium**, which talks to a provider. Pick one provider and give it credentials. The two most common setups are below; verify any of them with `vigolium ol -p 'what model are you running'`. **Method 1 — Codex (cheapest, with a ChatGPT subscription).** If you already use OpenAI's **Codex CLI**, vigolium reuses the same OAuth credential file, no API key, refresh handled automatically. This is the recommended default. ```bash theme={null} # 1. Install the Codex CLI (one-time) and log in. codex login codex exec 'hello' # sanity check — should print a model name # 2. Pin vigolium to it (defaults already match; this just makes it explicit). vigolium config set agent.olium.provider openai-codex-oauth vigolium config set agent.olium.oauth_cred_path ~/.codex/auth.json vigolium config set agent.olium.model gpt-5.5 # 3. Verify. vigolium ol -p 'what model are you running' ``` **Method 2 — Claude CLI (`claude` shell-out).** Delegate to the `claude` binary on your `$PATH`, so olium uses whatever auth `claude` itself is configured with (plus your personal `CLAUDE.md`, MCP servers, and installed skills). ```bash theme={null} which claude # must resolve vigolium config set agent.olium.provider anthropic-cli # alias: anthropic-claude-cli vigolium config set agent.olium.model claude-opus-4-7 vigolium ol -p 'what model are you running' ``` Once `vigolium ol` returns a model name, every agent command works: `vigolium agent autopilot`, `vigolium agent swarm`, `vigolium agent query`, and `vigolium agent audit`. These are two of several providers, olium also supports the Claude Code Agent SDK bridge, Anthropic OAuth/API-key, and any OpenAI-compatible backend (Ollama, OpenRouter, LM Studio, vLLM). If you already pay for a coding agent (Codex, Claude, Grok, …), the recommended path is to route it through a local OpenAI-compatible proxy instead of buying a new key: [Set up the agent → reuse your subscription](https://docs.vigolium.com/getting-started/setup-agent#recommended-reuse-the-coding-agent-subscription-you-already-have). For the full provider matrix, credential handling, and the source-code audit drivers, see the [setup guide](https://docs.vigolium.com/getting-started/setup-agent). *** ## 27. Export existing SQLite data to a browsable `fs` tree Already have a scan sitting in a `.sqlite` database (your project DB or a standalone export)? Turn it into the same **flat, browsable filesystem tree** the live mirror produces ([section 1](#1-mirror-ingested-traffic-findings-to-a-live-filesystem-tree)), so a coding agent (or plain `ls`/`grep`/`jq`) can investigate it with no DB. Use `--format fs` on `export` or `db export`, it writes two sibling directories off the `-o` base (defaulting to `vigolium` in the cwd when no `-o` is given, so `-o run` yields `run-traffic/` + `run-findings/`). ```bash theme={null} # Whole project DB → run-traffic/ + run-findings/ vigolium export --format fs -o run # A standalone .sqlite export, read-only (project scoping off, nothing written back) vigolium export -S --db scan.sqlite --format fs -o run # Many standalone exports merged into one tree (--glob-db implies -S) vigolium export --glob-db 'scans/*.sqlite' --format fs -o run # Filtered export via `db export` — honors --host/--status/--method/--severity/--search/--limit vigolium db export --format fs -o run --severity high,critical --host api.example.com # Drop the .resp.* files (request-only tree) vigolium export --format fs -o run --omit-response ``` What you get under each `-traffic//` and `-findings//`: | Path | Contents | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `-traffic//.req` | Raw request (leading `@target ://` line, then the request verbatim, replayable by stripping line 1) | | `-traffic//.resp.headers` | Status line + response headers | | `-traffic//.resp.body` | Response body (gzip-decoded so it greps clean) | | `-traffic//index.json` | Flat, jq-friendly array of ids → method/url/status/content-type (+ top severity) | | `-findings//.md` | Each finding, cross-linked to its `.req` file | Ids are zero-padded and assigned in `sent_at` order, so re-exports are reproducible. This is the one-shot counterpart to the server's live `--mirror-fs` ([section 1](#1-mirror-ingested-traffic-findings-to-a-live-filesystem-tree)): `export` writes a one-shot `index.json` array, the mirror appends to `index.jsonl`. `export` honors `--search`/`--severity`/`--limit`; `db export` adds the full `--host`/`--status`/`--method`/`--path`/`--scan-uuid`/`--from`/`--to` filter set (scoped to the active project). `--split-by-host` is a no-op here, `fs` already splits by host. To read a colleague's export without merging it into your DB first, see [section 4](#4-import-external-scan-data-into-the-database). *** **Oh dear, you actually read to the end.** Here's the secret the config file kept nudging you toward: by default every Vigolium request announces itself with a `Vigolium/` User-Agent, so a friendly blue team can spot your authorized scan in their logs in seconds. Go full ninja only when you *mean* to be sneaky: ```bash theme={null} # Persist it in your config vigolium config set scanning_strategy.http.user_agent random # Or a one-off via env var (overrides the config value for that run) export VIGOLIUM_DEFAULT_UA=random ``` # Choosing a Scan Mode Source: https://docs.vigolium.com/getting-started/choosing-a-mode Vigolium ships several scan modes, native, native-via-Burp, audit agent, autopilot, and swarm. This page helps you pick the right one for the job, plus the intensity preset (quick / balanced / deep) that controls how far it goes. If you only remember one thing: **native scan covers breadth, audit agent covers depth, autopilot is for hands-off black-box, and swarm is for hand-crafted requests.** Pick one of the five below, then dial the intensity at the bottom of this page. ## At a glance | Mode | Best for | Black-box / White-box | Needs source code? | Needs an LLM? | | ------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------- | ------------------ | ---------------------------------- | | [Native Scan](#1-native-scan) | Maximum-speed full-coverage blackbox sweep | Black-box | No | No | | [Native Scan via Burp Plugin](#2-native-scan-via-burp-suite-plugin) | Precise scan against one request/URL with every parameter | Black-box | No | No | | [Audit Agent](#3-audit-agent-vigolium-audit--piolium) | Thorough whitebox source-code audit | White-box | **Yes** | Frontier LLM (Claude / Codex / Pi) | | [Autopilot Agent](#4-autopilot-agent) | Hands-off black-box scan with a real browser + JIT extensions | Black-box (optionally source-aware) | Optional | Yes | | [Swarm Agent](#5-swarm-agent) | Generate tailored payloads against a single request | Black-box | No | Yes | *** ## 1. Native Scan Think of it as a supercharged version of **Burp Active Scanner + Nuclei + ffuf + Katana + Wayback Machine**, all driven by one binary, in parallel, with shared state. Reach for it when you want the broadest possible blackbox sweep against a target as fast as possible: external-data harvest, content discovery, browser-driven spidering (SPA-aware), known-issue scanning, and active/passive dynamic assessment across **323 scanner modules**, all in one run. ```bash theme={null} # Balanced full pipeline vigolium scan -t https://example.com # Stateless one-shot, JSONL out, nothing left behind vigolium scan --stateless -t https://example.com --format jsonl -o findings # Crank it to deep vigolium scan -t https://example.com --strategy deep ``` **When to pick it** * You're scoping a fresh target and want everything turned up. * You need repeatable, deterministic output for CI/CD. * You don't have (or don't want to use) an LLM. See [Native Scan & Stateless Scanning](/getting-started/native-scan) and [Strategies](/native-scan/strategies). *** ## 2. Native Scan via Burp Suite Plugin Same native scanner, but invoked from a Burp Suite tab against a single request or URL with **every parameter fuzzed** (headers, cookies, body fields, path segments). Use it when you already have the request in front of you in Burp and want a precise, single-shot scan rather than spidering the entire app. ```bash theme={null} # Equivalent CLI: scan a raw request or curl from the clipboard vigolium scan-request -i request.txt pbpaste | vigolium scan-request # Or scan a single URL with the same surface vigolium scan-url "https://example.com/api/users?id=1" ``` **When to pick it** * You have one request you care about and want the deepest possible parameter coverage on *just that*. * You're triaging a Burp finding and want an automated second opinion. * You want native scan output in your existing Burp workflow. The plugin is a thin wrapper around `scan-url` / `scan-request`, see [Native Scan & Stateless Scanning](/getting-started/native-scan). *** ## 3. Audit Agent (Vigolium Audit + Piolium) A whitebox source-code audit driven by frontier LLMs. Vigolium ships two drivers, both reachable through the unified `vigolium agent audit` dispatcher: * **Vigolium Audit (embedded)**: ships inside the vigolium binary, drives the `claude` or `codex` CLI. Up to 12 phases at `deep`. No extra install. * **[Piolium](https://www.npmjs.com/package/@vigolium/piolium)**: Pi-coding-agent extension. Up to 17 phases at `deep`. Requires the `pi` runtime + `pi install piolium`. Supports any provider Pi supports, including **local models**. ```bash theme={null} # Auto (default): embedded audit, fall back to piolium only if audit is unavailable vigolium agent audit --source ~/src/your-app --mode deep # Force the embedded audit driver vigolium agent audit --driver audit --source ~/src/your-app --mode deep # Piolium, the most thorough audit available vigolium agent audit --driver=piolium --source ~/src/your-app --mode deep # Run both side-by-side under one parent scan, with project-wide dedup vigolium agent audit --driver both --source ~/src/your-app ``` **When to pick it** * You have access to the source and want the deepest possible vulnerability coverage. * You want findings tied back to specific file/line ranges, not just URLs. * You're willing to pay frontier-model token cost, or run Pi + Piolium against a local model if budget matters. The audit agent only delivers its best results on frontier models (Claude Opus, GPT-5.x, etc.). The Piolium path is the only way to drive an audit against a local model, via Pi's own provider config. See [Setting Up the Agent](/getting-started/setup-agent) and [Agentic Security Audit](/getting-started/agentic-security-audit). *** Autopilot and Swarm Mode are still in the early stages. We would greatly appreciate your feedback on any false positives or bug reports ## 4. Autopilot Agent Hands-off black-box scanning where the **olium** runtime drives a real Chromium browser, generates custom JavaScript scanner extensions on the fly, and decides itself which CLI subcommands and modules to run. You can hand it a source-code path too, autopilot becomes source-aware and uses code context to guide its scanning. ```bash theme={null} # Pure black-box vigolium agent autopilot -t https://example.com --intensity balanced # Source-aware — pairs blackbox runtime checks with whitebox code reading vigolium agent autopilot -t https://example.com --source ~/src/your-app # Throw vigolium-audit into the mix in the background vigolium agent autopilot -t https://example.com --source ~/src/your-app --audit=balanced ``` **When to pick it** * You want to throw a target at the scanner and walk away. * The target is JS-heavy / auth-walled, a real browser is the only way to reach it. * You want the agent to write its own scanner extensions for app-specific quirks. See [Autopilot](/agentic-scan/autopilot). *** ## 5. Swarm Agent Guided multi-phase scanning where the agent's job is to **generate tailored payloads against a specific request**. Works best when you have a known-good request from Burp (or anywhere) and want bespoke fuzzing, not a generic active-scanner pass. ```bash theme={null} # Swarm against a single URL vigolium agent swarm --input "https://example.com/api/users?id=1" # Against a raw HTTP request file vigolium agent swarm --input ./request.txt --triage # Add discovery + triage for a fuller pipeline vigolium agent swarm --input https://example.com --discover --triage ``` **When to pick it** * You have a single request and want an LLM to design payloads specifically for it. * Native scan's stock payloads aren't getting hits and you suspect there's something there. * You want AI checkpoints (planning → triage → JS-extension generation) without giving the agent full autonomy. See [Swarm](/agentic-scan/swarm). *** ## Intensity matrix `--intensity quick|balanced|deep` is the cross-cutting dial that controls how far each mode goes. For native scan it also aliases `--strategy`. For agent modes it maps to per-driver phase counts. | | `quick` | `balanced` (default) | `deep` | | ------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Native Scan** | `dynamic-assessment` only, no discovery, no spidering, no known-issue scan. Best for stateless / CI gates. | Full pipeline: discovery + spidering + known-issue + dynamic-assessment. Sensible defaults for everything. | Adds external harvest, recursive discovery, extra modules, longer per-phase durations. The full hammer. | | **Native via Burp** | One-shot `scan-url` / `scan-request`, active modules only. | Active + passive, all insertion points. | Active + passive + heavy mutation strategy and longer timeouts. | | **Vigolium Audit** | **3 phases**, recon, triage, quick scan. CI-friendly. | **9 phases**, adds deep-dive, exploit-design, validation. | **12 phases**, full audit including secondary review and cross-file analysis. | | **Piolium Audit** | **4 phases**, quick triage. | **9 phases**, standard audit. | **17 phases**, exhaustive: recon, primary, longshot, revisit, confirm, merge, diff. | | **Autopilot** | Short max-duration cap, conservative module budget, fewer turns. Smoke-test territory. | Default turn / duration budget. | Larger turn budget, JS-extension generation enabled, runs vigolium-audit in the background if `--source` is provided. Browser tooling is available at every intensity. | | **Swarm** | Single-pass payload generation, no discover, no triage. | Generates + triages payloads; one feedback round. | Full multi-round generate / triage / refine across every insertion point. | ### Rule of thumb * **CI / pre-merge gates** → `quick`. Fast enough to block a PR; catches the obvious. * **Daily regression / scheduled scans** → `balanced`. The default for a reason. * **Pentest engagements / pre-release audits** → `deep`. Time-budget hours, not minutes. ### Combining intensity with strategy For native scans you can also reach for `--strategy lite|balanced|deep` directly, same phase-toggle effect, finer control over which phases run. `--intensity` is a higher-level alias that *also* tunes the scanning profile (pace, module budget, mutation aggression). ```bash theme={null} # Native scan, deep depth vigolium scan -t https://example.com --intensity deep # Or pin one knob at a time vigolium scan -t https://example.com --strategy deep --profile aggressive ``` *** ## Decision shortcuts * **"I just want to scan a URL right now."** → [Native Scan](#1-native-scan), `vigolium scan-url`. * **"I have a Burp request in my hand."** → [Native via Burp Plugin](#2-native-scan-via-burp-suite-plugin) or `vigolium scan-request`. * **"I have the source code and time to spare."** → [Audit Agent](#3-audit-agent-vigolium-audit--piolium). * **"I want the scanner to drive itself overnight."** → [Autopilot Agent](#4-autopilot-agent). * **"I want LLM-crafted payloads against this one endpoint."** → [Swarm Agent](#5-swarm-agent). ## Next steps * [Quickstart](/getting-started/quickstart), run your first scan in under a minute. * [Native Scan & Stateless Scanning](/getting-started/native-scan), every CLI scan recipe. * [Setting Up the Agent](/getting-started/setup-agent), wire up providers before reaching for the agent modes. * [Strategies](/native-scan/strategies), the full strategy / pace / profile reference. * [Scanning Modes Overview](/native-scan/scanning-modes-overview), compare every native-scan command in detail. # Configuration Reference Source: https://docs.vigolium.com/getting-started/configuration Vigolium uses a layered configuration system that merges settings from multiple sources. This document covers the config file format, environment variables, and every configurable section. ## Config File Location The main config file is `~/.vigolium/vigolium-configs.yaml`. It is created automatically on first run with sensible defaults. Vigolium searches for configuration in this order: 1. Path specified via the `--config` flag (error if not found) 2. `~/.vigolium/vigolium-configs.yaml` 3. `./vigolium-configs.yaml` (current working directory) If no config file is found, built-in defaults are used. ## Config Precedence Settings are resolved from highest to lowest precedence: 1. **CLI flags** -e.g. `--concurrency 100`, `--rate-limit 50` 2. **Environment variables** -e.g. `VIGOLIUM_API_KEY`, `VIGOLIUM_PROJECT_UUID` 3. **Scanning profile** -loaded via `--scanning-profile ` (from `~/.vigolium/profiles/`) 4. **Project-level config** -per-project overlay at `~/.vigolium/projects//config.yaml` 5. **Main config file** -`~/.vigolium/vigolium-configs.yaml` 6. **Built-in defaults** -hardcoded in the Go source Higher-precedence sources override lower ones. Within the config file, environment variables can be referenced using `${VAR}` or `$VAR` syntax and are expanded at load time. ## Environment Variables | Variable | Purpose | | ----------------------- | ----------------------------------------------------------------------------------- | | `VIGOLIUM_API_KEY` | API key for the REST server and ingestor client authentication | | `VIGOLIUM_PROJECT_UUID` | Default project UUID for CLI operations (equivalent to `--project-uuid`) | | `VIGOLIUM_PROJECT_NAME` | Default project *name* for CLI operations (equivalent to `--project-name`) | | `VIGOLIUM_PROXY` | HTTP/SOCKS proxy URL, used when `--proxy` is not set | | `VIGOLIUM_HOME` | Base directory for Vigolium data (used by the installer; defaults to `~/.vigolium`) | | `VIGOLIUM_DB_PATH` | Default for the global `--db` flag — pins a shell to one session database | ### `VIGOLIUM_DB_PATH` Set it once and every command in that shell reads and writes the same database file, without repeating `--db`: ```bash theme={null} export VIGOLIUM_DB_PATH=~/engagements/acme/session.sqlite vigolium scan -t https://acme.test # writes here vigolium finding --min-severity high # reads from here vigolium fuzz -u # resolves the record from here ``` Each run prints a one-line notice naming the resolved database (suppressed under `--silent`, `-j`, and `--ci-output-format`). The variable carries one deliberate asymmetry. **Reads** additionally treat the file as a *standalone source* — project scoping is turned off, so every row in the file is listed regardless of the `project_uuid` it carries, exactly as `-S --db ` behaves. **Writes** do not: a scan into the pinned database still records its own project. This means you can point the variable at a colleague's export and query all of it, while your own scans stay properly attributed. An explicit `--db` always wins. The variable is skipped until the file exists, so the first read of a not-yet-written session doesn't fail. And it never sets `--stateless` for you: on `scan` / `scan-url` / `scan-request` / `run` that flag means "scan into a throwaway temp database" and is mutually exclusive with `--db` — passing `-S` explicitly on one of those commands makes the variable a no-op. **A pinned path that exists but is unusable is now a hard error** (v0.4.5), not a silent fall-through to the shared default database. Previously a bad `VIGOLIUM_DB_PATH` quietly redirected every command to `~/.vigolium/database-vgnm.sqlite`, so a scan you believed was isolated to one engagement landed in the file every other engagement shares. If you need to confirm which store you actually got, read `db_path` off any `-j/--json` envelope or off the `scan.started` event. `vigolium log` reads through this same path, so a pinned shell reaches that session's log with project scoping off, exactly like `finding` and `traffic`. Any environment variable can also be interpolated inside `vigolium-configs.yaml`: ```yaml theme={null} database: postgres: password: ${VIGOLIUM_DB_PASSWORD} ``` ## CLI Config Overrides Use `vigolium config set` to update individual config values using dot-notation keys: ```bash theme={null} vigolium config set scanning_pace.concurrency 100 vigolium config set database.driver postgres vigolium config set notify.enabled true vigolium config set notify.severities high,critical vigolium config set server.service_port 8080 ``` These commands modify the main config file directly. For one-off overrides during a scan, use CLI flags instead. ## Config Sections ### `scanning_strategy` Controls which scan phases run for each strategy preset. ```yaml theme={null} scanning_strategy: default_strategy: balanced # lite | balanced | deep heuristics_check: basic scanning_profile: "" # name of a profile to auto-load profiles_dir: ~/.vigolium/profiles/ session: session_dir: ~/.vigolium/sessions/ use_in_discovery: true # apply session headers during discovery/spidering compare_enabled: true # cross-session IDOR/BOLA replay in dynamic-assessment reauth_interval: "" # e.g. "15m" to refresh tokens periodically reauth_on_status: [] # e.g. [401, 403] validate_url: "" # URL to GET after login to verify credentials # Phase toggles per strategy (canonical names): balanced: discovery: true spidering: true known_issue_scan: true dynamic-assessment: true external_harvesting: false # Alternate-port sweep on the target hosts (runs at deep intensity or with # --follow-subdomains; CLI override: --port-sweep-ports) port_sweep: ports: [3000, 5000, 8000, 8008, 8080, 8081, 8082, 8083, 8088, 8888, 9000, 8443, 9443] concurrency: 50 dial_timeout_ms: 1500 http_timeout_ms: 4000 honeypot_ratio: 0.7 # drop a host when this share of swept ports answer (a port-spoofing honeypot) scan_logs: persist_logs: false # write a per-scan session log (browse with `vigolium log`) sessions_dir: ~/.vigolium/native-sessions/ ``` Available strategies and their default phases: | Phase | lite | balanced | deep | | -------------------- | ---- | -------- | ---- | | external\_harvesting | - | - | yes | | discovery | - | yes | yes | | spidering | - | yes | yes | | known\_issue\_scan | - | yes | yes | | dynamic-assessment | yes | yes | yes | > **Phase aliases:** `dynamic-assessment` is the canonical name for active/passive vulnerability scanning. `audit`, `dast`, and `assessment` are accepted aliases on `--only` / `--skip` flags. `discovery` accepts `deparos` / `discover`; `spidering` accepts `spitolas`; `extension` accepts `ext`. For source-aware whitebox analysis, use `vigolium agent swarm --source ` or `vigolium agent audit --source ` instead of a native scan strategy. See [Agent Mode](/agentic-scan/agent-mode). ### `scanning_pace` Centralized speed control. Common values serve as baselines; per-phase subsections override them. ```yaml theme={null} scanning_pace: concurrency: 50 # global worker count rate_limit: 100 # max requests/sec across all hosts max_per_host: 10 # max concurrent requests per host max_duration: 2h # global time cap for a scan phase # Per-phase overrides (zero = inherit from common): discovery: concurrency: 0 rate_limit: 0 concurrency_factor: 0 # multiplier on common concurrency duration_factor: 0 # multiplier on common max_duration spidering: duration_factor: 0.15 # e.g. 2h * 0.15 = 18m known_issue_scan: duration_factor: 3.0 external_harvester: duration_factor: 0.2 audit: duration_factor: 1.0 parallel_passive: true # run passive modules in parallel feedback_drain_timeout: 500ms # wait for feedback loop items ``` ### `discovery` Content discovery (directory/file brute-forcing). ```yaml theme={null} discovery: mode: files_and_dirs # files_and_dirs | files_only | dirs_only scope_mode: subdomain # any | subdomain | exact save_response_body: true enable_malformed_path_probe: false dedup_cluster_cap: 10 # keep at most N near-identical responses per cluster (omit = 10, 0 = disabled) auto_fuzz_low_yield: true # auto-enable FUZZ on the original target when spidering came up empty or hit an SSO wall (omit = on) enrich_targets: false # feed paths from spidering/harvest into discovery recursion: enabled: true max_depth: 5 wordlists: short_file_path: "" # custom wordlist paths long_file_path: "" short_dir_path: "" long_dir_path: "" fuzz_wordlist_path: "" use_observed_names: true use_observed_paths: true use_observed_files: true enable_numeric_fuzzing: false extensions: test_custom: true custom_list: [] test_observed: true test_backup_extensions: true backup_extensions: [] test_no_extension: true engine: case_sensitivity: auto_detect # auto_detect | sensitive | insensitive timeout: 10s # per-request timeout (1s-300s) custom_headers: {} enable_cookie_jar: false max_consecutive_errors: 0 max_consecutive_waf_blocks: 0 observed_max_items: 4000 disable_secret_scan: false # turn off in-process secret scanning during discovery prefix_breaker: # circuit-breaker that stops recursing into trap directories enabled: true # omit = on (CLI: --no-prefix-breaker) min_samples: 0 # 0 = engine default for each of these trip_ratio: 0 prefix_segments: 0 length_bucket: 0 # Post-discovery cleanup of stored records: status retention + reflected-URL-robust dedup deparos_dedup: enabled: true # omit = on drop_client_errors: true # a fuzzed path the server 4xx-rejects is not a discovered resource keep_one_per_host: [401, 403, 429] # collapsed to one representative per host, not dropped keep_per_path: [405, 415, 422] # every distinct path kept — each is a real endpoint, wrong method/payload per_path_cap: 100 # cap on distinct keep_per_path paths per (host, status); negative = unbounded normalize_reflected: true # collapse records differing only by an echoed URL or per-request token # Targeted re-spider of rich/SPA routes discovery turned up, after dedup respider: enabled: true # omit = on max_seeds_per_host: 3 max_seeds_total: 10 per_seed_max_duration: 45s per_seed_max_states: 25 max_depth: 3 step_max_duration: 5m # wall-clock cap for the whole step # Embedded JavaScript intelligence engine (jstangle) jstangle: enabled: true replay_mode: exact # how harvested requests are reconstructed replay_safety: read-only # read-only = never replay state-changing methods source_maps: true asset_graph: true # follow imported chunks to find more endpoints protocol_handshake: false worker_count: 0 # 0 = scale to CPU count memory_budget_mb: 768 cache_mb: 128 job_timeout: 60s max_requests_per_file: 500 max_asset_depth: 4 max_assets_per_parent: 64 max_assets_per_host: 512 max_assets_total: 2048 normal_input_mb: 1 # inputs above this skip the expensive passes max_ast_input_mb: 4 hard_input_mb: 10 # inputs above this are skipped entirely max_ast_nodes: 500000 worker_max_jobs: 100 worker_max_rss_mb: 1024 ``` `discovery.engine.disable_kingfisher` is gone — secret scanning is in-process Go now (no external `kingfisher` binary). The replacement key is `disable_secret_scan`. ### `spidering` Browser-based crawling. ```yaml theme={null} spidering: max_depth: 6 # 0 = unlimited max_states: 1500 # 0 = unlimited max_duration: 30m max_consecutive_fails: 100 headless: true browser_count: 1 strategy: adaptive # normal | random | oldest_first | shallow_first | adaptive include_response_body: true browser_engine: chromium # chromium | ungoogled | fingerprint no_cdp: false # disable CDP event listener detection no_forms: false # disable automatic form filling self_register: false # complete a public signup form and crawl as that account graph_output_dir: "" # write the crawl graph here (one file per host); empty disables # AI pilot mode (agent-controlled browser): pilot_mode: false pilot_auto_register: true pilot_username: "" pilot_password: "" pilot_screenshot: true pilot_max_retries: 2 pilot_stall_timeout: 7m ``` `max_depth` and `max_states` default to **6** and **1500** — they were unlimited before v0.3.8. Both bound how the crawl spends its `max_duration`: without a depth bound a link-dense site can sink the whole budget into one deep branch and never revisit the breadth near the seed, and without a state bound a template that mints a state per row (a paginated table, a calendar) spends it on near-identical pages. Set either to `0` to restore unlimited. `self_register` lets the crawl complete a public signup form and continue as the account it creates — on an app with open registration, that is the difference between crawling the marketing shell and crawling the product. It runs at most once per host, only submits to an in-scope host, and reuses the identity it creates at any later login form. It stays **off** by default because registering is a write; `--intensity deep` turns it on, and setting this key to `true` forces it on at any intensity. `graph_output_dir` writes the finished crawl graph into a directory (one file per host). Captured traffic records *what* was requested; the graph records *how* the crawler got there — which action on which state, with which form values, and the selector to find that element again — which is what makes a run reproducible and lets a later pass re-reach a specific state. It is a directory rather than a path because one browser session reuses a single config across all of a host's seeds. ### `dynamic-assessment` Controls which scanner modules run and JavaScript extension settings. (Formerly `audit`.) ```yaml theme={null} dynamic-assessment: max_feedback_rounds: 1 # rescan rounds for newly-discovered URLs max_findings_per_module: 15 # 0 = unlimited enabled_modules: active_modules: ["all"] # ["all"] or list of module IDs passive_modules: ["all"] extensions: enabled: false extension_dir: ~/.vigolium/extensions/ custom_dir: [] # additional script paths variables: {} # key-value pairs passed to scripts limits: timeout: 30s max_memory_mb: 128 ``` ### `scope` Defines what is in scope for scanning. Exclude rules take priority over include rules. ```yaml theme={null} scope: applied_on_ingest: false # enforce scope during ingestion (not just scanning) cli_origin_mode: relaxed # relaxed | all | balanced | strict ignore_static_file: true # skip images, fonts, video, audio, etc. max_request_body_size: 1048576 # 1 MB max_response_body_size: 524288000 # 500 MB body_size_exceeded_action: truncate # truncate | drop | skip-scan host: include: ["*"] exclude: [] path: include: ["*"] exclude: [] status_code: include: ["*"] exclude: [] request_content_type: include: ["*"] exclude: [] response_content_type: include: ["*"] exclude: [] request_string: include: [] exclude: [] response_string: include: [] exclude: [] ``` ### `server` REST API server settings. ```yaml theme={null} server: auth_api_key: "" # auto-generated if empty; also set via VIGOLIUM_API_KEY service_port: 9002 ingest_proxy_port: 0 # 0 = disabled mirror_fs_path: "" # mirror ingested traffic + findings to this dir as a live filesystem tree (see --mirror-fs) cors_allowed_origins: reflect-origin enable_metrics: true agent_heavy_max: 5 # max concurrent autopilot/swarm runs via API agent_light_max: 10 # max concurrent query/chat runs via API agent_queue_timeout: 30s # wait for an agent slot before returning 429 disable_swagger: false # drop the Swagger UI + spec endpoint (CLI: --no-swagger) users_file: ~/.vigolium/users.json # file-based logins for POST /api/auth/login license: open-source # license tag surfaced in /server-info enable_burp_bridge: false # opt in to merging live proxy history into /api/http-records burp_bridge_url: "" # loopback bridge address used when enable_burp_bridge is true ``` The narrowing flags — `--view-only`, `--no-agent`, `--demo-only`, `--alternative-ingest-key`, `--disable-warm-session` — are CLI-only; see [Narrowing the Exposed Surface](/server-mode/running-the-server#narrowing-the-exposed-surface). `burp_bridge_url` has a sensible default address, so the boolean is what decides whether the bridge is contacted — otherwise every config would silently enable it. Precedence is `-B/--burp-bridge-url` → `$VIGOLIUM_BURP_BRIDGE_URL` → config (only when `enable_burp_bridge: true`); passing the flag works regardless of the boolean. `--caido-bridge-url` is an alias of the same flag (the [Caido plugin](/getting-started/caido-plugin) exposes the same loopback bridge) — both spellings set the same value and read the same env var and config key. ### `agent` AI agent integration. Every agent invocation is dispatched through the in-process **olium** runtime, there are no subprocess SDK or ACP backends. ```yaml theme={null} agent: default_agent: olium templates_dir: ~/.vigolium/prompts/ sessions_dir: ~/.vigolium/agent-sessions/ stream: true # real-time output streaming # Olium engine — used by every agent subcommand olium: provider: openai-compatible # openai-codex-oauth | anthropic-api-key | anthropic-oauth | openai-api-key | openai-responses | anthropic-cli | anthropic-claude-sdk-bridge | anthropic-compatible | anthropic-vertex | google-vertex | openai-compatible (default: openai-compatible) model: gemma4:latest # empty = provider default; matches custom_provider.model_id default (openai-compatible / anthropic-compatible) oauth_cred_path: ~/.codex/auth.json # openai-codex-oauth provider bridge_binary: "" # anthropic-claude-sdk-bridge: path to the vigolium-audit binary hosting the SDK bridge (empty = embedded blob, then PATH) oauth_token: "" # anthropic-oauth bearer token (from `claude setup-token`); supports ${ENV_VAR} llm_api_key: "" # for anthropic-api-key / openai-api-key / openai-responses; supports ${ENV_VAR}; falls back to provider env reasoning_effort: medium # minimal | low | medium | high | xhigh (codex) system_prompt: "" # override built-in olium system prompt max_tokens: 1000000 temperature: 0.0 max_turns: 32 # tool-loop iteration cap cache_size: 1024 # LRU entries; 0 disables max_concurrent: 4 # 0/unset = default 4; negative = unbounded call_timeout_sec: 600 # per-call deadline; -1 = no enforced timeout autopilot_mode: legacy # legacy | shadow | enforced # Skills always loaded for autopilot/swarm regardless of planner selection. # Empty = built-in default [triage-finding, write-jsext]. The CLI flags # --skill / --skill-tag / --no-skill-filter override selection per-run. always_on_skills: [] # Custom backend: used when provider == openai-compatible (OpenAI Chat # Completions — Ollama, OpenRouter, LM Studio, vLLM, Together, Groq, …) or # provider == anthropic-compatible (Anthropic Messages /v1/messages gateway). custom_provider: base_url: http://localhost:11434/v1 # e.g. http://localhost:11434/v1 (Ollama) model_id: gemma4:latest # backend-specific model id api_key: "" # optional; leave empty to skip Authorization header; supports ${ENV_VAR} extra_headers: [] # list of curl-style "Key: Value" strings, applied after standard headers # OpenRouter provider routing — typed knob for the request "provider" object. # Only fields you set are sent; unset fields are dropped from the wire body. provider_routing: order: [] # upstream provider slugs in preference order only: [] # restrict to these provider slugs ignore: [] # exclude these provider slugs allow_fallbacks: true # false = strict (only the chosen providers) sort: "" # price | throughput | latency quantizations: [] # e.g. [fp8, int8] data_collection: "" # allow | deny require_parameters: false # only providers that honour every request parameter zdr: false # Zero Data Retention providers only # Generic JSON-body passthrough merged into every openai-compatible request # (OpenRouter extensions, vLLM/Together body options, etc.). Reserved keys # (model, messages, tools, stream, stream_options) are rejected. Don't set a # `provider` key here if you also use provider_routing — use one or the other. extra_body: {} # Vertex AI (anthropic-vertex / google-vertex) — GCP project / region. # Credentials come from `oauth_cred_path` (service-account JSON) or # $GOOGLE_APPLICATION_CREDENTIALS. $GOOGLE_CLOUD_PROJECT and # $GOOGLE_CLOUD_LOCATION override these YAML values. google_cloud_project: "" # GCP project id google_cloud_location: "" # GCP region; default us-central1 # LLM config for JavaScript extension agent API (vigolium.agent.* in extensions) llm: provider: anthropic # anthropic | openai model: claude-sonnet-4-20250514 api_key: "" # inline key (prefer api_key_env) api_key_env: "" # env var name (default: ANTHROPIC_API_KEY or OPENAI_API_KEY) base_url: "" # custom endpoint for OpenAI-compatible providers max_tokens: 4096 temperature: 0.0 cache_size: 256 # LRU entries (0 = disabled) cache_ttl: 300 # seconds # DB context enrichment limits for swarm/autopilot context_limits: max_findings: 50 max_endpoints: 100 max_high_risk: 20 min_risk_score: 50 # Autopilot guardrails (SDK-era; mostly informational under olium) guardrails: log_commands: false max_turns: 0 # 0 = auto (MaxCommands * 3) disallowed_tools: [] # Optional agent-browser integration (Bash tool can drive a real Chromium for auth-walled flows) browser: enable: true binary_path: agent-browser # default: looked up on $PATH # Vigolium-audit integration (embedded harness) audit: enable: false # auto-enabled by --audit flag on autopilot/swarm mode: lite # lite | balanced | deep | mock sync_interval: 30 # seconds between state syncs # The audit driver runs the `claude` or `codex` CLI. There is no # platform knob here — the agent is resolved from agent.olium.provider # (anthropic-* → claude, openai-* → codex), overridable per-run with # --provider or --agent {claude|codex} on `vigolium agent audit`. ``` `autopilot_mode` is opt-in: `legacy` keeps one growing conversation and writes findings directly, `shadow` adds bounded context rotation while mirroring candidates, and `enforced` promotes only fresh-context verifier-confirmed candidates. Only `shadow` and `enforced` support `agent autopilot --resume `. Provider quick reference: | Provider | Default model | Credential source | | ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `openai-codex-oauth` | `gpt-5.5` | `oauth_cred_path` (`~/.codex/auth.json`) | | `anthropic-api-key` | `claude-opus-4-7` | `llm_api_key` or `$ANTHROPIC_API_KEY` | | `anthropic-oauth` | `claude-opus-4-7` | `oauth_token` (from `claude setup-token`); falls back to `$ANTHROPIC_API_KEY` | | `openai-api-key` | `gpt-5.5` | `llm_api_key` or `$OPENAI_API_KEY` | | `openai-responses` | `gpt-5.5` | `llm_api_key` or `$OPENAI_API_KEY` (public OpenAI Responses API, `/v1/responses`) | | `anthropic-cli` *(alias `anthropic-claude-cli`)* | `claude-opus-4-7` | local `claude` binary on `$PATH` | | `anthropic-claude-sdk-bridge` | Claude Code default | logged-in Claude Code subscription via the `vigolium-audit bridge` sidecar (no key); `bridge_binary` / `--bridge-bin` override | | `anthropic-compatible` | via `custom_provider.model_id` | `agent.olium.custom_provider` (Anthropic Messages `/v1/messages` gateway or proxy) | | `anthropic-vertex` | `claude-opus-4-6` | GCP service-account JSON (or ADC) via `google_cloud_project` / `google_cloud_location` | | `google-vertex` | `gemini-2.5-pro` | GCP service-account JSON (or ADC) via `google_cloud_project` / `google_cloud_location` | | `openai-compatible` *(default)* | `gemma4:latest` (via `custom_provider.model_id`) | `agent.olium.custom_provider` (Ollama, OpenRouter, LM Studio, vLLM, …) | CLI flags `--provider`, `--model`, `--oauth-cred`, `--oauth-token`, `--llm-api-key`, `--base-url`, `--bridge-bin` override these per-invocation. The REST API does **not** mirror these overrides, server-side workloads use the YAML config exclusively. See [Setting Up the Agent](/getting-started/setup-agent) for a step-by-step walkthrough or [Olium Agent](/agentic-scan/olium) for full provider details. ### `database` Storage backend. SQLite is the default; PostgreSQL is supported for multi-user deployments. ```yaml theme={null} database: enabled: true driver: sqlite # sqlite | postgres sqlite: path: ~/.vigolium/database-vgnm.sqlite busy_timeout: 15000 journal_mode: WAL # DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF synchronous: NORMAL # OFF | NORMAL | FULL | EXTRA cache_size: 10000 max_open_conns: 8 # WAL reader pool: concurrent readers + 1 writer postgres: host: localhost port: 5432 user: vigolium password: "" database: vigolium sslmode: disable max_open_conns: 25 max_idle_conns: 5 conn_max_lifetime: 5m ``` ### `known_issue_scan` Known-issue scanning powered by the Nuclei template engine. ```yaml theme={null} known_issue_scan: tags: [] # nuclei template tags (empty = all) exclude_tags: [dos] severities: [] # filter: critical, high, medium, low, info templates_dir: "" # custom templates path enrich_targets: true # feed discovered paths into known-issue scan # Remap the severity a finding is recorded with, keyed by nuclei template ID. # Default tones down generic config.json exposure (often only public base # URLs / feature flags). Add your own template-ID → severity entries. severity_overrides: config-json-exposure-fuzz: medium # critical | high | medium | low | info # Collapse many findings of the same module into one finding per distinct # *value* (e.g. one row per leaked secret, listing every URL it appears on) # instead of one row per URL. group_by_value: enabled: true per_host: true # group within a host rather than project-wide max_urls: 50 # URLs listed on the surviving finding by_module: [...] # modules grouped by their finding identity (long built-in list) by_rule: [secret-detect, host-header-injection, ldap-injection, ...] # grouped by the matched rule bundle_suspect: [secret-detect] # also bundle the module's Suspect-tier results tags: [] # extra module tags to include ``` Run `vigolium config ls known_issue_scan.group_by_value` to print the full built-in module lists for your build. ### `mutation_strategy` Controls how parameter values are mutated during active scanning. ```yaml theme={null} mutation_strategy: default_modes: [append] value_aware: enabled: true max_per_intent: 5 default_intents: [neighbor, boundary, escalation] enum_mappings: {} # custom enum escalation pairs param_synonyms: {} # custom param name synonyms field_type_defaults: email: ["test@example.com", "user@test.org"] uuid: ["550e8400-e29b-41d4-a716-446655440000"] integer: ["1", "100", "999"] # ... (all standard types have built-in defaults) ``` ### `external_harvester` Pre-scan intelligence gathering from public data sources. ```yaml theme={null} external_harvester: sources: [wayback, commoncrawl, alienvault, arquivo] # keyless — the default set # Additional sources: urlscan, virustotal (join the set only once their key is set) api_keys: urlscan: "" virustotal: "" ``` `wayback` and `commoncrawl` also mine archived response **bodies** (and Wayback's `robots.txt` history) for URLs no index holds. Mined URLs are scope-checked and junk-extension filtered before they enter the scan, while index rows are passed through verbatim. Budgets are fixed constants rather than flags, since the phase runs unattended. ### `oast` Out-of-Band Application Security Testing via interactsh callbacks. ```yaml theme={null} oast: enabled: true server_url: oast.pro token: "" # optional auth token poll_interval: 5 # seconds grace_period: 10 # seconds after scan for late callbacks oast_url: "" # fixed callback URL (empty = auto-generate) blind_xss_src: "" # JS script src for blind XSS payloads enabled_blind_xss: false ``` ### `source_aware` Storage location for cloned source repositories. Used when `--source` receives a git URL (autopilot, swarm, audit, query). Static analysis tooling (ast-grep, semgrep, etc.) has been removed, for AI-driven code audit, use `vigolium agent audit` or `vigolium agent swarm --source --code-audit`. ```yaml theme={null} source_aware: storage_path: ~/.vigolium/source-aware/ # base directory for cloned repos clone_depth: 1 # `git clone --depth` (1 = shallow) ``` ### `storage` Cloud storage integration for source code upload/download and scan result archival. Uses an S3-compatible API, supports GCS (via HMAC), AWS S3, and MinIO. ```yaml theme={null} storage: enabled: false driver: gcs # gcs | s3 | minio endpoint: "" # auto for gcs/s3; required for minio bucket: ${VIGOLIUM_STORAGE_BUCKET_NAME} region: asia-southeast1 access_key: ${VIGOLIUM_STORAGE_ACCESS_KEY} secret_key: ${VIGOLIUM_STORAGE_SECRET_KEY} use_ssl: true path_style: false # required for some MinIO deployments ``` When enabled, agent runs invoked with `--upload-results` archive their session bundle to `//agentic-scans//results.tar.gz`. Native scans use `//native-scans//results.tar.gz`. See [Storage API](/api-references/storage) for upload/download endpoints. ### `notify` Real-time finding notifications via Telegram or Discord. ```yaml theme={null} notify: enabled: false severities: [high, critical, medium] telegram: bot_token: "" chat_id: "" discord: webhook_url: "" ``` ## Scanning Profiles Scanning profiles are YAML files stored in `~/.vigolium/profiles/` that override subsets of the main config. They can tune any combination of: `scanning_strategy`, `scanning_pace`, `discovery`, `spidering`, `known_issue_scan`, `audit`, `external_harvester`, `mutation_strategy`, and `scope`. Apply a profile with: ```bash theme={null} vigolium scan --scanning-profile aggressive ``` This loads `~/.vigolium/profiles/aggressive.yaml` and overlays it onto the active config. Only non-zero fields in the profile override the base config; unspecified fields are left unchanged. Built-in profiles are bundled in `public/presets/profiles/`. See [native-scan/scanning-modes-overview](/native-scan/scanning-modes-overview) for details. ## Project-Level Config Each project can have its own config overlay at `~/.vigolium/projects//config.yaml`. This uses the same format as scanning profiles and is automatically applied when the project is active. Manage project configs with: ```bash theme={null} vigolium project config set scanning_pace.concurrency 200 vigolium project config show ``` See [projects](/others/projects) for full project management documentation. # Native Scan & Stateless Scanning Source: https://docs.vigolium.com/getting-started/native-scan The native scan is Vigolium's deterministic, Go-based scanning pipeline, fast, modular, and AI-free. This page is a hands-on tour of running native scans from the CLI, with a focus on stateless scanning. Use stateless scanning for CI/CD pipelines, scripting, AI-agent integration, and quick ad-hoc checks. For the conceptual deep-dive see [Scanning Modes Overview](/native-scan/scanning-modes-overview); for the full strategy reference see [Strategies](/native-scan/strategies). ## Stateless at a glance | Command | Persists to DB? | Phases | Use it for | | ------------------ | ----------------------------- | ----------------------- | ------------------------- | | `scan-url` | No | none, direct module run | One URL, fast | | `scan-request` | No | none, direct module run | A raw HTTP request / curl | | `scan --stateless` | No (temp DB, discarded) | full pipeline | One-shot full scan | | `scan` | Yes (`~/.vigolium/...sqlite`) | full pipeline | Persistent projects | `scan-url` and `scan-request` never touch a database. `scan --stateless` creates a temporary SQLite database, runs every requested phase, exports results, and deletes the database on exit. Pass `-o/--output` (with `--format`) when using `--stateless`, otherwise results are discarded along with the temporary database. Vigolium prints a warning if you forget. `--stateless` and `--db` are mutually exclusive. ## Scan a single URL, `scan-url` ```bash theme={null} # Simplest possible scan vigolium scan-url https://example.com/api/users?id=1 # JSON output for scripting vigolium scan-url -j https://example.com/api/users?id=1 ``` POST with a body and headers: ```bash theme={null} vigolium scan-url \ --method POST \ --body '{"user":"admin","pass":"secret"}' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer tok123' \ https://example.com/api/login ``` Scope the modules and skip work you don't need: ```bash theme={null} # Only injection-class modules (fuzzy match on ID/name) vigolium scan-url -m sqli -m xss "https://example.com/search?q=test" # Filter by tag vigolium scan-url --module-tag injection https://example.com/api/data # Skip passive analysis for the fastest result vigolium scan-url --no-passive https://example.com/api/data ``` Run a discovery/spider phase *before* the scan (these promote `scan-url` to the full pipeline and require a database, pass `--db`): ```bash theme={null} vigolium scan-url --discover --db /tmp/scan.sqlite https://example.com ``` ## Scan a raw HTTP request, `scan-request` ```bash theme={null} # From a file containing a raw HTTP request vigolium scan-request -i request.txt # From stdin printf 'GET /api/users?id=1 HTTP/1.1\r\nHost: example.com\r\n\r\n' \ | vigolium scan-request # From a curl command (auto-detected) echo "curl -X POST -d 'user=admin' https://example.com/login" \ | vigolium scan-request ``` Override the host when the request file has only a path: ```bash theme={null} vigolium scan-request -i request.txt --target https://staging.example.com ``` ## Piping from stdin Both `scan-url` and `scan-request` auto-detect the stdin format, plain URL, curl command, or raw HTTP request: ```bash theme={null} # Plain URL echo 'https://example.com/search?q=test' | vigolium scan-url # Curl command echo "curl -H 'Content-Type: application/json' -d '{\"id\":1}' https://example.com/api" \ | vigolium scan-url # Raw HTTP request printf 'POST /api/login HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nuser=admin&pass=secret' \ | vigolium scan-request # Scan whatever is on your clipboard (macOS) pbpaste | vigolium scan-url -j ``` ## Full stateless pipeline, `scan --stateless` Run discovery, spidering, and dynamic-assessment with no persistent state. `--stateless` works on both `scan` and `run`: ```bash theme={null} # Full pipeline, JSONL out, nothing left behind vigolium scan --stateless -t https://example.com --format jsonl -o results # Add content discovery, write both JSONL and HTML vigolium scan --stateless -t https://example.com \ --discover --format jsonl,html -o scan-output # A single phase, statelessly vigolium run dynamic-assessment --stateless -t https://example.com \ --format jsonl -o results ``` Multiple targets from a file scan into one shared temporary database and export to a single unified output file by default: ```bash theme={null} vigolium scan --stateless -T targets.txt --format jsonl -o results # -> results.jsonl (all targets combined) ``` Add `--split-by-host` to instead scan each target in its own isolated temporary database and write a separate per-host output file (the filename is suffixed with the host so results don't overwrite): ```bash theme={null} vigolium scan --stateless -T targets.txt --split-by-host --format jsonl -o results # -> results-example.com.jsonl, results-test.example.com.jsonl, ... ``` ## ⚡Scanning a huge list of targets in parallel When you point Vigolium at a large target list, use `-P/--parallel N` to scan several hosts at once. Each target runs in its own isolated child process — so there is no cross-contamination between workers — and each child keeps its own `--concurrency`, meaning real in-flight requests are roughly `N × --concurrency`. `-P` requires one of two output strategies so results never collide: * **`--stateless --split-by-host`** — each target runs against its own temporary database and writes a separate per-host output file (`base-.`). Nothing is persisted. Best for stateless, fire-and-forget batches. * **`--db-isolate`** — each worker scans into a private temporary SQLite database, then merges its results into the shared `--db` (or the default DB) at the end. This lets many parallel scans share one database without write contention, and you export one unified report from the merged DB afterward. ```bash theme={null} # Stateless fan-out: per-host JSONL + HTML files, 3 targets at a time vigolium scan -T list-of-targets.txt -P 3 \ --stateless --split-by-host \ --format jsonl,html --output prefix-output \ --discovery-wordlist ~/Tools/contents/fast.txt # Shared-DB fan-out: 4 targets at a time merged into one local.db, one unified output vigolium scan -T list-of-targets.txt -P 4 \ --db-isolate --db local.db \ --format jsonl,html --output report \ --discovery-wordlist ~/Tools/contents/fast.txt ``` `--db-isolate` is SQLite-only and cannot be combined with `--stateless` (they are two different ways to avoid write contention). Pressing `Ctrl-C` during a `-P` batch is treated as an operator stop: un-started and cut-short targets are reported as "not scanned" rather than failures. ### Resuming an interrupted fan-out A stateless parallel fan-out (`-S -T --split-by-host -P`) writes a tiny line-cursor manifest, `.progress.json`, tracking the targets that completed cleanly. If the batch is interrupted (Ctrl-C, a crash, a CI timeout), re-run it with `--resume` to skip the finished targets and scan only the remainder — Vigolium also prints a copy-pasteable resume command on Ctrl-C/failure: ```bash theme={null} # Original run vigolium scan -T targets.txt -P 4 --stateless --split-by-host --format jsonl -o results # Resume only the targets that didn't finish vigolium scan -T targets.txt -P 4 --stateless --split-by-host --format jsonl -o results --resume ``` Run `vigolium scan --resume` bare — with no other flags — and it auto-discovers the `*.progress.json` in the current directory and relaunches the saved run from it (pass `-o ` to disambiguate when several manifests exist). `--resume` currently applies only to the parallel fan-out (`-S -T --split-by-host -P > 1`). Resuming a plain sequential scan re-runs it in full. ## Stateless scans from other input sources ```bash theme={null} # OpenAPI / Swagger spec vigolium scan --stateless -i api.yaml -I openapi \ -t https://api.example.com --format jsonl -o results # WSDL / SOAP service description vigolium scan --stateless -i service.wsdl -I wsdl \ -t https://soap.example.com --format jsonl -o results # Postman collection vigolium scan --stateless -i collection.json -I postman \ -t https://api.example.com --format jsonl -o results # Burp Suite XML export vigolium scan --stateless -i export.xml -I burpxml --format jsonl -o results # HAR capture vigolium scan --stateless -i traffic.har -I har --format jsonl -o results # Nuclei JSONL vigolium scan --stateless -i nuclei.jsonl -I nuclei --format jsonl -o results ``` ## Tuning a scan ```bash theme={null} # Speed knobs — defaults: -c 50, -r 100 req/s, --max-per-host 30, --timeout 15s vigolium scan --stateless -t https://example.com \ -c 100 -r 200 --max-per-host 10 --timeout 30s \ --format jsonl -o results # Strategy presets trade depth for speed vigolium scan --stateless -t https://example.com --strategy lite -o r --format jsonl vigolium scan --stateless -t https://example.com --strategy deep -o r --format jsonl # Route everything through a proxy vigolium scan --stateless -t https://example.com --proxy http://127.0.0.1:8080 \ --format jsonl -o results # Constrain how broadly scope is interpreted vigolium scan --stateless -t https://example.com --scope-origin strict \ --format jsonl -o results # Include the full HTTP response body in findings (scan / run only) vigolium scan --stateless -t https://example.com --include-response \ --format jsonl -o results ``` ## Authenticated stateless scans Pass an inline session or a session file, both work in stateless mode: ```bash theme={null} # Inline session: name:Header:value vigolium scan --stateless -t https://example.com \ --auth "admin:Cookie:session_id=abc123" \ --format jsonl -o results # Single session or multi-session bundle file (YAML or JSON) vigolium scan --stateless -t https://example.com \ --auth-file ./admin-session.yaml \ --format jsonl -o results # A static header is often enough for token auth vigolium scan-url -H 'Authorization: Bearer token123' \ https://example.com/api/me ``` See [Authenticated Scanning](/native-scan/authentication) for login flows, token extraction, and multi-session IDOR/BOLA testing. ## CI/CD integration `--ci-output-format` forces clean JSONL with no banners or color codes, ideal for parsing in a pipeline: ```bash theme={null} vigolium scan --stateless -t https://example.com \ --ci-output-format -o findings ``` A minimal gate that fails the build when any finding is reported: ```bash theme={null} vigolium scan --stateless -t "$TARGET" --ci-output-format -o findings test ! -s findings.jsonl || { echo "Vulnerabilities found"; exit 1; } ``` See [CI/CD Integration](/guides/ci-cd-integration) for full pipeline examples. ## Output formats recap | `--format` | Output | Notes | | -------------------- | ------------------------------- | --------------------------------------------------------------------------------- | | `console` | Terminal (default) | Colored, human-readable | | `jsonl` | `.jsonl` | One JSON object per line; `-j` is shorthand | | `html` | `.html` | Interactive ag-grid report; requires `-o` | | `report` / `pdf` | `.html` / `.pdf` | Document-style report; `pdf` renders through headless Chrome | | `sarif` | `.sarif` | SARIF 2.1.0 for GitHub code scanning / DefectDojo / SARIF viewers; requires `-o` | | `sqlite` | `.sqlite` | Standalone DB dump (`VACUUM INTO`); requires `-S` + `-o`. Aliases `sqlite3`, `db` | | `fs` | `-traffic/`, `-findings/` | Flat browsable tree; alias `file-system` | | `console,jsonl,html` | All of the above | Comma-separate to combine | For stateless runs, `-o` is the **base** path, Vigolium appends the correct extension per format and materializes every requested format from the temporary database before tearing it down. ## Next steps * [Stateless Scanning Guide](/guides/stateless-scan), the extended recipe book. * [Scanning Strategies](/native-scan/strategies), strategies, profiles, pace. * [Native Scan: How It Works](/native-scan/how-it-works), the pipeline internals. * [Scanner Modules Reference](/native-scan/modules-reference), every module. * [Output & Reporting](/getting-started/output-and-reporting), formats and reports in depth. **Oh dear, you actually read to the end.** Here's the secret the config file kept nudging you toward: by default every Vigolium request announces itself with a `Vigolium/` User-Agent, so a friendly blue team can spot your authorized scan in their logs in seconds. Go full ninja only when you *mean* to be sneaky: ```bash theme={null} # Persist it in your config vigolium config set scanning_strategy.http.user_agent random # Or a one-off via env var (overrides the config value for that run) export VIGOLIUM_DEFAULT_UA=random ``` # Output and Reporting Source: https://docs.vigolium.com/getting-started/output-and-reporting Vigolium supports multiple output formats for scan results, discovery data, and spidering output. This guide covers the available formats, result structures, and how to query stored findings. Workbench project overview showing scan summary and severity counts Workbench findings list with severity filters and search ## Output Formats The `--format` flag controls the output format, and formats can be combined (e.g. `--format jsonl,html`). Native scans accept: | Format | Output | | --------- | ------------------------------------------------------------------------------- | | `console` | Human-readable terminal output (default) | | `jsonl` | One JSON object per line | | `html` | Self-contained interactive grid report | | `report` | Self-contained document-style report | | `pdf` | PDF document rendered through headless Chrome | | `sarif` | SARIF 2.1.0 log for GitHub code scanning, DefectDojo, SARIF viewers | | `sqlite` | Standalone database that can be reopened by Vigolium (aliases: `sqlite3`, `db`) | | `fs` | Flat, browsable request/response and finding tree (alias: `file-system`) | Matching is case-insensitive, and a format that writes a file requires `-o/--output`. ### Console (default) ```bash theme={null} vigolium scan --target https://example.com ``` Human-readable terminal output with color-coded severity levels. Findings are printed as they are discovered, with a summary table at the end of the scan. This is the default when no `--format` flag is specified. Severity colors: * **Critical**: Red * **High**: Orange/Yellow * **Medium**: Yellow * **Low**: Blue * **Info**: Gray ### JSONL ```bash theme={null} vigolium scan --target https://example.com --format jsonl ``` One JSON object per line, machine-readable. Each line is a self-contained JSON document representing a single finding or event. This format is well suited for piping to `jq`, ingesting into SIEMs, or processing with custom scripts. Example usage with `jq`: ```bash theme={null} vigolium scan --target https://example.com --format jsonl | jq 'select(.severity == "high")' ``` For CI/CD pipelines, `--ci-output-format` is a shorthand that forces clean JSONL with no banners or color codes, drop-in safe for parsing in build scripts: ```bash theme={null} vigolium scan --stateless -t https://example.com --ci-output-format -o findings ``` See [CI/CD Integration](/guides/ci-cd-integration) for full pipeline examples. ### HTML ```bash theme={null} vigolium scan --target https://example.com --format html -o report.html ``` Interactive HTML report using an embedded ag-grid table. The report is a self-contained HTML file with sorting, filtering, and search capabilities. The `-o/--output` flag is required when using HTML format. HTML format is supported for: * Scan results (findings) * Discovery phase output (discovered URLs and endpoints) * Spidering phase output (crawled pages) ### SARIF ```bash theme={null} vigolium scan --target https://example.com --format sarif -o scan.sarif ``` A [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html) log of the run's findings — the interchange format GitHub code scanning, DefectDojo, the VS Code SARIF Viewer, and most SAST/DAST aggregators ingest without a custom parser. Available on `scan`/`scan-url`/`scan-request`/`run`, on [`vigolium export`](#exporting-stored-results-in-several-formats), and on `vigolium import --format`. How findings map onto the schema: | Vigolium | SARIF | | ----------------------- | ----------------------------------------------------------------------------------------------------------- | | Module id / name | `tool.driver.rules[].id` / `.name` | | Severity | `level` — critical + high → `error`, medium → `warning`, low + info → `note` | | Severity / CVSS | `properties["security-severity"]`, on both the rule and the result — what GitHub buckets alerts by | | CWE | Rule tag `external/cwe/cwe-079` (zero-padded, links to MITRE) | | Source file `path:line` | `locations[].physicalLocation` with a `region` | | URL | `locations[].physicalLocation` when there is no source file, otherwise a `relatedLocation` | | Request / response | `webRequest` / `webResponse` (the spec's own DAST fields) | | Finding hash | `partialFingerprints` — lets a consumer track one finding across re-scans instead of reopening it every run | * **Code findings** (from [`vigolium agent audit`](/getting-started/agentic-security-audit) and the source-aware agent modes) anchor to a **file and line**, which is what lets GitHub annotate them onto a pull-request diff. An absolute audit path is made repo-relative only when the repository name identifies a directory in it; otherwise the absolute path is kept rather than guessed at, so a result is never pinned to the wrong file. * **Native-module findings** anchor to their **URL** and carry the proof exchange in `webRequest`/`webResponse`. Bodies are capped (4 KB each) so a large scan stays inside upload limits; a result whose evidence was cut carries `properties.evidenceTruncated`. * A clean scan still emits `results: []` and `rules: []` — an absent array reads as a malformed run rather than as zero findings. Uploading to GitHub code scanning: ```bash theme={null} vigolium scan -S "$TARGET" --format sarif -o results.sarif gh api /repos/{owner}/{repo}/code-scanning/sarifs \ -f commit_sha="$(git rev-parse HEAD)" -f ref="refs/heads/main" \ -f sarif="$(gzip -c results.sarif | base64)" ``` See [CI/CD Integration](/guides/ci-cd-integration#github-code-scanning-sarif) for the full workflow. ### SQLite ```bash theme={null} vigolium scan --stateless --target https://example.com --format sqlite -o scan ``` Dumps the run's standalone database to `.sqlite` (via SQLite `VACUUM INTO`), producing a single self-contained file you can reopen later with `vigolium finding`/`traffic` (see [Reading a Standalone Export](#reading-a-standalone-export)). Aliases: `sqlite3`, `db`. * Requires `-S/--stateless` and `-o/--output`. Stateless runs use a per-run temporary database, so "this scan's SQLite" is well-defined; a persisted run writes into the shared project DB, where it would be ambiguous (use `vigolium export` for that). * Combines with other formats: `--format sqlite,html -o scan` writes both `scan.sqlite` and `scan.html`. * Under `--split-by-host`, each per-host file is named `-.sqlite`. ### Filesystem (fs) ```bash theme={null} vigolium scan --target https://example.com --format fs -o run ``` Writes a flat, browsable filesystem tree instead of a single file — so you (or a coding agent) can investigate a scan with plain `ls`/`grep`/`jq`, no database required. Two sibling directories are written off the `-o` base (defaulting to `vigolium` in the current directory when `-o` is omitted), so `-o run` yields `run-traffic/` and `run-findings/`: ``` run-traffic/ index.json # [{id,host,path,method,url,status,content_type,bytes,finding}, …] /0001.req # "@target https://" line + the raw request (replayable) /0001.resp.headers # status line + response headers /0001.resp.body # response body, gzip-decoded so it greps clean run-findings/ index.json # [{id,host,path,severity,confidence,module,title,url,traffic}, …] /0001.md # the finding, cross-linked to ../../run-traffic//0001.req ``` * Per-host ids are zero-padded and assigned in `sent_at` order, so re-exports are reproducible. * Each `.req` starts with an `@target ://` line followed by the raw request verbatim — strip line 1 and it replays directly. * `index.json` is the entry point: one `jq` over it maps every id to its url/status and to the file holding the bytes. On a traffic row, the `finding` field carries the top severity of any finding touching that request; each finding `.md` links straight to the `.req`/`.resp.*` that proves it. * Available on `vigolium export`, `vigolium db export` (honoring its filters), and `scan`/`scan-url`/`scan-request`/`run` — with or without `-S/--stateless`. * Honors `--omit-response` (drops the `.resp.*` files). `--split-by-host` is a no-op, since `fs` already splits by host. The ingestion server can produce this same tree **live** with `vigolium server --mirror-fs ` — see [Running the Server](/server-mode/running-the-server#live-filesystem-mirror). ## Exporting Stored Results in Several Formats `vigolium export --format` also takes a comma-separated list — `html`, `report`, `pdf`, `jsonl`, `markdown` (alias `md`), `sarif`, `bundle` (alias `gz`), `fs` (alias `file-system`). The database is read **once** and every format renders from that one result set, so three formats cost one query rather than three: ```bash theme={null} vigolium export --format html,markdown,bundle -o acme-q3 # → acme-q3.html # acme-q3.md # acme-q3.tar.gz ``` With more than one format, `-o/--output` is a shared **base path** and each format appends its own extension (an extension already on the base is replaced, not stacked). Because one `-o` cannot name several files, it is required as soon as you pass a second format. A single format still uses `-o` verbatim: `--format html -o report` writes exactly `report`. `{ts}` and `{project-uuid}` are expanded once for the whole run, so every file carries the same timestamp even when a slow renderer (`pdf`, which shells out to headless Chrome) finishes seconds after its siblings. The same holds for a `gs://` base, where each format is uploaded under its own key: ```bash theme={null} vigolium export --format html,markdown,bundle -o gs://{project-uuid}/exports/scan-{ts} ``` If one format fails, the others are still written and the command exits non-zero naming the format that failed. `vigolium db export -f` remains single-valued. On the scan commands, `--format` selects live output writers (the formats above under [Output Formats](#output-formats)) rather than export renderers. ## Severity Scale Findings are classified using five severity levels: | Severity | Description | | ------------ | ------------------------------------------------------------------------------------------------ | | **Critical** | Exploitable vulnerabilities with severe impact (e.g., RCE, SQL injection with data exfiltration) | | **High** | Significant vulnerabilities that can lead to data compromise or unauthorized access | | **Medium** | Vulnerabilities that require specific conditions to exploit or have limited impact | | **Low** | Minor issues with minimal security impact | | **Info** | Informational findings, such as technology fingerprints or configuration details | ## Confidence Scale Each finding includes a confidence level indicating the reliability of the detection: | Confidence | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | **Certain** | Confirmed with proof. The scanner has verified the vulnerability through direct evidence (e.g., a reflected payload executed, data was extracted). | | **Firm** | Strong evidence supports the finding. Multiple indicators confirm the issue, but direct proof of exploitation was not obtained. | | **Tentative** | Based on heuristic or pattern matching. The finding may be a false positive and should be manually verified. | ## Finding Structure Each finding contains the following fields: | Field | Description | | --------------- | ---------------------------------------------------------------------------------------- | | **Module** | The scanner module that produced the finding (e.g., `xss-reflected`, `sqli-error-based`) | | **Severity** | Critical, High, Medium, Low, or Info | | **Confidence** | Certain, Firm, or Tentative | | **URL** | The target URL where the vulnerability was detected | | **Parameter** | The specific parameter or insertion point that was tested (if applicable) | | **Evidence** | Proof of the vulnerability, response excerpts, payloads, or other confirming data | | **Description** | Human-readable explanation of the vulnerability and its potential impact | ## Saving Output ### Using the -o/--output Flag Write output directly to a file: ```bash theme={null} # Save JSONL output vigolium scan --target https://example.com --format jsonl -o results.jsonl # Save HTML report vigolium scan --target https://example.com --format html -o report.html # Save console output vigolium scan --target https://example.com -o results.txt ``` ### Piping JSONL JSONL output can be piped to other tools for processing: ```bash theme={null} # Filter high and critical findings vigolium scan --target https://example.com --format jsonl | jq 'select(.severity == "high" or .severity == "critical")' # Count findings by severity vigolium scan --target https://example.com --format jsonl | jq -s 'group_by(.severity) | map({severity: .[0].severity, count: length})' # Extract just URLs with findings vigolium scan --target https://example.com --format jsonl | jq -r '.url' ``` ## Discovery and Spidering Output The discovery and spidering phases produce their own output alongside scan findings. ### Discovery Output Discovery output includes URLs and endpoints found through wordlist-based content discovery, Wayback Machine data, and JavaScript analysis. Each discovered URL is reported with its HTTP status code and response metadata. ```bash theme={null} # Run only discovery and save results vigolium scan --target https://example.com --only discovery --format html -o discovery-report.html ``` ### Spidering Output Spidering output includes pages found by the browser-based crawler, along with forms, links, and dynamic content discovered during crawling. ```bash theme={null} # Run only spidering and save results vigolium scan --target https://example.com --only spidering --format html -o spider-report.html ``` Both phases support all three output formats (console, JSONL, HTML). ## OAST Interactions Out-of-band Application Security Testing (OAST) findings come from DNS and HTTP callback interactions. When a scanner payload triggers an out-of-band request to the OAST server, the interaction is correlated back to the original test case. OAST findings appear in output with: * The original request that triggered the out-of-band interaction * The type of interaction (DNS lookup, HTTP request) * Timing information (when the callback was received) * Correlation data linking the interaction to the specific payload OAST interactions may arrive after the initial scan phase completes, as some out-of-band triggers have delayed execution. Vigolium waits for a configurable period after scanning to collect late-arriving callbacks. If outbound DNS or HTTP is blocked by a firewall, OAST-based detections will not work. The scanner will still produce findings through other detection methods, OAST simply adds an additional layer of out-of-band detection. ## Querying Results from Database All scan data is stored in the database (SQLite by default). You can query stored results using CLI commands without re-running scans. ### Listing Findings ```bash theme={null} # List all findings vigolium finding list # List findings for a specific project vigolium finding list --project my-project # Filter by minimum severity and by confidence (adds a colorized CONFIDENCE column) vigolium finding list --min-severity high vigolium finding list --confidence firm vigolium finding list --confidence certain,firm ``` `--confidence` keeps only findings whose confidence matches one of the comma-separated levels (`certain`, `firm`, `tentative`) and renders a colorized CONFIDENCE column, complementing `--min-severity`. ### Listing Traffic ```bash theme={null} # List recorded HTTP traffic vigolium traffic list # List traffic for a specific project vigolium traffic list --project my-project ``` Results are scoped to the active project. Use `--project-name`/`--project-uuid` to target a specific project, or set a default for your shell with `eval $(vigolium project use )`. See the [Projects & Multi-Tenancy](/others/projects) reference for details on multi-tenancy and project scoping. ### Reading a Standalone Export `finding` and `traffic` can read a file directly instead of your project database, which is handy for inspecting a `--format jsonl` export or a foreign `.sqlite` file from another machine. Pass `-S/--stateless` together with `--db `: ```bash theme={null} # Browse a scan's JSONL export with all the normal filters and sorting vigolium finding -S --db ./scan.jsonl --min-severity medium vigolium traffic -S --db ./scan.jsonl --status 500 -n 20 # A standalone .sqlite (e.g. from --format sqlite) works too vigolium finding -S --db ./scan.sqlite --json --with-records ``` `-S/--stateless` turns project scoping **off**, so every row in the file is shown regardless of the `project_uuid` it carries. Nothing is written to your project database (a JSONL source is loaded into a throwaway in-memory SQLite). The source type is auto-detected by extension, falling back to a header sniff (`.jsonl`/`.ndjson` vs the `SQLite format 3` magic / `.sqlite`/`.sqlite3`/`.db`). ### Rendering a Finding or Record as Markdown `--markdown` prints the selected findings/records as Markdown — evidence plus request/response in fenced `http` blocks — to stdout. Pipe it to a file or a viewer like `glow`, and pair it with `--id`, a fuzzy search term, or `-n 1` to focus a single item: ```bash theme={null} vigolium finding xss --markdown > findings.md vigolium finding -S --db ./scan.jsonl --id 42 --markdown vigolium traffic -S --db ./scan.jsonl search-term -n 1 --markdown ``` Under `-S/--stateless`, add `--compact` to window the response around the finding's match (`matched_at` / `extracted_results`) — or cap a record's body to a preview — so a long page doesn't flood the console. Without `--compact`, bodies render in full. # Vigolium Overview Source: https://docs.vigolium.com/getting-started/overview Vigolium is a high-fidelity web vulnerability scanner written in Go. It combines deterministic, module-based scanning with AI-driven agentic analysis to provide broad and deep coverage of web application security issues. Vigolium Workbench showing scan overview with severity breakdown and individual finding details Vigolium is a high-fidelity web vulnerability scanner that fuses agentic AI with native speed, modularity, and precision. It combines deterministic multi-phase scanning with AI-driven autonomous analysis to deliver comprehensive security coverage, from injection flaws and access control issues to framework-specific vulnerabilities and blind out-of-band attacks. The platform is built around three key components that work together to provide a complete vulnerability scanning solution. ## Key Components The core scanning engine. Handles all scanning logic, module execution, and heavy lifting, from content discovery and browser-based spidering to active fuzzing and agentic AI scanning. A self-hosted dashboard for visualizing scan results, managing projects, and tracking findings across your infrastructure. Deploy it on your own servers for full control over your data. A cloud-based solution that provides managed scanning, team collaboration, and centralized reporting without the overhead of self-hosting. ## Scanning Mode ### Agentic Scan Agentic scanning uses AI agents to drive or augment the scanning process. Invoked via `vigolium agent `. All AI dispatch is routed through the in-process **olium** engine, which supports eleven providers: `openai-codex-oauth`, `anthropic-api-key`, `anthropic-oauth`, `openai-api-key`, `openai-responses`, `anthropic-cli`, `anthropic-claude-sdk-bridge`, `anthropic-compatible`, `anthropic-vertex`, `google-vertex`, and `openai-compatible` (Ollama / OpenRouter / LM Studio / vLLM). | Mode | Command | Description | | ------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Query** | `vigolium agent query` | Single-shot prompt execution. Good for code review, endpoint discovery, secret detection. No network scanning. | | **Autopilot** | `vigolium agent autopilot` | Autonomous AI-driven pentest. The olium engine discovers endpoints, runs scans, and triages findings on its own, driving Bash, file ops, and first-class Vigolium tools until it calls `halt_scan`. Supports diff-focused runs (`--diff`), prior/knowledge-base context, intensity presets, and durable-mode resume. | | **Swarm** | `vigolium agent swarm` | AI-guided pipeline for targeted single-request or full-scope (`--discover`) scanning. The master agent selects modules, generates custom JS scanner extensions, runs code audit and SAST, executes scans, and triages results, with native Go handling the heavy lifting while AI intervenes at checkpoints. | | **Audit** | `vigolium agent audit` | Unified driver dispatcher for whitebox security audits — runs the embedded vigolium-audit harness, the standalone piolium harness, or both side-by-side under one parent scan. Modes: lite / balanced / deep. | | **Piolium** | `vigolium agent audit --driver=piolium` | Pi-native whitebox audit driver, up to 17 phases at `deep`. Requires the `pi` runtime and the `piolium` extension installed separately. | | **Olium** | `vigolium olium` / `vigolium ol` | Interactive TUI chat or one-shot non-interactive prompt, the underlying agent runtime. | All scan-oriented modes support `--source` for source-aware analysis and store session artifacts (plans, extensions, output) under `~/.vigolium/agent-sessions/`. See [Setting Up the Agent](/getting-started/setup-agent) for provider setup. Agent mode runs with **no sandbox**: the LLM has full shell, file, and network access on the host, and extensions can run arbitrary commands. Run agent mode in a disposable container or VM scoped to your engagement. See the [Security Warning](/others/security-warning) before you start. ### Native Scan Deterministic, multi-phase vulnerability scanning via `vigolium scan`. Fast, modular, and repeatable, runs content discovery, browser spidering, SPA crawling, and active/passive `dynamic-assessment` phases with **323 scanner modules** (207 active, 116 passive). | Category | Coverage | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Injection** | XSS (reflected, DOM-based, SSR hydration), SQL injection (error-based, boolean/time-blind), NoSQL injection, SSTI/CSTI, CRLF injection, command injection, XXE/SAML, prototype pollution | | **Access Control** | CSRF, IDOR, authorization bypass, mass assignment, forbidden bypass, HTTP method tampering | | **File & Path** | LFI, path traversal, file upload flaws, directory listing, backup/sensitive file discovery, path normalization bypass | | **API & Protocol** | GraphQL security suite (introspection, SQLi, IDOR/BOLA, DoS, batching), MCP server security (tool/resource/prompt fuzzing, session & origin checks), SSRF (direct & blind), open redirect, HTTP request smuggling, JWT vulnerabilities, JSONP callback, WebSocket security, race conditions | | **Framework-Specific** | Spring Boot, Django, Laravel, Rails, Express, Next.js, Nuxt, Remix, ASP.NET/Blazor, IIS, Flask, FastAPI | | **Enterprise & SaaS Platforms** | Adobe Experience Manager (dispatcher bypass, sensitive servlets, RCE-surface exposure, CVE probes), Salesforce Experience Cloud (Aura object/record exposure, guest Apex execution, Lightning debug mode), ServiceNow (widget & KB data exposure), Microsoft Power Pages (Dataverse Web API exposure) | | **CMS** | WordPress (XML-RPC, user enum, AJAX exposure), Drupal, Joomla, CMS installer exposure | | **Cloud & Infra** | Firebase (RTDB, storage, auth, functions), cloud storage listing/takeover, default credentials, web cache poisoning, CORS misconfiguration | | **Out-of-Band** | Blind vulnerabilities via OAST callbacks (blind SSRF, blind SSTI, OAST probes) | *** ## Vigolium CLI The CLI is the heart of Vigolium, powering all scanning operations with two complementary modes: ### CLI Highlights * **Value-aware mutation**: classify parameter values by semantic type and generate intelligent mutations * **Multi-phase pipeline**: external harvesting, content discovery, SPA crawling, and audit controlled by strategy presets * **Scanning profiles**: bundle strategy, pace, scope, and module config into a single YAML file * **Multiple input formats**: URLs, OpenAPI/Swagger, WSDL/SOAP, Postman, Burp Suite, cURL, Nuclei JSONL * **Browser-based spider**: Chromium-driven crawler with SPA support, form filling, and JS analysis * **Multi-session authentication**: inline sessions, session files, or full auth configs with login flows and token extraction * **JavaScript extensions**: custom modules and hooks via embedded JS engine * **Source-aware agentic scan**: pair `--source` with `swarm`, `autopilot`, or `audit` for code-context-aware AI scanning and audit * **Concurrent architecture**: configurable worker pool with per-host rate limiting and hybrid queue * **HTML reports**: self-contained HTML reports with sortable/filterable tables * **API server mode**: REST API with Swagger UI, multi-format ingestion, transparent HTTP proxy *** ## Vigolium Workbench A self-hosted web dashboard that provides a visual interface for managing and analyzing your scan data. Deploy Workbench on your own infrastructure to maintain complete control over your vulnerability data while giving your team an intuitive way to: * Browse and filter scan findings with severity breakdown per project * Track vulnerability trends across repositories and scan history * Manage multiple projects with multi-tenancy support * View detailed request/response evidence for each finding Workbench project overview showing scan summary and severity counts Workbench findings list with severity filters and search Workbench detailed finding view showing HTTP request and response evidence Workbench scan history view with vulnerability trend tracking Self-contained HTML report showing vulnerability summary with severity chart Self-contained HTML report with sortable and filterable findings table *** ## Vigolium Cloud Console A cloud-based solution for teams that want the power of Vigolium without managing infrastructure. Console is the **upgraded, fully-featured version of Vigolium**, managed scanning, centralized reporting, team collaboration, and extra features layered on top of the open-source core, so you can focus on fixing vulnerabilities instead of maintaining tooling. Check out the Cloud Console at [console.vigolium.com](https://console.vigolium.com/). Vigolium Console native scan view Vigolium Console agentic scan view Open-source audit project list Open-source audit findings detail *** ## Where to Go Next | I want to... | Start here | | ------------------------------------- | ------------------------------------------------------------------------------- | | Install the CLI and run my first scan | [Quickstart](/getting-started/quickstart) | | Pick the right mode for the job | [Choosing a Scan Mode](/getting-started/choosing-a-mode) | | Run one-shot scans (CI, ad-hoc) | [Native Scan & Stateless Scanning](/getting-started/native-scan) | | Wire up AI providers | [Setting Up the Agent](/getting-started/setup-agent) | | Understand how native scanning works | [How It Works](/native-scan/how-it-works) | | Pick the right scanning strategy | [Strategies](/native-scan/strategies) | | Dive into individual scan phases | [Phases](/native-scan/phases/discovery), discovery, spidering, audit, extension | | Try agentic scanning | [Agent Mode](/agentic-scan/agent-mode) | | Let AI drive scans autonomously | [Autopilot](/agentic-scan/autopilot) | | Run multi-phase AI + native pipelines | [Swarm](/agentic-scan/swarm) | | Audit source code in depth | [Agentic Security Audit](/getting-started/agentic-security-audit) | | Chat with the agent runtime | [Olium](/agentic-scan/olium) | | Run Vigolium as an API server | [Server Mode](/server-mode/running-the-server) | | Tweak scan settings | [Configuration](/getting-started/configuration) | | Export and format results | [Output & Reporting](/getting-started/output-and-reporting) | | Write custom JS extensions | [Writing Extensions](/customization/writing-extensions) | | Browse the REST API | [API Overview](/api-overview) | *** For any inquiries, feel free to contact us at [contact@vigolium.com](mailto:contact@vigolium.com). # Installation & QuickStart Source: https://docs.vigolium.com/getting-started/quickstart Get up and running with Vigolium in a few minutes, install, verify, and run your first scan. ## Step 1: Install [Vigolium Open Source](https://github.com/vigolium/vigolium) ```bash theme={null} curl -fsSL https://vigolium.com/install.sh | bash ``` Installs `~/.local/bin/vigolium` and adds it to your shell profile. The installer verifies the SHA-256 checksum before installing. ```bash theme={null} npm install -g @vigolium/vigolium ``` Thin launcher that pulls the prebuilt binary for your platform as an optional dependency (Node 16+). ```bash theme={null} bun add -g @vigolium/vigolium ``` Installs the Vigolium launcher globally with Bun. Same prebuilt-binary launcher as the npm install path; Bun is just a faster package manager. ```bash theme={null} brew install vigolium/tap/vigolium ``` Installs the latest release from the Vigolium tap on macOS and Linux. Upgrade with `brew upgrade vigolium`. ```bash theme={null} docker pull j3ssie/vigolium:latest docker run --rm j3ssie/vigolium:latest scan -h ``` The container entrypoint is the `vigolium` binary, pass any subcommand directly. ```bash theme={null} git clone https://github.com/vigolium/vigolium.git cd vigolium make build ``` Requires Go 1.27+, Bun 1.3.11+, `git`, and `make`. Always use `make build` (not `go build`), it injects version metadata. ```powershell theme={null} npm install -g @vigolium/vigolium ``` Vigolium ships a native **windows/amd64** build since v0.4.3. The npm launcher pulls `vigolium.exe` for you; alternatively download `vigolium__windows_amd64.zip` from the [releases page](https://github.com/vigolium/vigolium/releases) and put `vigolium.exe` on your `PATH`. The `curl | bash` installer and Homebrew are POSIX-only. `vigolium update` cannot self-update on Windows either — it names the zip to download instead. Windows on ARM installs the x64 build and runs it under emulation (Bun ships no `bun-windows-arm64` target, so a native arm64 build would ship with the audit driver broken). Shell-dependent features resolve the host shell automatically: an installed Git Bash / MSYS2 `bash` if present, otherwise PowerShell. If `~/.local/bin` was not already on your `PATH`, activate it without restarting your shell: ```bash theme={null} export PATH="$HOME/.local/bin:$PATH" ``` ## Step 2: Validate the installation ```bash theme={null} vigolium version vigolium doctor ``` `doctor` reports any missing optional dependencies (a browser for SPA spidering, nuclei templates for the known-issue scan, `bun`/`pi` for agent drivers) and confirms your config is valid. Let it auto-install anything missing: ```bash theme={null} vigolium doctor --fix # auto-install/fix every failing check ``` `--only` accepts any of: `nuclei`, `chrome`, `bun`, `claude`, `agent-browser`, `pi`, `piolium`. Everything Vigolium stores lives under `~/.vigolium/` (override with `VIGOLIUM_HOME`): config at `vigolium-configs.yaml`, scan DB at `database-vgnm.sqlite`, agent artifacts under `agent-sessions/`. Run `vigolium init` to create the workspace explicitly. ## Step 3: Run a full scan `vigolium scan` runs the full multi-phase pipeline (discovery → spidering → dynamic-assessment) using the **balanced** strategy by default: ```bash theme={null} vigolium scan -t https://example.com ``` Tune the depth/speed trade-off with a strategy preset: ```bash theme={null} vigolium scan -t https://example.com --strategy lite # fast, dynamic-assessment only vigolium scan -t https://example.com --strategy balanced vigolium scan -t https://example.com --strategy deep # thorough, more modules ``` `--intensity quick|balanced|deep` is a higher-level alias that also tunes the scanning profile. Not sure which mode to use? See [Choosing a Scan Mode](/getting-started/choosing-a-mode). ## Step 4: One-shot stateless scan For CI/CD pipelines, scripting, or quick ad-hoc checks where you don't want anything left behind on disk, add `--stateless` and export results with `-o`. Vigolium spins up a temporary SQLite database, runs the requested phases, writes the output, then deletes the database on exit. ```bash theme={null} # Full pipeline, JSONL out, nothing persisted vigolium scan --stateless -t https://example.com --format jsonl -o findings # JSONL + HTML report, with content discovery vigolium scan --stateless -t https://example.com --discover --format jsonl,html -o scan # Single endpoint, no DB at all vigolium scan-url https://example.com/api/users?id=1 -j ``` Multiple targets each get an isolated temp database and a per-host filename suffix so results don't overwrite: ```bash theme={null} vigolium scan --stateless -T targets.txt --format jsonl -o results # -> results-example.com.jsonl, results-test.example.com.jsonl, … ``` `--stateless` and `--db` are mutually exclusive. See [Native Scan & Stateless Scanning](/getting-started/native-scan) for the full recipe book. ## Step 5: Choose what to scan ```bash theme={null} # A file of targets, one URL per line vigolium scan -T targets.txt # From an OpenAPI / Swagger spec vigolium scan -i api.yaml -I openapi -t https://api.example.com # From a WSDL / SOAP service (or a live .svc/.asmx URL) vigolium scan -i service.wsdl -I wsdl -t https://soap.example.com # Pipe URLs from stdin cat urls.txt | vigolium scan # A raw HTTP request or curl command (auto-detected) echo "curl -X POST -d 'user=admin' https://example.com/login" | vigolium scan-request ``` Supported input modes (`-I`): `urls`, `openapi`, `swagger`, `wsdl` (`soap`, `svc`), `postman`, `curl`, `burpraw`, `burpxml`, `nuclei`, `har`. An unknown `-I` value is rejected with a clear error (no silent fallback). ## Step 6: Pick specific modules (optional) ```bash theme={null} # Only run XSS and SQLi modules (fuzzy match on module ID/name) vigolium scan -t https://example.com -m xss -m sqli # Filter by tag instead vigolium scan -t https://example.com --module-tag injection # List everything available vigolium -M ``` ## Step 7: Get results out By default findings stream to the console. For files or machine-readable output, use `--format` with `-o`: ```bash theme={null} # JSONL for scripting / CI vigolium scan -t https://example.com --format jsonl -o results # Self-contained HTML report vigolium scan -t https://example.com --format html -o report # Several formats at once vigolium scan -t https://example.com --format jsonl,html -o scan ``` | Flag | Effect | | ----------------------- | -------------------------------------------------------- | | `--format console` | Human-readable terminal output (default) | | `--format jsonl` / `-j` | One JSON object per line | | `--format html` | Interactive ag-grid report (requires `-o`) | | `-o, --output` | Output file path (base name; extension added per format) | | `--ci-output-format` | JSONL only, no banners or color, ideal for CI | | `--silent` | Suppress everything except findings | ## Step 8: Run a single phase Use `run ` (an alias for `scan --only `) when you only want one stage of the pipeline: ```bash theme={null} vigolium run discovery -t https://example.com # content discovery only vigolium run spidering -t https://example.com # browser crawl only vigolium run dynamic-assessment -t https://example.com ``` Phases: `ingestion`, `discovery`, `external-harvest`, `spidering`, `known-issue-scan`, `dynamic-assessment`, `extension`. ## A note on persistence `vigolium scan` writes results to a persistent SQLite database at `~/.vigolium/database-vgnm.sqlite`, so you can browse them afterward: ```bash theme={null} vigolium traffic list # ingested HTTP records vigolium finding list # discovered vulnerabilities ``` For one-shot runs that leave nothing behind (CI, ad-hoc checks), add `--stateless` and export with `-o`. See [Native Scan & Stateless Scanning](/getting-started/native-scan) for the full set of recipes. ## Updating & uninstalling ```bash theme={null} vigolium update # update binary + nuclei templates vigolium update --skip-templates # only reinstall the binary vigolium update -F # skip the confirmation prompt ``` Homebrew, npm, and Docker installs are upgraded through their own tooling (`brew upgrade vigolium` / `npm update -g @vigolium/vigolium` / `docker pull`), not `vigolium update`. ## Next steps * [Choosing a Scan Mode](/getting-started/choosing-a-mode), pick the right mode for your job. * [Native Scan & Stateless Scanning](/getting-started/native-scan), CLI scan recipes. * [Scanning Strategies](/native-scan/strategies), strategies, profiles, pace. * [Authenticated Scanning](/native-scan/authentication), sessions and login flows. * [Setting Up the Agent](/getting-started/setup-agent), AI-driven autopilot and swarm scans. * [Configuration Reference](/getting-started/configuration), full configuration options. # Server Mode: Ingesting Data via the API Source: https://docs.vigolium.com/getting-started/server-and-ingestion This guide covers how to start Vigolium in server mode and ingest HTTP traffic into the database using the REST API and CLI. ## Starting the Server ```bash theme={null} # Start with an API key export VIGOLIUM_API_KEY=my-secret-key vigolium server # Custom host and port export VIGOLIUM_API_KEY=my-secret-key vigolium server --host 127.0.0.1 --service-port 9002 # With transparent HTTP proxy for recording traffic export VIGOLIUM_API_KEY=my-secret-key vigolium server --ingest-proxy-port 9003 # Without authentication (development only) vigolium server -A ``` The server listens on `0.0.0.0:9002` by default. ## CORS Configuration The server's CORS behavior is controlled by `cors_allowed_origins` in `~/.vigolium/vigolium-configs.yaml`: ```yaml theme={null} server: cors_allowed_origins: reflect-origin ``` | Value | Behavior | | ---------------------------------------------------- | --------------------------------------------------------------- | | `reflect-origin` (default) | Echoes the requesting `Origin` header back. Allows credentials. | | `*` | Allows all origins without credentials (standard wildcard). | | *(empty string)* | Disables CORS middleware entirely. | | `https://app.example.com, https://admin.example.com` | Comma-separated allowlist. Allows credentials. | ## Project Scoping All server operations are scoped to a project via the `X-Project-UUID` request header. If omitted, the default project is used. ```bash theme={null} # Ingest into a specific project curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "X-Project-UUID: a1b2c3d4-..." \ -H "Content-Type: application/json" \ -d '{"input_mode": "url", "content": "https://example.com"}' ``` All queries (findings, HTTP records, stats, scans) return data scoped to the project specified in the header. See [Projects](/others/projects) for the full multi-tenancy reference. ## Authentication All API requests (except `/health`) require a Bearer token: ``` Authorization: Bearer my-secret-key ``` API key resolution order: `VIGOLIUM_API_KEY` env var > `server.auth_api_key` in config file. ## API Endpoints | Method | Path | Description | | ------ | ------------------------------------------ | --------------------------------------------------------------------------------------- | | GET | `/` | App info (no auth required) | | GET | `/health` | Health check (no auth required) | | GET | `/metrics` | Prometheus metrics (no auth required) | | GET | `/swagger/*` | Swagger UI and OpenAPI spec (no auth required) | | GET | `/server-info` | Server status, queue depth, record/finding counts | | GET | `/api/modules` | List available scanner modules | | GET | `/api/http-records` | Query stored HTTP records | | GET | `/api/findings` | Query scan findings | | POST | `/api/ingest-http` | Ingest HTTP traffic into the database | | GET | `/api/stats` | Aggregated scan statistics | | GET | `/api/scope` | View scope configuration | | POST | `/api/scope` | Update scope configuration | | GET | `/api/config` | View server configuration | | POST | `/api/config` | Update server configuration | | POST | `/api/scan` | Trigger a background scan | | GET | `/api/scan/status` | Check scan status | | DELETE | `/api/scan` | Cancel a running scan | | GET | `/api/source-repos` | List source repos | | POST | `/api/source-repos` | Create a source repo | | GET | `/api/source-repos/:id` | Get a source repo | | PUT | `/api/source-repos/:id` | Update a source repo | | DELETE | `/api/source-repos/:id` | Delete a source repo | | POST | `/api/agent/run/query` | Single-shot agent prompt execution | | POST | `/api/agent/run/autopilot` | Autonomous AI-driven scanning session | | POST | `/api/agent/run/swarm` | AI-guided multi-phase vulnerability scan | | POST | `/api/agent/run/audit` | Unified whitebox audit (audit and/or piolium) with `driver: auto\|both\|audit\|piolium` | | POST | `/api/agent/chat/completions` | OpenAI-compatible chat completions (synchronous) | | GET | `/api/agent/status/list` | List agent runs | | GET | `/api/agent/status/:id` | Get agent run status (includes full result when completed) | | GET | `/api/agent/sessions` | Paginated session history | | GET | `/api/agent/sessions/:id` | Full session detail with debug fields | | GET | `/api/agent/sessions/:id/logs` | Read or tail `runtime.log` (SSE supported) | | GET | `/api/agent/sessions/:id/artifacts` | List session artifact files | | GET | `/api/agent/sessions/:id/artifacts/{name}` | Read a specific artifact | ## Ingesting Data via API The `/api/ingest-http` endpoint accepts multiple input modes. All requests use `POST` with a JSON body. ### Declaring the Pushing Tool Records ingested through this endpoint are stored with `source: "ingest-server"` by default — the same label every HTTP client shares. A client can name itself instead, so its traffic stays distinguishable later: ```bash theme={null} curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "X-Vigolium-Source: caido" \ -H "Content-Type: application/json" \ -d '{"input_mode": "url", "content": "https://example.com/api/users?id=1"}' ``` Accepted values are `burp` and `caido` — the same labels the bridge path writes, so one `--source caido` filter covers traffic that arrived either by Vigolium pulling over the bridge or by the plugin pushing here. The Vigolium [Burp extension](/getting-started/burp-suite) and [Caido plugin](/getting-started/caido-plugin) set it automatically. The list is a closed allowlist rather than a passthrough, and anything else falls back to `ingest-server`. `source` is not only a display label: it decides which rows scan-on-receive feeds back into a running scan, so a client free to claim `scanner` could silently exclude its own traffic from the scan it just asked for. ### Ingest a Single URL ```bash theme={null} curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "input_mode": "url", "content": "https://example.com/api/users?id=1" }' ``` ### Ingest Multiple URLs (url\_file mode) Pass a newline-separated list of URLs. Lines starting with `#` are treated as comments. ```bash theme={null} curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "input_mode": "url_file", "content": "https://example.com/api/users?id=1\nhttps://example.com/api/posts?page=2\nhttps://example.com/login" }' ``` ### Ingest a curl Command ```bash theme={null} curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "input_mode": "curl", "content": "curl -X POST https://example.com/api/login -H \"Content-Type: application/json\" -d \"{\\\"username\\\":\\\"admin\\\",\\\"password\\\":\\\"test\\\"}\"" }' ``` Using `content_base64` to avoid JSON escaping issues: ```bash theme={null} # Encode the curl command ENCODED=$(echo -n 'curl -X POST https://example.com/api/login -H "Content-Type: application/json" -d "{\"username\":\"admin\",\"password\":\"test\"}"' | base64) curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"curl\", \"content_base64\": \"$ENCODED\" }" ``` ### Ingest a Raw HTTP Request (Burp-style) Send a base64-encoded raw HTTP request, optionally with its response: ```bash theme={null} # Encode raw request RAW_REQ=$(printf 'GET /api/users?id=1 HTTP/1.1\r\nHost: example.com\r\nCookie: session=abc123\r\n\r\n' | base64) curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"burp_base64\", \"http_request_base64\": \"$RAW_REQ\" }" ``` With both request and response: ```bash theme={null} RAW_REQ=$(printf 'POST /api/login HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\n\r\n{"username":"admin","password":"test"}' | base64) RAW_RESP=$(printf 'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{"token":"eyJhbGciOiJIUzI1NiJ9..."}' | base64) curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"burp_base64\", \"http_request_base64\": \"$RAW_REQ\", \"http_response_base64\": \"$RAW_RESP\" }" ``` ### Ingest a Raw HTTP Request with a URL Hint Raw HTTP requests don't contain the scheme (`https` vs `http`), and the `Host` header may not match the public hostname (e.g. behind a load balancer). Use the `url` field to provide the correct scheme and host: ```bash theme={null} RAW_REQ=$(printf 'POST /api/login HTTP/1.1\r\nHost: internal-lb\r\nContent-Type: application/json\r\n\r\n{"user":"admin"}' | base64) curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"burp_base64\", \"url\": \"https://app.example.com\", \"http_request_base64\": \"$RAW_REQ\" }" ``` ### Ingest an OpenAPI / Swagger Spec ```bash theme={null} curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "input_mode": "openapi", "content": "{\"openapi\":\"3.0.0\",\"info\":{\"title\":\"Example\",\"version\":\"1.0\"},\"servers\":[{\"url\":\"https://api.example.com\"}],\"paths\":{\"/users\":{\"get\":{\"summary\":\"List users\"}},\"/users/{id}\":{\"get\":{\"summary\":\"Get user\",\"parameters\":[{\"name\":\"id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"integer\"}}]}}}}" }' ``` Using base64 for larger specs: ```bash theme={null} SPEC=$(base64 < openapi.yaml) curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"openapi\", \"content_base64\": \"$SPEC\" }" ``` ### Ingest a Postman Collection ```bash theme={null} COLLECTION=$(base64 < collection.json) curl -X POST http://localhost:9002/api/ingest-http \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d "{ \"input_mode\": \"postman_collection\", \"content_base64\": \"$COLLECTION\" }" ``` ## Ingesting Data via CLI The `vigolium ingest` command supports both remote (server) and local (direct-to-database) modes. ### Remote Ingestion (to a running server) ```bash theme={null} export VIGOLIUM_API_KEY=my-secret-key # Pipe URLs from stdin cat urls.txt | vigolium ingest -s http://localhost:9002 # From a file vigolium ingest -s http://localhost:9002 --input targets.txt # OpenAPI spec with a base URL vigolium ingest -s http://localhost:9002 \ --input api.yaml -I openapi -t https://api.example.com # Control submission rate vigolium ingest -s http://localhost:9002 \ --input urls.txt --concurrency 20 -r 200 ``` ### Local Ingestion (direct to database) When `--server` is omitted, requests are fetched and stored directly in the local database: ```bash theme={null} # Ingest URLs (fetches each and stores request + response) cat urls.txt | vigolium ingest # From an OpenAPI spec vigolium ingest --input api.yaml -I openapi -t https://api.example.com # From a WSDL / SOAP service description vigolium ingest --input service.wsdl -I wsdl -t https://soap.example.com # With a custom scan ID for tagging vigolium ingest --input urls.txt --scan-uuid 6f1c2b90-8b1e-4a6c-9d2f-5c7a1e3b9d40 # Use a specific database file vigolium ingest --input urls.txt --db ./project.db # Ingest into a specific project vigolium ingest --input urls.txt --project-uuid a1b2c3d4-... ``` ## Ingesting via Transparent Proxy Start the server with a proxy port to passively record HTTP traffic: ```bash theme={null} export VIGOLIUM_API_KEY=my-secret-key vigolium server --ingest-proxy-port 9003 ``` Then route your tools through the proxy: ```bash theme={null} # curl through the proxy curl -x http://localhost:9003 https://example.com/api/users # httpx through the proxy echo "https://example.com" | httpx -proxy http://localhost:9003 # nuclei through the proxy nuclei -u https://example.com -proxy http://localhost:9003 ``` All proxied HTTP traffic is automatically recorded in the database. HTTPS CONNECT tunneling is passed through without recording. ## Querying Ingested Data To merge live proxy history into the same traffic API, enable the bridge listener in the Vigolium [Burp extension](/getting-started/burp-suite) or the [Caido plugin](/getting-started/caido-plugin) and start the server with: ```bash theme={null} vigolium server --burp-bridge-url http://127.0.0.1:9009 ``` `--caido-bridge-url` is an alias for the same flag — both integrations speak one protocol, so which one is listening is discovered rather than declared. The `/api/http-records` examples below then query the combined database and live result set; no separate bridge search route is needed. Live records are identified by their source, `"source": "burp"` or `"source": "caido"`, taken from the `implementation` the listener reports on every reply (a listener too old to report one is read as Burp). The response also carries `X-Vigolium-Bridge-Source` naming the vendor that answered, so a client can label the merged page without inspecting each row. Filtering by `?source=` works as expected in both directions: `?source=caido` against a Burp listener returns only the database's own Caido rows, and vice versa. ### List HTTP Records ```bash theme={null} # All records (paginated, default limit=50) curl -s http://localhost:9002/api/http-records \ -H "Authorization: Bearer my-secret-key" | jq . # Filter by domain curl -s "http://localhost:9002/api/http-records?domain=example.com" \ -H "Authorization: Bearer my-secret-key" | jq . # Filter by status code and method curl -s "http://localhost:9002/api/http-records?status_code=200,302&method=GET,POST" \ -H "Authorization: Bearer my-secret-key" | jq . # Search across URLs and headers curl -s "http://localhost:9002/api/http-records?search=admin&limit=10" \ -H "Authorization: Bearer my-secret-key" | jq . # Pagination curl -s "http://localhost:9002/api/http-records?limit=20&offset=40" \ -H "Authorization: Bearer my-secret-key" | jq . ``` ### List Findings ```bash theme={null} # All findings curl -s http://localhost:9002/api/findings \ -H "Authorization: Bearer my-secret-key" | jq . # Filter by severity curl -s "http://localhost:9002/api/findings?severity=high,critical" \ -H "Authorization: Bearer my-secret-key" | jq . # Filter by module curl -s "http://localhost:9002/api/findings?module_name=xss-reflected" \ -H "Authorization: Bearer my-secret-key" | jq . # Filter by domain curl -s "http://localhost:9002/api/findings?domain=example.com" \ -H "Authorization: Bearer my-secret-key" | jq . ``` ### Server Info ```bash theme={null} curl -s http://localhost:9002/server-info \ -H "Authorization: Bearer my-secret-key" | jq . ``` Response: ```json theme={null} { "version": "0.1.0", "uptime": "2h15m30s", "service_addr": "0.0.0.0:9002", "proxy_addr": "0.0.0.0:9003", "db_driver": "sqlite", "queue_depth": 0, "total_records": 1542, "total_findings": 23 } ``` ## Scan Management via API After ingesting HTTP records, trigger a vulnerability scan via the API. ### Trigger a Scan ```bash theme={null} curl -s -X POST http://localhost:9002/api/scan \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{}' | jq . ``` Force re-scan with specific modules: ```bash theme={null} curl -s -X POST http://localhost:9002/api/scan \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "force": true, "enable_modules": ["xss-scanner", "sqli-error-based"] }' | jq . ``` Returns `202 Accepted` on success, `409 Conflict` if a scan is already running. ### Check Scan Status ```bash theme={null} curl -s http://localhost:9002/api/scan/status \ -H "Authorization: Bearer my-secret-key" | jq . ``` ### Cancel a Running Scan ```bash theme={null} curl -s -X DELETE http://localhost:9002/api/scan \ -H "Authorization: Bearer my-secret-key" | jq . ``` See the [API Reference](/api-references/scan) for full request/response details. ## Running AI Agents via API The agent API provides four run modes that mirror the `vigolium agent` CLI subcommands (query, autopilot, swarm, audit). Concurrency is controlled by `server.agent_heavy_max` and `server.agent_light_max`. ### Query, Single-Shot Agent Run ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/query \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "prompt_template": "security-code-review", "source": "/home/user/src/my-app" }' | jq . ``` At least one of `prompt_template`, `prompt_file`, or `prompt` is required. Returns `202 Accepted` on success. Set `"stream": true` for real-time SSE output. The legacy `repo_path` JSON field is still accepted as an alias for `source`. ### Autopilot, Autonomous Scanning ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/autopilot \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "target": "https://example.com", "focus": "API injection", "intensity": "balanced", "stream": true }' ``` ### Swarm, AI-Guided Multi-Phase Scan ```bash theme={null} curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "input": "https://example.com", "discover": true, "triage": true, "stream": true }' ``` SSE events are `data:` lines with JSON payloads: `{"type":"chunk","text":"..."}` for real-time output, `{"type":"phase","phase":"..."}` for swarm phase transitions, `{"type":"done","result":{...}}` on completion, or `{"type":"error","error":"..."}` on failure. > **Provider overrides are CLI-only.** The server resolves the olium provider once from `agent.olium.*` in `vigolium-configs.yaml`. To switch providers on a server-side workload, edit the YAML and reload, there is no per-request provider field. ### List All Agent Runs ```bash theme={null} curl -s http://localhost:9002/api/agent/status/list \ -H "Authorization: Bearer my-secret-key" | jq . ``` ### Check Agent Run Status ```bash theme={null} curl -s http://localhost:9002/api/agent/status/agt-550e8400... \ -H "Authorization: Bearer my-secret-key" | jq . ``` Once the run completes, the response includes a `result` field with the full agent output (raw text, findings, HTTP records). See [Agent Mode](/agentic-scan/agent-mode) for the full agent documentation (autopilot, swarm, audit, piolium, query, olium) and the [API Reference](/api-references/agent) for request/response details. ## Input Modes Reference | Mode | Content Field | Description | | --------------------- | ----------------------------- | ----------------------------------- | | `url` | `content` | A single URL | | `url_file` | `content` | Newline-separated list of URLs | | `curl` | `content` or `content_base64` | A curl command string | | `burp_base64` | `http_request_base64` | Base64-encoded raw HTTP request | | `openapi` / `swagger` | `content` or `content_base64` | OpenAPI/Swagger spec (JSON or YAML) | | `postman_collection` | `content` or `content_base64` | Postman Collection (JSON) | For `burp_base64` mode, you can also include `http_response_base64` to store the response alongside the request. For modes that accept large payloads, prefer `content_base64` to avoid JSON escaping issues. # Setting Up the Agent Source: https://docs.vigolium.com/getting-started/setup-agent Vigolium's AI features (autopilot, swarm, source-code audit, query) all run through one in-process runtime called olium. This page walks you through wiring each piece up, pick the section that matches your setup. olium talks to a provider (Claude / OpenAI / a local model), and two specialised drivers, **vigolium-audit** and **piolium**, run on top of it for source-code audits. ## Recommended: reuse the coding-agent subscription you already have If you already pay for a coding agent, **Codex/ChatGPT, Claude Code, Grok, Gemini, Copilot**, you don't need a new API key. Put a local router in front of those logins and point vigolium at it as a plain OpenAI-compatible endpoint. One config, and every `vigolium agent …` command runs on the subscription you're already paying for. Two routers do this well, pick either: * **[OmniRoute](https://github.com/diegosouzapw/OmniRoute)** * **[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)** Both log into your existing agent accounts and expose them behind a single local OpenAI-compatible `/v1` endpoint, so switching models is a one-line change on vigolium's side. Install and log in per the router's README, then leave it running. It will print the local port it listens on. ```bash theme={null} export OMNI_API_KEY="sk-" vigolium config set agent.olium.provider openai-compatible vigolium config set agent.olium.custom_provider.base_url http://localhost:20128/v1 vigolium config set agent.olium.custom_provider.model_id gc/grok-4.5 vigolium config set agent.olium.custom_provider.api_key '${OMNI_API_KEY}' ``` ```bash theme={null} vigolium ol -p 'what model are you running' ``` A model name back means you're done, `autopilot`, `swarm`, `query` and the audit drivers all use it from here. The port and the `model_id` are whatever your router exposes, `http://localhost:20128/v1` and `gc/grok-4.5` above are OmniRoute's; hit the router's `/v1/models` to list the ids it routes for you. Quoting `'${OMNI_API_KEY}'` keeps the key out of `vigolium-configs.yaml`, it's expanded from the environment at load time (see [custom headers](#custom-headers-auth-routing-observability)). Whatever you route to must actually emit **tool calls**, olium is an agent loop, not a chat box. Frontier models behind these routers (Grok, Claude, GPT, Gemini) are all fine; small local models often aren't. See the [tool-calling caveat](#4-custom-llm-backend-openaianthropic-compatible-api) below. *** If you'd rather wire a provider up directly, without a router, the rest of this page covers each one: | What | Section | When you need it | | -------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | olium provider | [Olium agent](#1-olium-agent-the-engine-everything-runs-on) | Always, every agent command needs one provider. | | Codex (OpenAI OAuth) | [Codex](#2-codex-cheapest-with-a-chatgpt-subscription) | You have a ChatGPT Plus/Pro/Team subscription. | | Claude | [Claude](#3-claude-anthropic) | You have a Claude Code subscription (Agent SDK bridge — the default — or the `claude` CLI), an OAuth token, or an Anthropic API key. | | Custom LLM backend / local model | [Custom LLM backend](#4-custom-llm-backend-openaianthropic-compatible-api) | You want a local model (Ollama, LM Studio) or a custom OpenAI- / Anthropic-compatible gateway (OpenRouter, vLLM, LiteLLM, …). | | Vigolium Audit | [Vigolium audit](#5-vigolium-audit-source-code-driver) | You want a whitebox source-code audit with no extra install. | | Piolium audit | [Piolium audit](#6-piolium-audit-pi-native-driver) | You want piolium's 17-phase Pi-native audit (separate install). | All settings live in `~/.vigolium/vigolium-configs.yaml`. You can edit it directly, or use `vigolium config set `. *** ## 1. Olium agent, the engine everything runs on olium is the in-process agent runtime (`pkg/olium/`) that backs every `vigolium agent …` subcommand. Setting it up means picking one provider and giving it credentials. The supported providers: | Provider | Auth | Default model | Notes | | ------------------------------- | ----------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | | `openai-compatible` *(default)* | optional `api_key` | `gemma4:latest` (via `custom_provider.model_id`) | Custom OpenAI-format backend: Ollama, OpenRouter, LM Studio, vLLM, … | | `anthropic-compatible` | optional `api_key` | via `custom_provider.model_id` | Custom Anthropic Messages-format backend (self-hosted gateway / LiteLLM proxy). | | `openai-codex-oauth` | `~/.codex/auth.json` (from `codex login`) | `gpt-5.5` | Cheapest with a ChatGPT sub. | | `anthropic-api-key` | `$ANTHROPIC_API_KEY` | `claude-opus-4-7` | Direct Anthropic API billing. | | `anthropic-oauth` | `claude setup-token` bearer | `claude-opus-4-7` | Uses your Claude Pro/Max plan. | | `openai-api-key` | `$OPENAI_API_KEY` | `gpt-5.5` | Direct OpenAI API billing (Chat Completions). | | `openai-responses` | `$OPENAI_API_KEY` | `gpt-5.5` | Direct OpenAI API billing via the public Responses API (`/v1/responses`). | | `anthropic-claude-sdk-bridge` | Claude Code subscription (no key) | Claude Code default | Claude via the [Agent SDK](https://github.com/vigolium/vigolium-audit); the recommended default Claude path. | | `anthropic-cli` | `claude` binary on `$PATH` | `claude-opus-4-7` | Shells out to the Claude Code CLI (alias: `anthropic-claude-cli`). | | `anthropic-vertex` | GCP service-account JSON | `claude-opus-4-6` | Claude on Vertex AI. | | `google-vertex` | GCP service-account JSON | `gemini-2.5-pro` | Gemini on Vertex AI. | Verify any setup with: ```bash theme={null} vigolium ol -p 'what model are you running' ``` If that returns a model name, the provider is wired correctly. From there, `vigolium agent autopilot`, `vigolium agent swarm`, etc. all work. *** ## 2. Codex, cheapest with a ChatGPT subscription If you already use OpenAI's **Codex CLI**, vigolium reuses the same OAuth credential file. No API key needed, refresh handled automatically. ```bash theme={null} # 1. Install Codex CLI (one-time) and log in. codex login codex exec 'hello' # sanity check — should print a model name # 2. Pin vigolium to it (defaults already match; this just makes it explicit). vigolium config set agent.olium.provider openai-codex-oauth vigolium config set agent.olium.oauth_cred_path ~/.codex/auth.json vigolium config set agent.olium.model gpt-5.5 # 3. Verify. vigolium ol -p 'what model are you running' ``` `~/.codex/auth.json` is read on every run; the JWT is auto-refreshed when it expires, so you don't have to re-login. *** ## 3. Claude (Anthropic) **Prefer the official Claude Code client.** The recommended way to drive Claude from olium is through Anthropic's own client — the **Agent SDK bridge** (`anthropic-claude-sdk-bridge`, 3a below, the default) or the `claude` CLI shell-out (`anthropic-cli`, 3b). Both ride your existing Claude Code subscription through the sanctioned path. The raw **OAuth token** (3c) and **API key** (3d) options talk to the Messages API directly — a Pro/Max token used that way lands you in rate-limit / overage territory almost immediately, and the API key bills per token at the highest rates of any provider listed here — so reach for them only if you need direct-API billing (see the warnings on each). Four options, in order of preference. The first two ride the official Claude Code client; the last two hit the Messages API directly. ### 3a. Claude Code Agent SDK (`anthropic-claude-sdk-bridge`) This is the **recommended default** way to use Claude with vigolium. It drives Claude Code through the **Claude Agent SDK** by shelling out to the `vigolium-audit bridge` sidecar (from [vigolium/vigolium-audit](https://github.com/vigolium/vigolium-audit)) — no API key, it uses your logged-in Claude Code subscription. The run is a controlled, reproducible SDK invocation that always loads the `vigolium-scanner` skill so the agent knows the `vigolium` CLI. ```bash theme={null} # 1. Be logged into Claude Code once. claude # complete the interactive login # 2. Point vigolium at the SDK bridge (the default Claude config). vigolium config set agent.olium.provider anthropic-claude-sdk-bridge vigolium config set agent.olium.model opus # optional: opus | sonnet | a full id; omit for the Claude Code default # 3. Verify. vigolium ol -p 'what model are you running' ``` The `vigolium-audit` binary that hosts the bridge is **embedded** in vigolium — no separate install. Override it with `vigolium config set agent.olium.bridge_binary /path/to/vigolium-audit` or the per-run `--bridge-bin` flag. When `agent.olium.llm_api_key` / `oauth_token` is set it's forwarded to the bridge; otherwise the ambient subscription is used. Unlike the `claude` CLI shell-out (3b), the SDK bridge does **not** load your personal `~/.claude` config or a project's `CLAUDE.md` — it's a self-contained, reproducible run that behaves the same on any machine (CI, containers). Pick the CLI shell-out below when you want your full personal Claude Code environment applied to the current directory. ### 3b. Anthropic CLI (`claude` shell-out) If you'd rather have vigolium delegate to the `claude` binary on `$PATH` (so it uses whatever auth `claude` itself is configured with, plus your personal `CLAUDE.md`, MCP servers, and installed skills): ```bash theme={null} which claude # must resolve vigolium config set agent.olium.provider anthropic-cli # alias: anthropic-claude-cli vigolium config set agent.olium.model claude-opus-4-7 ``` This mode spawns a fresh `claude -p` subprocess per turn (slower than the SDK bridge above), but it's the way to apply your full personal Claude Code environment to the current directory. **Note on permissions.** vigolium invokes `claude -p` with `--permission-mode bypassPermissions` so Bash / Read / WebFetch tool calls execute without interactive approval (the wrapper is non-interactive, there's no TTY for you to confirm prompts on). This is equivalent to running `claude --dangerously-skip-permissions` and applies for the duration of the subprocess only. ### 3c. Claude OAuth (Claude Pro/Max subscribers) `claude setup-token` mints an OAuth bearer token tied to your Claude subscription. No per-token billing. ```bash theme={null} # 1. Install Claude Code, then mint a token. claude setup-token # prints sk-ant-oat01-… export ANTHROPIC_API_KEY=sk-ant-oat01- # shell rc; survives reboots # 2. Point vigolium at the OAuth provider. vigolium config set agent.olium.provider anthropic-oauth vigolium config set agent.olium.model claude-opus-4-7 # 3. Verify. vigolium ol -p 'what model are you running' ``` `anthropic-oauth` reads `agent.olium.oauth_token` first, then falls back to `$ANTHROPIC_API_KEY`. The env var is the path of least resistance. **Heads-up, enable extra usage on your Claude account.** Pro/Max subscriptions ship with the OAuth token capped to the in-app Claude Code allowance. Driving the same token from vigolium (or any third-party client) hits the Messages API directly and is rejected with `429 rate_limit_error` until you turn on **extra usage / pay-as-you-go overage** in the Anthropic Console (Settings → Billing → Usage limits). Without that toggle the verify call above will fail even with a valid token. ### 3d. Anthropic API key For users billing through the standard Anthropic API. ```bash theme={null} export ANTHROPIC_API_KEY=sk-ant-api03- vigolium config set agent.olium.provider anthropic-api-key vigolium config set agent.olium.model claude-opus-4-7 vigolium ol -p 'what model are you running' ``` *** ## 4. Custom LLM Backend (OpenAI/Anthropic Compatible API) vigolium can drive **any custom LLM backend** — a cloud gateway or a **local LLM API** — through two provider drivers that share the same `agent.olium.custom_provider` config block, differing only in the wire format they speak: * **`openai-compatible`** — endpoints that speak the **OpenAI Chat Completions** format: Ollama, LM Studio, vLLM, OpenRouter, Together, Groq, LocalAI, or any custom proxy. * **`anthropic-compatible`** — endpoints that speak the **Anthropic Messages** (`/v1/messages`) format: a self-hosted gateway or LiteLLM-style proxy fronting Claude (or other) models. Both read `custom_provider.base_url` (required), `custom_provider.model_id`, an optional `custom_provider.api_key` (empty = no auth header, for local servers), and `custom_provider.extra_headers`. Pick whichever matches your backend's wire format — everything below applies to both. This is also the driver behind the [recommended router setup](#recommended-reuse-the-coding-agent-subscription-you-already-have) at the top of the page: OmniRoute / CLIProxyAPI are just another OpenAI-compatible `base_url`, one that happens to be fronting your existing Codex / Claude / Grok logins. ### Ollama (local, no key) ```bash theme={null} ollama pull gemma4:latest ollama serve # if not already running vigolium config set agent.olium.provider openai-compatible vigolium config set agent.olium.custom_provider.base_url http://localhost:11434/v1 vigolium config set agent.olium.custom_provider.model_id gemma4:latest vigolium ol -p 'what model are you running' ``` Empty `api_key` means no `Authorization` header is sent, required for Ollama. ### OpenRouter ```bash theme={null} export OPENROUTER_API_KEY=sk-or-… vigolium config set agent.olium.provider openai-compatible vigolium config set agent.olium.custom_provider.base_url https://openrouter.ai/api/v1 vigolium config set agent.olium.custom_provider.model_id anthropic/claude-3.5-sonnet vigolium config set agent.olium.custom_provider.api_key '${OPENROUTER_API_KEY}' # Optional: OpenRouter ranking signal (shows your app on the leaderboard). vigolium config set agent.olium.custom_provider.extra_headers.add 'HTTP-Referer: https://your-site.example' vigolium config set agent.olium.custom_provider.extra_headers.add 'X-Title: vigolium' ``` ### LM Studio ```bash theme={null} vigolium config set agent.olium.provider openai-compatible vigolium config set agent.olium.custom_provider.base_url http://localhost:1234/v1 vigolium config set agent.olium.custom_provider.model_id ``` ### Anthropic-compatible gateway If your backend speaks the Anthropic **Messages API** (`/v1/messages`) rather than OpenAI Chat Completions — a self-hosted gateway or a LiteLLM-style proxy fronting Claude — switch the provider to `anthropic-compatible`. The `custom_provider` block is identical: ```bash theme={null} vigolium config set agent.olium.provider anthropic-compatible vigolium config set agent.olium.custom_provider.base_url https://my-gateway.example.com/v1 vigolium config set agent.olium.custom_provider.model_id claude-3-5-sonnet vigolium config set agent.olium.custom_provider.api_key '${MY_GATEWAY_KEY}' # optional; empty = no x-api-key header vigolium ol -p 'what model are you running' ``` `base_url` accepts a full `/v1/messages` URL, a `/v1` root (`/messages` is appended), or a bare host (`/v1/messages` is appended). Use `extra_headers` to switch to a `Bearer` scheme or pin a specific `anthropic-version` — entries are applied after the standard headers. ### Custom headers (auth, routing, observability) Some OpenAI- and Anthropic-compatible backends need extra headers, non-`Bearer` auth schemes, tenant/routing signals, request tagging for cost analytics, etc. `extra_headers` takes a list of curl-style `"Key: Value"` entries that are applied **after** the standard headers, so they can override `Authorization` (or `x-api-key`) when needed. ```bash theme={null} # Clear, then add. Each .add appends one header to the list. vigolium config set agent.olium.custom_provider.extra_headers.clear "" vigolium config set agent.olium.custom_provider.extra_headers.add 'X-Custom-ID: your-cli' vigolium config set agent.olium.custom_provider.extra_headers.add 'Authorization: Bearer custom-api-token' ``` Or edit `~/.vigolium/vigolium-configs.yaml` directly: ```yaml theme={null} agent: olium: custom_provider: extra_headers: - "X-Custom-ID: your-cli" - "Authorization: Bearer custom-api-token" # overrides the default Bearer api_key ``` Notes: * `${VAR}` references are expanded from the environment when the config is loaded, so credentials don't need to be checked into the file. * On duplicate keys the **last** entry wins (matches `http.Header.Set` semantics). * Malformed entries (no `:`) are logged at warn level and skipped, the agent keeps running. * To replace the whole list, run `.clear ""` first, then `.add` each entry. You can also pass these as one-shot overrides without touching the config: ```bash theme={null} vigolium ol \ --provider openai-compatible \ --base-url http://localhost:11434/v1 \ --model gemma4:latest \ -p 'hello' ``` Every agent subcommand (`olium`, `query`, `swarm`, `autopilot`, `triage`) takes the same override set: `--provider`, `--model`, `--base-url`, `--llm-api-key`, `--oauth-cred`, `--oauth-token`. For the two Vertex providers there are two more: ```bash theme={null} vigolium ol --provider anthropic-vertex \ --gcp-project my-gcp-project --gcp-location us-east5 \ --oauth-cred ~/keys/vertex-sa.json -p 'hello' ``` `--gcp-project` falls back to `$GOOGLE_CLOUD_PROJECT`, then `agent.olium.google_cloud_project`, then the service-account file's own `project_id`. `--gcp-location` falls back to `$GOOGLE_CLOUD_LOCATION`, then `agent.olium.google_cloud_location`, then `us-central1`. `--oauth-cred` points at the service-account JSON (or set `$GOOGLE_APPLICATION_CREDENTIALS`). `extra_headers` has no CLI flag, set it once in YAML (or via `config set ... .add`) and it sticks across runs. **Tool-calling caveat.** OpenAI-style function tools are part of the wire format but only some models actually emit them. `gemma4`, `qwen2.5-coder`, `llama3.1-instruct`, and `mistral-nemo` work well. Smaller models often ignore tool definitions and reply in prose, if the agent never calls tools, switch model. *** ## 5. Vigolium audit, source-code driver `vigolium agent audit` runs a whitebox source-code audit. The harness (agents, commands, skills) ships **embedded in the vigolium binary**, no extra install. It drives the `claude` or `codex` CLI under the hood (resolved from `agent.olium.provider`), so you need a working Claude *or* Codex setup from [section 2](#2-codex-cheapest-with-a-chatgpt-subscription) or [section 3](#3-claude-anthropic). ```bash theme={null} # 1. Make sure `claude` (or `codex`) is installed and authenticated. claude --version claude -p 'hello' # sanity check # 2. Run an audit. vigolium agent audit --source ~/src/your-app # 3. Or wire it into autopilot/swarm so it runs automatically when --source is set. vigolium config set agent.audit.enable true vigolium config set agent.audit.mode lite # lite | balanced | deep vigolium agent autopilot -t https://example.com --source ~/src/your-app ``` Audit modes: `lite` (3 phases, CI-friendly), `balanced` (9 phases, default for `--audit=balanced`), `deep` (12 phases, full audit). All produce findings under the same parser/schema as native scanner output and are ingested into the vigolium DB. Findings land under `~/.vigolium/agent-sessions//vigolium-results/`. See [Agentic Security Audit](/getting-started/agentic-security-audit) for the full reference. *** ## 6. Piolium audit, Pi-native driver `vigolium agent audit --driver=piolium` runs a separate, more thorough audit (17 phases at `deep`) via the **Pi coding-agent runtime**. Unlike vigolium-audit, piolium is **not** embedded, you install it once and vigolium drives the `pi` binary. ```bash theme={null} # 1. Install Pi runtime. bun install -g @earendil-works/pi-coding-agent pi --version # 2. Install the piolium extension. pi install git:git@github.com:vigolium/piolium.git pi list # verify "piolium" appears # 3. Configure pi's default provider (the audit subprocess uses pi's own auth, # not vigolium's). Example with Anthropic: pi login # or: pi /login # 4. Run an audit. vigolium agent audit --driver=piolium --source ~/src/your-app # balanced (default) vigolium agent audit --driver=piolium --source ~/src/your-app --mode lite # quick triage vigolium agent audit --driver=piolium --source ~/src/your-app --intensity deep # full 17-phase # 5. Override pi's provider/model just for this run if you want. vigolium agent audit --driver=piolium --source ~/src/your-app \ --pi-provider vertex-anthropic --pi-model claude-opus-4-6 ``` Vigolium runs a one-turn preflight against pi before the audit to catch auth/quota errors early. If preflight fails you'll see the upstream error (e.g. `No API key found for google-vertex. Use /login to log into a provider`) and the audit won't start. By default vigolium uses pi's per-user install at `~/.pi/agent`. To use a system-wide install instead, export `PIOLIUM_HOME=/opt/piolium` (or any other path). See [Agentic Security Audit](/getting-started/agentic-security-audit) for modes, intensity presets, and the full flag reference. ### vigolium-audit vs piolium | | Vigolium Audit | Piolium | | -------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | Install | Embedded, zero setup | Requires `pi` + `pi install …` | | Driver | `claude` or `codex` CLI | `pi --mode json -p /piolium-` | | Modes | lite (3), balanced (9), deep (12), revisit, confirm, merge, diff, longshot, reinvest, refresh, mock | lite (4), balanced (9), deep (17), revisit, confirm, merge, diff, longshot, smoke | | Provider | Whatever `claude` / `codex` is configured with | Whatever `pi` is configured with (separate from olium) | | Best for | "I want a source audit, no extra setup" | "I want the most thorough audit available" | You can also run both side-by-side with `vigolium agent audit --driver both --source …`, that dispatches vigolium-audit then piolium under a single parent scan with project-wide deduplication. *** ## 7. Verifying the full stack After whichever sections you set up, run these in order. Each one fails fast with a useful error if a piece is missing: ```bash theme={null} # Olium: one prompt, one provider call. No DB, no scan. vigolium ol -p 'hello' # Agent query: same path the engine takes for source-code review. vigolium agent query -p 'list every route in this repo' --source . # Autopilot smoke test (target-only, no source): vigolium agent autopilot -t https://example.com --intensity quick --max-duration 5m # Vigolium audit (requires claude or codex installed): vigolium agent audit --source . --mode lite # Piolium audit (requires pi + piolium installed): vigolium agent audit --driver=piolium --source . --mode lite ``` If any of these errors out, the message points at the missing piece, usually an unset env var, a wrong `agent.olium.provider`, or a missing binary. *** ## Where to go next * [Olium Agent](/agentic-scan/olium), what olium is and what its tools do. * [Autopilot](/agentic-scan/autopilot), autonomous scanning. * [Swarm](/agentic-scan/swarm), guided multi-phase scanning. * [Agentic Security Audit](/getting-started/agentic-security-audit), vigolium-audit + piolium reference. * [Configuration Reference](/getting-started/configuration), every config knob with inline docs. # Web UI & Vigolium Server Source: https://docs.vigolium.com/getting-started/web-ui Start Vigolium's server with vigolium server to run the built-in Web UI, back the Burp Suite extension, and expose a REST API for viewing results and triggering scans. `vigolium server` starts the Vigolium server: one process that serves the built-in **Web UI**, backs the **Burp Suite extension**, and exposes a full **REST API**. It's the hub every non-CLI surface talks to — the UI, Burp, and any script you write all read and write the same local database. ```bash theme={null} vigolium server ``` By default, the server listens on `http://localhost:9002`. Open that URL in your browser to use the dashboard. Vigolium Web UI dashboard with project summary, scan data, and severity breakdown ## Ways to run the server The server takes flags for authentication, binding, ingestion, and passive-only analysis. Common setups: ```bash theme={null} # Recommended: start with API-key authentication export VIGOLIUM_API_KEY=my-secret-key vigolium server # Bind to a specific host and port vigolium server --host 127.0.0.1 --service-port 9002 # Back the Burp extension and also mirror ingested traffic to disk as files vigolium server --mirror-fs ./mirror # Passive-only ingestion — analyze browsed traffic without sending active requests vigolium server --scan-on-receive --passive-only # Merge live Burp Proxy history over the loopback bridge vigolium server --burp-bridge-url http://127.0.0.1:9009 # Local development only — DISABLE authentication (see warning below) vigolium server -A ``` **`-A` / `--no-auth` runs the server with no authentication.** Every `/api/*` route — including endpoints that trigger scans and read stored findings and traffic — becomes callable by anyone who can reach the port. Only use `-A` on `localhost` for local development, and **never expose an `-A` server to the internet or an untrusted network.** For shared, remote, or production use, always start with `VIGOLIUM_API_KEY` (or `server.auth_api_key` in config) set and bind to a trusted interface. The default bind address is `0.0.0.0`, so if you must reach the server from another machine, keep authentication on and prefer restricting the bind with `--host` plus a firewall or reverse proxy. Retrieve the active key at any time: ```bash theme={null} vigolium config ls server.auth_api_key --force ``` ## What you can do in the Web UI The Web UI gives you a visual workspace for the data Vigolium stores in its local database. Use it when you want to explore results, tune scans, or work across projects without staying in the terminal. * View project-level scan summaries and severity breakdowns. * Browse, search, and filter findings. * Open finding details with request, response, payload, and evidence context. * Review HTTP records collected from CLI scans, API ingestion, or proxy ingestion — filter them by source (scan, ingestion, proxy) with the records source dropdown, and copy any request as a ready-to-run `curl` command. * See available scanner modules, including active and passive modules. * Start new scans and monitor scan progress. * Modify scope and scanner configuration from the server-backed config surface. Vigolium Web UI findings list with filters, severity labels, and searchable results Vigolium Web UI finding detail with request and response evidence ## Start scans from the dashboard Use the scan controls to launch a background scan against a URL, imported traffic, or records already stored in the project. The Web UI sends the request to the same server API that powers `vigolium scan`, so the scan output lands in the same database and appears in the findings views as soon as results are written. When starting a scan, you can tune common options such as: * Target URL or stored HTTP records. * Scan strategy and intensity. * Specific modules or module tags. * Scope settings. * Active/passive module behavior. Vigolium Web UI scan history and progress view ## Use it with the Burp Suite extension `vigolium server` is also the local backend for the **Vigolium Burp extension** — running the server is the setup step for using Burp with Vigolium. The extension connects to your running server, forwards traffic through `/api/ingest-http`, and launches native and agentic scans from Burp's context menus, so findings appear in both Burp and the Web UI because everything is stored in the same database. ```bash theme={null} # Start a server for the Burp extension to connect to export VIGOLIUM_API_KEY=my-secret-key vigolium server ``` Point the extension at the **Server URL** (`http://127.0.0.1:9002`) and paste in the API key — then browse, forward proxy traffic, and dispatch scans straight from Burp. Install the extension, forward proxy traffic, dispatch native and agentic scans, and use the bidirectional live bridge — see the dedicated guide. The same workflows from Caido: install `caido-vigolium.zip`, forward proxied traffic, dispatch scans from the palette, and drive the same live bridge with `--caido-bridge-url`. ## REST API for results and scans Everything the Web UI and the Burp extension do is backed by a documented REST API, so you can drive Vigolium from scripts, CI, or a coding agent. All `/api/*` routes require `Authorization: Bearer ` unless the server was started with `-A`. **View results:** ```bash theme={null} # List findings (filter by severity, project, scan, and more) curl -s http://localhost:9002/api/findings \ -H "Authorization: Bearer my-secret-key" | jq . # Inspect stored HTTP records (traffic) curl -s http://localhost:9002/api/http-records \ -H "Authorization: Bearer my-secret-key" | jq . # Project-level statistics and severity counts curl -s http://localhost:9002/api/stats \ -H "Authorization: Bearer my-secret-key" | jq . ``` **Trigger scans:** ```bash theme={null} # Native scan of a single URL curl -s -X POST http://localhost:9002/api/scan-url \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/api/users?id=1" }' # Native scan of one or more targets (equivalent to vigolium scan -t ) curl -s -X POST http://localhost:9002/api/scans/run \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "targets": ["https://example.com"] }' # Agentic (AI-guided) scan of stored or supplied traffic curl -s -X POST http://localhost:9002/api/agent/run/swarm \ -H "Authorization: Bearer my-secret-key" \ -H "Content-Type: application/json" \ -d '{ "target": "https://example.com" }' ``` The API also covers ingestion, scope, projects, modules, config, OAST interactions, and scan status/history. See the [API overview](/api-references/overview) for the full surface, and the [Scan](/api-references/scan), [Findings](/api-references/findings), and [HTTP Records](/api-references/http-records) references for request/response fields. A live Swagger UI is served at `http://localhost:9002/swagger/` while the server runs. ## Review available modules The dashboard exposes the module registry so you can see what Vigolium can test before launching a scan. Use module search and filters to find checks by vulnerability class, technology, resource cost, or module type. For CLI parity, you can list modules from the terminal too: ```bash theme={null} vigolium module ls vigolium module ls xss # positional filter: id / name / description / tag vigolium module ls --type active ``` You can also query modules through the server API: ```bash theme={null} curl -s http://localhost:9002/api/modules \ -H "Authorization: Bearer my-secret-key" | jq . ``` See [Modules reference](/native-scan/modules-reference) for scanner module concepts and [Modules API](/api-references/modules) for API fields. ## Adjust configuration Configuration changes made through the Web UI use the same server configuration model described in the CLI docs. Use the UI when you want to adjust scan behavior interactively, then keep durable settings in `~/.vigolium/vigolium-configs.yaml`. Common configuration tasks include: * Updating scope rules. * Changing concurrency, rate limits, and per-host limits. * Enabling or disabling module groups. * Adjusting CORS or server behavior. * Reviewing project-level data separation. For complete configuration details, see [Configuration](/getting-started/configuration) and [Running the Server](/server-mode/running-the-server). # Authenticated Scanning Source: https://docs.vigolium.com/native-scan/authentication Configure multi-session authenticated scanning with login flows, IDOR/BOLA testing, and token refresh. Vigolium supports multi-session authenticated scanning via the `--auth` and `--auth-file` flags. This enables scanning behind login walls and detecting authorization bypass vulnerabilities (IDOR/BOLA). The `--auth` / `--auth-file` flags were previously named `--session` / `--session-file`, and a single bundle file holding multiple sessions used the separate `--auth-config` flag. The old names still work as deprecated aliases (`--session` → `--auth`, `--session-file` → `--auth-file`), and `--auth-file` now also accepts the multi-session `sessions:` bundle that used to require `--auth-config`. Update your commands to the new names. Session config files can be written in **YAML or JSON**, the format is auto-detected by file extension (`.json`) or by content sniffing (leading `{` or `[`). Separate from configured sessions, the browser crawler can also try a short list of common **default credentials** (`admin:admin`, …) against a confirmed local login form during spidering, so the crawl reaches authenticated areas without an `--auth` session. It's off by default and auto-enabled at `balanced`/`deep` intensity, negative-control gated, and never a brute-force. See [Default-credential login attempts](/native-scan/phases/spidering#default-credential-login-attempts). When `--spider` runs, any session the browser establishes — cookies from a cleared WAF/bot challenge plus its User-Agent — is **carried forward** into the discovery and scanning phases (per host), so later probes reuse the cleared session. It's on by default; disable with `--no-carry-browser-session`, and your own `-H/--header` overrides always win. See [Session carry-forward](/native-scan/phases/spidering#session-carry-forward). ## Quick Start ```bash theme={null} # Inline session (simplest) vigolium scan https://app.com --auth "admin:Cookie:session_id=abc123" # Single-session file (YAML or JSON) vigolium scan https://app.com --auth-file ./admin-session.yaml vigolium scan https://app.com --auth-file ./admin-session.json # Multi-session bundle file (sessions: key, YAML or JSON) vigolium scan https://app.com --auth-file ./auth-config.yaml vigolium scan https://app.com --auth-file ./auth-config.json ``` ## Authentication Flags | Flag | Description | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `--auth` | Inline session in `name:Header:value` format. Repeatable. | | `--auth-file` | Path to an auth file (YAML or JSON). Accepts either a single session or a `sessions:` bundle with multiple definitions. Repeatable. | Both flags can be combined. If no session is explicitly marked as `primary`, the first session loaded is used as the primary. ## Session Roles Each session has a **role** that determines how it is used during the scan: * **`primary`**: The main session. Used for discovery, spidering, and as the default requester during the audit phase. There should be exactly one primary session. * **`compare`**: Comparison sessions for IDOR/BOLA testing. During the audit phase, every request made by the primary session is replayed with each compare session's credentials. If a compare session can access resources it shouldn't, the `authz-compare` module flags it. ## Inline Sessions The `--auth` flag accepts sessions in `name:Header:value` format: ```bash theme={null} # Single session with a cookie vigolium scan https://app.com --auth "admin:Cookie:session_id=abc123" # Bearer token vigolium scan https://app.com --auth "user1:Authorization:Bearer eyJhbGciOi..." # Multiple sessions for IDOR testing vigolium scan https://app.com \ --auth "admin:Cookie:session=admin_token" \ --auth "regular:Cookie:session=user_token" ``` Values containing colons are handled correctly, only the first two colons are used as delimiters. ## Session Files For sessions with multiple headers or login flows, use files. Both YAML and JSON formats are supported. ### Static Headers **YAML:** ```yaml theme={null} name: admin role: primary headers: Cookie: "session_id=abc123" Authorization: "Bearer mytoken" ``` **JSON:** ```json theme={null} { "name": "admin", "role": "primary", "headers": { "Cookie": "session_id=abc123", "Authorization": "Bearer mytoken" } } ``` Use with: ```bash theme={null} vigolium scan https://app.com --auth-file ./admin-session.yaml vigolium scan https://app.com --auth-file ./admin-session.json ``` Session files are resolved from the configured `session_dir` (default `~/.vigolium/sessions/`) if the path is not absolute. See [Session Strategy Configuration](#session-strategy-configuration) below. ### Login Flows Session files can define automated login flows. The scanner executes the login request at scan start and extracts credentials from the response. **YAML:** ```yaml theme={null} name: admin role: primary login: url: "https://app.com/api/auth/login" method: POST content_type: "application/json" body: '{"username":"${ADMIN_USER}","password":"${ADMIN_PASS}"}' extract: - source: json path: "$.token" apply_as: "Authorization: Bearer {value}" ``` **JSON:** ```json theme={null} { "name": "admin", "role": "primary", "login": { "url": "https://app.com/api/auth/login", "method": "POST", "content_type": "application/json", "body": "{\"username\":\"${ADMIN_USER}\",\"password\":\"${ADMIN_PASS}\"}", "extract": [ { "source": "json", "path": "$.token", "apply_as": "Authorization: Bearer {value}" } ] } } ``` ### Extraction Sources | Source | Description | Example | | -------- | --------------------------------------------------------------------------------------- | ---------------------- | | `json` | Extract a value from the JSON response body using dot-notation. | `path: "$.token"` | | `cookie` | Extract cookies from `Set-Cookie` response headers. Omit `name` to extract all cookies. | `name: "session_id"` | | `header` | Extract a value from a response header. | `name: "X-Auth-Token"` | The `apply_as` field defines how the extracted value is applied as a request header. Use `{value}` as a placeholder. ## Multi-Session Bundle File A bundle file defines all sessions in one place under a `sessions` key, and is loaded with `--auth-file` (this is what the removed `--auth-config` flag used to do). ### YAML Format ```yaml theme={null} sessions: # Primary session: JSON API login - name: admin role: primary login: url: "https://app.com/api/auth/login" method: POST content_type: "application/json" body: '{"username":"${ADMIN_USER}","password":"${ADMIN_PASS}"}' extract: - source: json path: "$.token" apply_as: "Authorization: Bearer {value}" # Compare session: form-based login - name: regular_user role: compare login: url: "https://app.com/login" method: POST content_type: "application/x-www-form-urlencoded" body: "username=${USER_NAME}&password=${USER_PASS}" extract: - source: cookie # Compare session: static API key (no login needed) - name: api_key_user role: compare headers: X-API-Key: "${API_KEY}" ``` ### JSON Format ```json theme={null} { "sessions": [ { "name": "admin", "role": "primary", "login": { "url": "https://app.com/api/auth/login", "method": "POST", "content_type": "application/json", "body": "{\"username\":\"${ADMIN_USER}\",\"password\":\"${ADMIN_PASS}\"}", "extract": [ { "source": "json", "path": "$.token", "apply_as": "Authorization: Bearer {value}" } ] } }, { "name": "regular_user", "role": "compare", "login": { "url": "https://app.com/login", "method": "POST", "content_type": "application/x-www-form-urlencoded", "body": "username=${USER_NAME}&password=${USER_PASS}", "extract": [ { "source": "cookie" } ] } }, { "name": "api_key_user", "role": "compare", "headers": { "X-API-Key": "${API_KEY}" } } ] } ``` Use with: ```bash theme={null} vigolium scan https://app.com --auth-file ./auth-config.yaml vigolium scan https://app.com --auth-file ./auth-config.json ``` ### When to Use JSON JSON is a good choice when: * **AI agents generate session configs**: most LLMs produce cleaner JSON than YAML, and agent modes (swarm, autopilot) already output session config as JSON natively. * **Programmatic generation**: scripts, CI pipelines, or tools that build session configs are often simpler in JSON. * **Embedding in other JSON payloads**: e.g., the REST API `POST /api/agent/run/swarm` body includes session config as a nested JSON object. YAML remains convenient for hand-written configs where comments and multi-line strings help readability. ### Format Detection The format is detected automatically: 1. **File extension**: `.json` files are parsed as JSON; `.yaml` / `.yml` as YAML. 2. **Content sniffing**: if the extension is ambiguous (or missing), content starting with `{` or `[` (after whitespace trimming) is parsed as JSON. 3. **Fallback**: everything else is parsed as YAML. This means extensionless files work too, pipe JSON directly and it will be detected: ```bash theme={null} # Generate config from a script, write to a temp file, scan ./gen-auth-config.sh > /tmp/auth-config vigolium scan https://app.com --auth-file /tmp/auth-config ``` ## Session Config Schema Reference Both YAML and JSON use the same field names. Here is the full schema: ``` SessionConfig ├── sessions[] # Array of session definitions │ ├── name # (string, required) Unique session name │ ├── role # (string) "primary" or "compare" │ ├── headers # (map) Static headers, e.g. {"Cookie": "sid=abc"} │ ├── login # (object) Automated login flow │ │ ├── url # (string, required) Login endpoint URL │ │ ├── method # (string, required) HTTP method (POST, GET, etc.) │ │ ├── content_type # (string) Request Content-Type │ │ ├── body # (string) Request body │ │ └── extract[] # (array, required) Credential extraction rules │ │ ├── source # (string) "json", "cookie", or "header" │ │ ├── name # (string) Cookie/header name to extract │ │ ├── path # (string) JSONPath for json source │ │ └── apply_as # (string) Header template, e.g. "Authorization: Bearer {value}" │ └── login_request # (string) Raw HTTP request for login (alternative to login) ``` Only one of `headers`, `login`, or `login_request` can be set per session. ## Managing Sessions with the `auth` Command The `--auth`/`--auth-file` flags load sessions for a single scan. The `vigolium auth` subcommand manages reusable, persisted sessions and provides helpers for validating configs and automating 2FA. | Command | Purpose | | -------------------- | ------------------------------------------------------------------------------ | | `vigolium auth list` | Print every session stored for the active project. | | `vigolium auth load` | Persist sessions from a file, stdin, or a raw login request into the database. | | `vigolium auth lint` | Validate a session config file for errors and warnings before you use it. | | `vigolium auth totp` | Generate an RFC 6238 TOTP code for scripting 2FA login flows. | ### List stored sessions ```bash theme={null} vigolium auth list # Filter to a single host vigolium auth list --host app.example.com ``` Each row shows hostname, session name, role (primary/compare), position, a token preview, and extract rules. Sessions are scoped to the active project. ### Load sessions into the database `auth load` persists sessions so subsequent scans pick them up automatically. It accepts a native config file, an agent-produced `session-config.json`, stdin, or even a raw HTTP login request (which it sends, then auto-discovers tokens from the JSON body, `Set-Cookie`, and auth headers): ```bash theme={null} # From a YAML/JSON session config vigolium auth load ./auth-config.yaml # From stdin cat auth-config.json | vigolium auth load - # From a raw login request — tokens auto-discovered from the response vigolium auth load ./login-request.txt --host app.example.com --name admin # Skip the validation login round-trip vigolium auth load ./auth-config.yaml --no-validate ``` By default, `load` executes each login flow once to verify the credentials work; pass `--no-validate` to skip. Agent format is auto-detected when the path contains `agent-sessions/` (or force it with `--agent-format`). The hostname is derived from the first login URL when `--host` is omitted. ### Lint a session config Catch mistakes before a scan — missing/invalid fields, bad extract rules, unknown login or extract types, multiple primary sessions, or duplicate names: ```bash theme={null} vigolium auth lint ./auth-config.yaml # Or from stdin cat auth-config.json | vigolium auth lint --stdin ``` ### Generate a TOTP code for 2FA For automating two-factor login flows, `auth totp` turns a base32 secret into the current code (JSON output, `{"code": "...", "expires_in": }`): ```bash theme={null} vigolium auth totp --secret JBSWY3DPEHPK3PXP # {"code":"123456","expires_in":18} # Inject the code into a login body CODE=$(vigolium auth totp --secret "$TOTP_SECRET" | jq -r .code) ``` ## Environment Variables Session files (both YAML and JSON) support `${VAR}` syntax for secrets. This keeps credentials out of config files: ```bash theme={null} export ADMIN_USER=admin export ADMIN_PASS=s3cret vigolium scan https://app.com --auth-file ./auth-config.json ``` All `${VAR}` references are expanded from the environment at load time, before format parsing. ## IDOR/BOLA Testing To test for authorization bypass vulnerabilities, define at least two sessions, one primary and one or more compare sessions. **YAML:** ```yaml theme={null} sessions: - name: admin role: primary headers: Cookie: "${ADMIN_SESSION_COOKIE}" - name: regular_user role: compare headers: Cookie: "${USER_SESSION_COOKIE}" # Optional: unauthenticated session - name: unauthenticated role: compare ``` **JSON:** ```json theme={null} { "sessions": [ { "name": "admin", "role": "primary", "headers": { "Cookie": "${ADMIN_SESSION_COOKIE}" } }, { "name": "regular_user", "role": "compare", "headers": { "Cookie": "${USER_SESSION_COOKIE}" } }, { "name": "unauthenticated", "role": "compare" } ] } ``` The built-in `authz-compare` module automatically activates when compare sessions are present. It replays primary session requests with compare session credentials and flags responses that indicate broken access control. ### How Detection Works 1. The primary session makes a request and gets a response (e.g., `GET /api/users/42` -> 200 OK with user data). 2. The same request is replayed with each compare session's credentials. 3. If a compare session also receives a successful response with similar content, the module reports a potential IDOR/BOLA finding with **High** severity. ### Filtering to Auth Modules Only To run only authorization testing without other active modules: ```bash theme={null} vigolium scan https://app.com \ --auth-file ./auth-config.json \ --module-tag access-control ``` ## How Sessions Affect Scan Phases | Phase | Session Usage | | --------------------- | ---------------------------------------------------------------------------------------------------------- | | Discovery / Spidering | Primary session only (controlled by `use_in_discovery`) | | Audit | Primary session for main scanning; compare sessions for IDOR/BOLA replay (controlled by `compare_enabled`) | ## Session Strategy Configuration Session behavior is configured under `scanning_strategy.session` in `vigolium-configs.yaml` (see `public/vigolium-configs.example.yaml` for the full annotated example). ```yaml theme={null} scanning_strategy: session: # Directory where session files are stored. # When --auth-file receives a bare name (e.g. "myapp"), the scanner # resolves it as /myapp.yaml (or .yml, .json). # Default: ~/.vigolium/sessions/ session_dir: ~/.vigolium/sessions/ # Apply primary session headers during discovery and spidering phases. # When false, those phases run unauthenticated and credentials are only # used during the audit phase. # Default: true use_in_discovery: true # Enable cross-session IDOR/BOLA replay with compare sessions. # When true and multiple sessions are defined, the authz-compare module # replays primary-session requests with each compare session's credentials. # When false, compare sessions are ignored even if defined. # Default: true compare_enabled: true # Re-execute login flows at this interval to refresh expiring tokens. # Format: Go duration string (e.g. "15m", "1h", "30m"). # Default: "" (disabled — login once at scan start) reauth_interval: "" # Trigger reactive re-authentication when the primary session receives # one of these HTTP status codes. The login flow is re-executed immediately # and the failed request is retried. # Default: [] (disabled) reauth_on_status: [] # URL to GET after login to verify that extracted credentials work. # The scanner checks for a 2xx response before proceeding. # Can be a relative path (resolved against the target) or absolute URL. # Default: "" (disabled) validate_url: "" ``` ### Field Reference | Field | Type | Default | Description | | ------------------ | -------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `session_dir` | string | `~/.vigolium/sessions/` | Directory for session file lookup. `--auth-file myapp` resolves to `/myapp.yaml` (tries `.yaml`, `.yml`, `.json` in order). Supports `~` expansion. | | `use_in_discovery` | bool | `true` | When `true`, the primary session's headers are injected into the requester used for discovery and spidering. When `false`, those phases run unauthenticated, useful for mapping the public attack surface first, then scanning authenticated. | | `compare_enabled` | bool | `true` | When `true`, compare sessions are created and the `authz-compare` module is activated for IDOR/BOLA testing. When `false`, compare sessions are ignored even if defined, handy when you only need authenticated scanning without authorization comparison. | | `reauth_interval` | duration | `""` (disabled) | Go duration string (e.g. `"15m"`, `"1h"`). When set, login flows are re-executed at this interval to refresh tokens that expire mid-scan. | | `reauth_on_status` | \[]int | `[]` (disabled) | HTTP status codes that trigger reactive re-authentication. When the primary session receives one of these codes, its login flow is re-executed immediately and the request is retried. | | `validate_url` | string | `""` (disabled) | Relative or absolute URL to GET after login. The scanner checks for a 2xx response to confirm credentials are working before proceeding. Catches bad credentials early. | ### Session Directory Resolution When `--auth-file` receives a bare name (no path separators), the scanner resolves it from `session_dir`. Extensions are tried in order: `.yaml`, `.yml`, `.json`. ```bash theme={null} # These are all equivalent when session_dir is ~/.vigolium/sessions/ vigolium scan https://app.com --auth-file myapp vigolium scan https://app.com --auth-file ~/.vigolium/sessions/myapp.yaml vigolium scan https://app.com --auth-file ~/.vigolium/sessions/myapp.json ``` If the bare name has no matching file with any extension, `.yaml` is appended as the default. Absolute paths and relative paths with directory separators (e.g. `./sessions/myapp.json`) bypass `session_dir` and are used as-is. To change the lookup directory: ```yaml theme={null} scanning_strategy: session: session_dir: /opt/vigolium/shared-sessions/ ``` ### Common Patterns **Unauthenticated discovery, authenticated scanning:** ```yaml theme={null} scanning_strategy: session: use_in_discovery: false ``` Crawls the public-facing site first, then applies session headers only during the audit phase. This is useful when you want to see what an unauthenticated attacker can discover before testing the authenticated surface. **Authenticated scanning without IDOR testing:** ```yaml theme={null} scanning_strategy: session: compare_enabled: false ``` Useful when you only need to scan behind a login wall but don't have multiple user roles to compare. The primary session's credentials are applied to all phases, but no compare requesters are created and the `authz-compare` module stays inactive. **Long-running scan with token refresh:** ```yaml theme={null} scanning_strategy: session: reauth_interval: "30m" reauth_on_status: [401, 403] validate_url: "/api/whoami" ``` Re-executes login flows every 30 minutes proactively, and also reactively when a 401 or 403 is received. The `validate_url` confirms credentials work after each login before resuming scanning. **Team shared sessions directory:** ```yaml theme={null} scanning_strategy: session: session_dir: /shared/team/vigolium-sessions/ ``` Point all team members to a shared directory so `--auth-file staging-admin` resolves the same file for everyone. Scanning profiles (`~/.vigolium/profiles/`) can also override session strategy values, useful for having a "quick unauthenticated" profile alongside a "deep authenticated" profile. ## Using Session Config with Agent Modes Agent modes (`swarm`, `autopilot`) can auto-generate session configs from source code analysis. The generated configs are always written as JSON to the session directory. When running agent swarm with `--source`, the source-analysis phase discovers authentication flows in the codebase and produces a `session-config.json` and `auth-config.yaml` in the session directory. This config is then fed into subsequent scan phases automatically. Swarm accepts a pre-built bundle via its own `--auth-config` flag (it skips browser/CLI auth synthesis and uses the file verbatim). Autopilot does not take a pre-built file. For both autopilot and swarm, put credentials, roles, login URL, and browser requirements in `--prompt` (or the positional prompt); browser tooling is always available and the intent parser extracts the auth signals: ```bash theme={null} # Swarm with a pre-built auth-config bundle vigolium agent swarm \ --target https://app.com \ --auth-config ./auth-config.json # Autopilot prepares auth from the task prompt during preflight vigolium agent autopilot \ --target https://app.com \ --prompt "Log in with admin/admin123 and user/user123. Compare both roles for IDOR." ``` The REST agent endpoints still accept structured `credentials`, `credential_sets`, `auth_required`, `requires_browser`, `browser_start_url`, and `focus_routes` fields. Their removal applies to the autopilot/swarm CLI flags only. ## Examples ### Scan a REST API with Bearer Token ```bash theme={null} vigolium scan https://api.example.com \ --auth "admin:Authorization:Bearer eyJhbG..." ``` ### Scan with Cookie-Based Auth ```bash theme={null} vigolium scan https://app.example.com \ --auth "user:Cookie:PHPSESSID=abc123; csrftoken=xyz" ``` ### Full IDOR Test with Login Automation (YAML) ```bash theme={null} export ADMIN_USER=admin ADMIN_PASS=admin123 export USER_NAME=user1 USER_PASS=user123 vigolium scan https://app.example.com \ --auth-file ./auth-config.yaml \ --module-tag access-control ``` ### Full IDOR Test with Login Automation (JSON) ```bash theme={null} export ADMIN_USER=admin ADMIN_PASS=admin123 export USER_NAME=user1 USER_PASS=user123 vigolium scan https://app.example.com \ --auth-file ./auth-config.json \ --module-tag access-control ``` ### Combine with Other Scan Options Auth flags work with all other scan options: ```bash theme={null} vigolium scan https://app.example.com \ --auth-file ./auth-config.json \ --strategy blackbox \ --only audit \ --concurrency 10 \ --format html -o report.html ``` ### One-Liner JSON Auth Config For quick testing or CI scripts, you can write a JSON config inline: ```bash theme={null} echo '{"sessions":[{"name":"admin","role":"primary","headers":{"Authorization":"Bearer '"$TOKEN"'"}}]}' > /tmp/auth.json vigolium scan https://app.com --auth-file /tmp/auth.json ``` ### Agent-Generated Session Config When an AI agent discovers auth flows in source code, it produces JSON like: ```json theme={null} { "sessions": [ { "name": "default_user", "role": "primary", "login": { "url": "https://app.com/api/login", "method": "POST", "content_type": "application/json", "body": "{\"email\":\"test@test.com\",\"password\":\"testpassword\"}", "extract": [ { "source": "json", "path": "$.token", "apply_as": "Authorization: Bearer {value}" } ] } } ] } ``` This can be saved and reused across scans: ```bash theme={null} vigolium scan https://app.com --auth-file ./agent-generated-auth.json ``` # Anatomy of a Scan Source: https://docs.vigolium.com/native-scan/how-it-works Traces the complete lifecycle of an HTTP request through a Vigolium scan, from CLI invocation to vulnerability finding. This document traces the complete lifecycle of an HTTP request through a Vigolium scan, from `vigolium scan -t https://example.com` on the command line to a vulnerability finding written to the terminal. It is an architecture deep-dive intended for contributors who want to understand the scanning pipeline end-to-end. ## High-Level Pipeline ``` CLI invocation │ ▼ ┌──────────────────────┐ │ CLI Entry & Config │ cmd/vigolium/main.go → pkg/cli/scan.go │ Flag parsing, config│ Config loading, strategy/profile, DB init │ loading, DB init │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Input Parsing │ pkg/input/source/ │ URL/file/stdin → │ InputSource.Next() → WorkItem │ WorkItem stream │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Runner Orchestration│ internal/runner/runner.go │ 6-phase pipeline: │ Heuristics → Harvest → Discovery → │ build infra, run │ Spidering → KnownIssueScan → │ phases in order │ Dynamic-Assessment └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Executor │ pkg/core/executor.go │ Worker pool feeds │ feedItems() → worker() → processItem() │ items to modules │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Module Dispatch │ pkg/modules/ │ Passive (sequential)│ ScanPerHost → ScanPerRequest │ Active (parallel) │ ScanPerHost/Request/InsertionPoint └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Result Emission │ pkg/output/output.go │ Post-hooks → DB │ assignModuleInfo → emitResult → │ save → output write │ SaveFinding → OnResult → Notify └──────────────────────┘ ``` ## Stage 1: CLI Entry and Configuration ### Entry Point `cmd/vigolium/main.go` prints the banner (unless `--json` or certain subcommands suppress it), then calls `cli.Execute()` which invokes the Cobra root command. ### Root Command, `pkg/cli/root.go` `rootCmd.PersistentPreRunE` fires before every subcommand and: 1. Initializes the global `zap.Logger` via `initLogger()`. 2. Falls back to the `VIGOLIUM_PROXY` environment variable if `--proxy` is empty. 3. Runs first-time setup via `ensureInitialized()`, creates `~/.vigolium/` and writes the default config, profiles, prompt templates, and skills if they don't exist. 4. Handles early-exit flags: `--list-modules`, `--list-input-mode`, `--full-example`. ### Scan Command, `pkg/cli/scan.go` `runScanCmd()` is the heart of the scan flow. It performs these steps in order: 1. **Copy global flags** into `scanOpts` (`*types.Options`): targets, concurrency, timeout, modules, proxy, format, phases, etc. 2. **Reconcile `--json` and `--format`**: if `--json` is set and format is still the default `"console"`, switch to `"jsonl"`. 3. **Load config**: `config.LoadSettings(configPath)` reads `~/.vigolium/vigolium-configs.yaml`. CLI overrides are applied for origin mode, OAST URL, and database settings. Validates database, extensions, and strategy configs. 4. **Resolve scanning profile**: precedence is `--scanning-profile` flag > `settings.ScanningStrategy.ScanningProfile`. Profiles are loaded from `~/.vigolium/profiles/` or embedded presets, and applied via `config.ApplyProfile()`. 5. **Resolve scanning strategy**: precedence is `--strategy` flag > `settings.ScanningStrategy.DefaultStrategy`. Strategy determines which phases are enabled (discovery, spidering, KnownIssueScan, etc.). 6. **Resolve heuristics check level**: `--skip-heuristics` > `--heuristics-check` > config > default `"basic"`. 7. **Phase isolation**: `--only` and `--skip` are mutually exclusive. `--only ` enables a single phase and disables all others. `--skip ` disables specific phases. Phase aliases are normalized: `deparos`/`discover` → `discovery`, `spitolas` → `spidering`, `ext` → `extension`, `cve`/`kis`/`known-issues` → `known-issue-scan`, and `audit`/`dast`/`assessment` → `dynamic-assessment`. `dynamic-assessment` is the canonical name for the module-based vulnerability-scanning phase (formerly `audit`). 8. **Validate HTML output**: `--format html` requires `--output` and is only allowed with `--only discovery` or `--only spidering`. 9. **Apply scanning pace**: concurrency and max-per-host from config are applied unless explicitly set on CLI. 10. **Initialize database**: `database.NewDB()` → `CreateSchema()` → `database.NewRepository()`. 11. **Handle `--source`**: clone git URLs or resolve local paths, link source repos to targets in DB. 12. **Branch into one of three execution paths**: ``` Has --input file? ──yes──▶ runScanWithIngest() Parse file, create InputSource, run │ no ▼ Has targets? ──no───▶ runDBScan() Scan existing DB records (empty source) │ yes ▼ runner.New(scanOpts) ──────────────────▶ Target-based: build source from CLI targets .SetSettings(settings) .SetRepository(repo) .RunNativeScan() .Close() ``` ## Stage 2: Input Parsing ### The InputSource Interface, `pkg/input/source/source.go` Input sources provide a pull-based stream of work items: ```go theme={null} type InputSource interface { Next(ctx context.Context) (*work.WorkItem, error) Close() error } ``` Return conventions: `(*WorkItem, nil)` = next item, `(nil, io.EOF)` = source exhausted, `(nil, context.Canceled)` = cancelled. The optional `Countable` interface adds `Count() int64` for progress tracking. ### InputSource Implementations | Type | File | Description | Countable | | ------------------------------ | ------------------------------ | ------------------------------------------------------------------------------- | --------- | | `TargetSource` | `source.go` | Iterates CLI `-t` targets, builds GET requests via `GetRawRequestFromURL()` | Yes | | `FileSource` | `file.go` | Parses input files (OpenAPI, Burp, HAR, cURL, etc.) via format-specific parsers | Yes | | `StdinSource` | `stdin.go` | Reads URLs line-by-line from stdin | No | | `SingleSource` | `single_source.go` | Returns a single item, then EOF. Used by `scan-url`/`scan-request` | Yes (1) | | `MultiSource` | `multi.go` | Drains sub-sources sequentially in order | Yes (sum) | | `ConcurrentMultiSource` | `concurrent.go` | Reads all sub-sources concurrently. Used for queue-based sources | No | | `ExternalHarvesterInputSource` | `external_harvester_source.go` | Runs external harvesting (Wayback, CommonCrawl, etc.) | No | | `DeparosDiscoverySource` | `deparos_discovery.go` | Runs content discovery engine per target | No | `NewInputSource(cfg SourceConfig)` is the factory function. Based on the config fields, it creates `TargetSource`, `FileSource`, and/or `StdinSource`, wrapping multiple sources in a `MultiSource`. ### Supported Input Formats Resolved by `resolveFormat()` in `file.go`: | Format names | Parser | | ----------------------------- | ----------------------------------- | | `urls`, `url`, `list` | Line-delimited URLs | | `openapi`, `swagger` | OpenAPI/Swagger spec | | `wsdl`, `soap`, `svc` | WSDL 1.1 / SOAP service description | | `postman` | Postman collection | | `curl` | cURL commands | | `burpraw`, `burp-raw`, `raw` | Burp raw request files | | `burpxml`, `burp-xml`, `burp` | Burp XML export | | `nuclei`, `nuclei-output` | Nuclei JSONL output | | `deparos`, `deparos-output` | Deparos discovery output | ### The WorkItem, `pkg/work/item.go` ```go theme={null} type WorkItem struct { Request *httpmsg.HttpRequestResponse EnableModules []string // per-item module selection (empty = all) RecordUUID string // pre-existing DB record UUID (skip store) onComplete func() // queue ack callback (unexported) } ``` `Complete()` is called after processing to acknowledge queue-based sources. ## Stage 3: HTTP Types ### HttpRequestResponse, `pkg/httpmsg/http_request_response.go` The central data type flowing through the entire pipeline. It pairs an HTTP request with an optional response: ```go theme={null} type HttpRequestResponse struct { request *HttpRequest // required response *HttpResponse // optional, may be nil } ``` Key methods: `Request()`, `Response()`, `HasResponse()`, `Service()`, `URL()`, `Target()`, `ID()` (FNV-1a hash of `host:port:method`), `Clone()`, `WithResponse()`, `CreateInsertionPoints()`, `BuildRetryableRequest()`. Factory functions: * `GetRawRequestFromURL(url)`: builds a minimal GET request from a URL string (used by `TargetSource` and `StdinSource`) * `ParseRawRequest(raw)`: parses raw HTTP text * `FromStdRequest(req)`: converts a stdlib `http.Request` ### HttpRequest, `pkg/httpmsg/http_request.go` Stores the raw HTTP request bytes as the source of truth, with lazy-parsed accessors: ```go theme={null} type HttpRequest struct { raw []byte // source of truth service *Service // host/port/protocol // lazy-parsed cache (populated by ensureParsed()) method, path string headers []HttpHeader bodyOffset int parsed bool mu sync.RWMutex } ``` `ensureParsed()` is thread-safe via a double-checked RW mutex. It extracts headers, method, path, and body offset from the raw bytes. Immutable builder methods (`WithMethod()`, `WithPath()`, `WithHeader()`, `WithBody()`, etc.) return new `*HttpRequest` instances with rebuilt raw bytes. The `RequestOption` / `Apply()` batch builder pattern rebuilds raw bytes only once for multiple changes. ### HttpResponse, `pkg/httpmsg/http_response.go` Same lazy-parsing pattern as `HttpRequest`: ```go theme={null} type HttpResponse struct { raw []byte statusCode int headers []HttpHeader bodyOffset int parsed bool mu sync.RWMutex } ``` ### Service, `pkg/httpmsg/service.go` A host/port/protocol triple: ```go theme={null} type Service struct { host string // hostname only (no port) port int protocol string // "http" or "https" } ``` ## Stage 4: Runner Orchestration ### The Runner, `internal/runner/runner.go` The Runner is the high-level orchestrator. It builds shared infrastructure and executes the multi-phase scan pipeline. ```go theme={null} type Runner struct { output output.Writer options *types.Options settings *config.Settings inputSource source.InputSource dedupManager *dedup.Manager repository *database.Repository heuristicsResults map[string]*HeuristicsResult } ``` ### buildInfrastructure() Called once at the top of `RunNativeScan()`. Creates all shared services in the `phaseInfra` container: ```go theme={null} type phaseInfra struct { svc *services.Services httpRequester *http.Requester scopeMatcher *config.ScopeMatcher hostLimiter *hostlimit.HostRateLimiter notifier *notify.Manager hookChain *jsext.HookChain jsEngine *jsext.Engine scanUUID string } ``` Built in order: 1. **Notifier**: Telegram and/or Discord backends (from config or env vars). 2. **Services**: wraps Options, Notifier, DedupManager, and HostErrors (circuit breaker for unresponsive hosts). 3. **HostRateLimiter**: per-host concurrency control (CLI default 30 concurrent per host, 1000 max tracked hosts, 30s idle eviction). 4. **HTTP Requester**: HTTP client with retry, proxy, redirect, and middleware support. 5. **ScopeMatcher**: host/path/status/content-type/body-string filtering from config. 6. **JS Engine**: Grafana Sobek engine for JavaScript extensions, including pre/post hook chains. ### RunNativeScan(), The 7-Phase Pipeline ``` RunNativeScan() │ ├── buildInfrastructure() │ ├─── Phase 0: Heuristics Check [guard: heuristicsCheck != "none"] │ Probes target root pages, detects blank/JSON/SPA responses. │ Flags targets to skip spidering. │ ├─── Phase 1: External Harvest [guard: ExternalHarvestEnabled] │ Queries Wayback, CommonCrawl, AlienVault, URLScan, VirusTotal. │ Ingests discovered URLs into DB (no modules, pure ingestion). │ ├─── Phase 2: Discovery [guard: !SkipIngestion] │ Content discovery (brute-force dirs/files via deparos engine) │ + CLI input source. Both wrapped in MultiSource. │ Ingests into DB (no modules, pure ingestion). │ Fallback: seedCLITargets() if ingestion skipped but KnownIssueScan/DA need records. │ ├─── Phase 3: Spidering [guard: SpideringEnabled] │ Browser-based crawling (Chromium). Applies heuristics filter. │ Stores discovered pages in DB via repository. │ ├─── Phase 4: KnownIssueScan [guard: KnownIssueScanEnabled] │ Nuclei template scan + Kingfisher secret detection on stored │ response bodies. Targets enriched with discovered paths │ (enrich_targets). Filters out secret_detect passive module │ to avoid duplicates in DA phase. │ Post-phase: DeduplicateFindings() groups same-module/URL findings. │ └─── Phase 5: Dynamic-Assessment [guard: !SkipAudit] THE CORE SCANNING PHASE. Reads records from DB, dispatches active + passive modules. Per-module finding cap suppresses noisy modules. Feedback loop (up to 3 rounds) re-scans newly discovered URLs. Post-phase: DeduplicateFindings() merges redundant findings. ``` Phases 0-4 populate the database with HTTP records. Phase 5 reads those records back and runs the full module pipeline against them. ### Phase 5 Detail: KnownIssueScan 1. Queries distinct paths from DB via `GetDistinctPaths()`. 2. Builds target URLs, either path-enriched (default, `enrich_targets: true`) or host-level only. 3. Runs Nuclei templates + Kingfisher secret scanning against targets. 4. **Post-phase dedup**: calls `DeduplicateFindings()` to group findings with identical `(module_id, severity, matched_at URL)`. ### Phase 6 Detail: Audit 1. Creates a `database.Scan` record with cursor tracking. 2. Resolves DA concurrency from config (separate from discovery concurrency). 3. Optionally starts the OAST (out-of-band) service. 4. Runs a **feedback loop** (up to `maxFeedbackRounds = 3`): * Creates a `OneShotDBInputSource` that reads records after the scan cursor. * Builds an Executor with all active + passive modules, `SkipBaseline: true` (responses already in DB). * The Executor enforces a per-module finding cap (`MaxFindingsPerModule`, default 10), once a module emits this many findings, further results from that module are suppressed. * After each round, checks for newly created records. Breaks early if none. 5. **Post-phase dedup**: calls `DeduplicateFindings()` to merge findings where the same module fired on the same URL with different payloads. 6. Marks the scan as completed. ## Stage 5: The Executor ### Executor Struct, `pkg/core/executor.go` The Executor is the central dispatch engine. It receives work items, distributes them to a worker pool, and dispatches modules. ```go theme={null} type Executor struct { cfg ExecutorConfig source source.InputSource activeModules []modules.ActiveModule passiveModules []modules.PassiveModule httpClient *http.Requester scanCtx *modules.ScanContext hooks HookRunner // Pre-grouped by scan scope at init time perHostActive []modules.ActiveModule perRequestActive []modules.ActiveModule perIPActive []modules.ActiveModule perHostPassive []modules.PassiveModule perRequestPassive []modules.PassiveModule ipCache *lru.Cache[string, []httpmsg.InsertionPoint] // 4096-entry LRU requestUUIDs *shardedMap // request hash → DB record UUID } ``` ### Module Pre-Grouping At construction time, `NewExecutor()` pre-groups all modules by their `ScanScope` bitmask into five slices. A module declaring `ScanScopeInsertionPoint | ScanScopeRequest` appears in both `perIPActive` and `perRequestActive`. This avoids per-item scope-check iteration. ### Execute(), Worker Pool ```go theme={null} func (e *Executor) Execute(ctx context.Context) (bool, error) ``` 1. Spawns `Workers` goroutines reading from a buffered channel (`cap = Workers * 2`). 2. Calls `feedItems()` on the calling goroutine (producer loop). 3. Closes the channel, waits for all workers to drain. 4. Flushes passive modules (`Flusher` interface) and OAST service. 5. Returns `(foundResults, nil)`. ### feedItems(), The Producer For each item from `source.Next()`: 1. **Static file filter**: if path matches a static file extension (`.jpg`, `.css`, etc.), skip. 2. **Pre-request scope check**: `ScopeMatcher.InScopeRequest(host, path, "", "")`, host + path only, no HTTP round-trip. Rejects obviously out-of-scope items early. 3. **Host error check**: if `HostErrors.Check(hostID)` returns true (host has been circuit-broken), skip. 4. Send item to the worker channel. ### worker(), The Consumer Each worker goroutine loops on the channel: ```go theme={null} for item := range itemCh { e.processItem(item) item.Complete() e.statsTracker.Increment() } ``` ## Stage 6: Processing an Item `processItem()` is the per-item hot path. Every item that passes `feedItems()` goes through these steps: ### Step 1: Baseline HTTP Fetch ``` if SkipBaseline && response already attached: use existing response (DB-sourced items in audit phase) else: httpClient.Execute(request) → response copy response bytes from pool before Close() attach response to request via WithResponse() ``` Response bytes are copied from a `sync.Pool` of recycled buffers (32 KiB initial, max 1 MiB for pool return) to reduce GC pressure. ### Step 2: Traffic Callback If configured, calls `OnTraffic(method, url, statusCode, contentType)`, an observer hook for printing traffic lines to stderr. ### Step 3: Pre-Hooks ``` hooks.RunPreHooks(request) → error: log and skip item → nil return: hook filtered it out, skip item → modified request: continue with transformed request ``` Pre-hooks can inject auth headers, transform requests, or signal to skip entirely. ### Step 4: Body Size Enforcement If `ScopeMatcher` is set, checks request and response body sizes: * `BodySizeDrop` → drop item entirely. * `BodySizeTruncate` → truncate bodies to limits, continue scanning. * `BodySizeSkipScan` → truncate, save to DB, but skip scanning. ### Step 5: Scope Check + Database Save ``` if ScopeMatcher configured: check full scope (host, path, status, content types, body strings) if out-of-scope and ScopeOnIngest: drop entirely (no save, no scan) save to database if out-of-scope: saved but not scanned → return else: save to database and continue ``` `saveToDatabase()` calls `repo.SaveRecord()` and stores the returned UUID in the `requestUUIDs` sharded map (keyed by request SHA-256 hash) for later finding linkage. ### Step 6: Eligibility Pre-Computation `computeEligibility()` runs once per item (not per module): 1. Request nil check 2. URL parse check 3. Media/JS URL check (`utils.IsMediaAndJSURL`) 4. HTTP method check (skip `OPTIONS`, `CONNECT`, `HEAD`, `TRACE`) The cached `baseEligible` result lets the executor skip calling `CanProcess()` on modules that embed the standard base checks when the base would reject. ### Step 7: Module Filter If `item.EnableModules` is non-empty, builds a map-based O(1) filter. Otherwise uses the `allModulesFilter` sentinel. ### Step 8: Passive Module Execution (Sequential) ``` runPassivePerHost(request, filter) sequential loop over perHostPassive runPassivePerRequest(request, filter) sequential loop over perRequestPassive ``` For each module: check filter → check `CanProcess()` → call scan method → process results. No goroutines, passive modules do not perform network I/O. ### Step 9: Active Module Execution (Parallel) Three categories run in parallel via `conc.WaitGroup`: ``` var g conc.WaitGroup g.Go(func() { runActivePerHost(request, filter, eligibility) }) g.Go(func() { runActivePerRequest(request, filter, eligibility) }) g.Go(func() { runActivePerInsertionPoint(request, filter, eligibility) }) g.Wait() ``` Within each category, eligible modules also run concurrently (inner `conc.WaitGroup`). For the insertion-point category specifically, insertion points are iterated **serially** (one at a time), but all eligible modules for a given point run **concurrently**: ``` insertion points = ipCache.GetOrCompute(requestHash) for each insertionPoint: for each eligible module (parallel): module.ScanPerInsertionPoint(request, insertionPoint, httpClient, scanCtx) ``` ### Concurrency Model Summary ``` Execute() ├── feedItems() [calling goroutine, producer] └── Workers goroutines [consumer pool] └── processItem() ├── Passive modules [sequential on worker goroutine] │ ├── runPassivePerHost │ └── runPassivePerRequest └── Active modules [3-way parallel via conc.WaitGroup] ├── runActivePerHost [inner parallel: all modules] ├── runActivePerRequest [inner parallel: all modules] └── runActivePerInsertionPoint └── for each IP (serial) [inner parallel: all modules] ``` ## Stage 7: Insertion Points ### The InsertionPoint Interface, `pkg/httpmsg/insertion_point.go` ```go theme={null} type InsertionPoint interface { Name() string // parameter name (e.g. "id", "username") BaseValue() string // original value at this position Type() InsertionPointType // one of INS_* constants BuildRequest(payload []byte) []byte // new request bytes with payload injected PayloadOffsets(payload []byte) []int // [startOffset, endOffset] in built request } ``` ### InsertionPointType Constants | Constant | Value | Description | | -------------------------- | ----- | --------------------------- | | `INS_PARAM_URL` | 0 | URL query parameter value | | `INS_PARAM_BODY` | 1 | POST body parameter value | | `INS_PARAM_COOKIE` | 2 | Cookie value | | `INS_PARAM_XML` | 3 | XML element value | | `INS_PARAM_XML_ATTR` | 4 | XML attribute value | | `INS_PARAM_MULTIPART_ATTR` | 5 | Multipart attribute value | | `INS_PARAM_JSON` | 6 | JSON value | | `INS_PARAM_AMF` | 7 | AMF parameter value | | `INS_HEADER` | 32 | HTTP header value | | `INS_URL_PATH_FOLDER` | 33 | REST URL path folder | | `INS_PARAM_NAME_URL` | 34 | URL parameter name | | `INS_PARAM_NAME_BODY` | 35 | Body parameter name | | `INS_ENTIRE_BODY` | 36 | Entire request body | | `INS_URL_PATH_FILENAME` | 37 | REST URL path filename | | `INS_USER_PROVIDED` | 64 | User-defined position | | `INS_EXTENSION_PROVIDED` | 65 | Extension-provided position | | `INS_UNKNOWN` | 127 | Unknown / unclassified | ### InsertionPoint Implementations | Type | Description | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ParameterInsertionPoint` | Standard parameter replacement. Uses offset-based splicing with type-aware payload encoding (URL-encode for URL/body/cookie, JSON-aware for JSON params, raw for XML). | | `HeaderInsertionPoint` | Header value replacement. Uses `AddOrReplaceHeader()` instead of offset splicing. Created for existing injectable headers + synthetic headers (`X-Forwarded-For`, `X-Forwarded-Host`, `Referer`, `True-Client-IP`, `X-Real-IP`). | | `NestedInsertionPoint` | Multi-level encoding chains (e.g., URL-encoded JSON inside a body parameter). `BuildRequest()` applies inner-to-outer: child builds first, then parent encodes the result. | | `EncodedInsertionPoint` | Custom encoder chain. Applies `prefix + payload → encoder.Encode() → splice`. Used for complex encoding scenarios. | ### LRU Cache The Executor maintains a 4096-entry LRU cache (`ipCache`) keyed by request SHA-256 hash. `CreateAllInsertionPoints()` is called once per unique request, and the results are reused for all modules scanning that request. ### Shared Base Request `CreateAllInsertionPoints()` creates a single `sharedBaseRequest` clone of the raw bytes, shared across all `ParameterInsertionPoint` instances from that call. This is safe because `BuildRequest()` never mutates the shared bytes, it always allocates a new result slice. ## Stage 8: Module Dispatch ### Module Interface Hierarchy, `pkg/modules/` ``` Module (base) ├── ActiveModule │ ├── ScanPerInsertionPoint(request, insertionPoint, httpClient, scanCtx) │ ├── ScanPerRequest(request, httpClient, scanCtx) │ ├── ScanPerHost(request, httpClient, scanCtx) │ └── AllowedInsertionPointTypes() InsertionPointTypeSet │ └── PassiveModule ├── ScanPerRequest(request, scanCtx) ├── ScanPerHost(request, scanCtx) ├── Scope() PassiveScanScope └── (optional) Flusher: Flush(scanCtx) ``` ### ScanScope Bitmask, `pkg/modules/modkit/types.go` ```go theme={null} const ( ScanScopeInsertionPoint ScanScope = 1 << iota // = 1 ScanScopeRequest // = 2 ScanScopeHost // = 4 ) ``` A module declares one or more scopes by OR-ing constants. The executor uses `ScanScopes().Has(scope)` to pre-group modules at startup. ### InsertionPointTypeSet, `pkg/modules/modkit/types.go` A `uint32` bitmask where each bit corresponds to an `InsertionPointType`. Checked by the executor before calling `ScanPerInsertionPoint()`: ```go theme={null} module.AllowedInsertionPointTypes().Contains(ip.Type()) ``` Pre-built presets: `URLParamTypes`, `BodyParamTypes`, `CookieTypes`, `HeaderTypes`, `AllParamTypes`. ### CanProcess Semantics **Active modules** (via `BaseActiveModule`): reject nil requests, unparseable URLs, media/JS URLs, and non-testable HTTP methods (`OPTIONS`, `CONNECT`, `HEAD`, `TRACE`). The executor pre-computes these checks in `computeEligibility()` and skips calling `CanProcess()` when the base would reject. **Passive modules** (via `BasePassiveModule`): only check that the required HTTP transaction parts (request and/or response) are present. They process all content types including media, no method filtering. ### Execution Pattern ``` Per item: 1. Passive per-host → sequential loop, no goroutines 2. Passive per-request → sequential loop, no goroutines 3. Active per-host → parallel: all eligible modules concurrently 4. Active per-request → parallel: all eligible modules concurrently 5. Active per-IP → for each insertion point (serial): all eligible modules concurrently ``` Steps 3-5 run as three concurrent goroutine groups via `conc.WaitGroup`. ### ScanContext, `pkg/modules/modkit/context.go` Shared resources available to all modules during scanning: ```go theme={null} type ScanContext struct { DedupManager *dedup.Manager RiskScoreUpdater RiskScoreUpdater RequestUUIDResolver RequestUUIDResolver OASTProvider OASTProvider MutationGen MutationGenerator baselineCache sync.Map // "METHOD:host/path" → *BaselineEntry } ``` * **DedupManager**: request-level deduplication. * **OASTProvider**: generates out-of-band callback URLs for blind vulnerability detection. * **MutationGenerator**: classifies parameter values and generates test mutations. * **baselineCache**: caches baseline responses for diff-based scanning. ### Flusher Interface Passive modules that buffer state across many requests (e.g., `anomaly_ranking`) implement `Flusher`: ```go theme={null} type Flusher interface { Flush(scanCtx *ScanContext) } ``` Called by the executor after all workers complete, enabling end-of-scan aggregation and final result emission. ### Module Development Defaults, `pkg/modules/modkit/` Module authors embed `BaseActiveModule` or `BasePassiveModule` to get default implementations of all interface methods. Module IDs must be lowercase kebab-case with prefix `active-` or `passive-` (validated at construction, panics on violation). The `modkit` package also provides `NewBaseModule()`, `NewBaseActiveModule()`, and `NewBasePassiveModule()` constructors. ## Stage 9: Result Emission ### ResultEvent, `pkg/output/output.go` ```go theme={null} type ResultEvent struct { ModuleID string `json:"template-id"` Info Info `json:"info,inline"` Type string `json:"type"` Host string `json:"host,omitempty"` URL string `json:"url,omitempty"` Matched string `json:"matched-at,omitempty"` ExtractedResults []string `json:"extracted-results,omitempty"` Request string `json:"request,omitempty"` Response string `json:"response,omitempty"` Metadata map[string]interface{} `json:"meta,omitempty"` Timestamp time.Time `json:"timestamp"` // ... } ``` `ResultEvent.ID()` computes a SHA-1 hash over `ModuleID | Description | Severity | Matched`, this becomes `finding_hash` in the database for deduplication. ### processResults() and emitResult() When a module returns results, the executor processes them: ``` Module returns []*ResultEvent │ ▼ processResults(results, module) │ for each result: │ ├── moduleFindingAllowed(module.ID()) │ Per-module finding cap check (MaxFindingsPerModule). │ When > 0, suppresses results after the limit is reached. │ Logs a one-time warning when a module hits its cap. │ ├── assignModuleInfo(result, module) │ Set ModuleID, Info.Name, Description, Severity, Confidence │ Default Type = "http" │ Derive Matched from URL if empty │ Derive URL from request bytes if empty │ Fill Host from URL │ └── emitResult(result) │ ├── 1. Post-hooks: RunPostHooks(result) │ nil return → drop result (hook filtered it out) │ ├── 2. Set results flag: e.results.Store(true) │ ├── 3. Database save: │ Build temp HttpRequest from result.Request │ Look up requestUUIDs[requestHash] → recordUUID │ repo.SaveFinding(result, [recordUUID], scanUUID) │ Uses INSERT ON CONFLICT (finding_hash) DO NOTHING │ ├── 4. OnResult callback → output writer │ └── 5. Notifier.Send(result) → Telegram/Discord ``` ## Stage 10: Output ### Writer Interface, `pkg/output/output.go` ```go theme={null} type Writer interface { Close() Write(*ResultEvent) error WriteFileOnly(*ResultEvent) error } ``` ### StandardWriter The default `Writer` implementation: 1. Sets `Timestamp = time.Now()`, defaults `Type = "http"`, forces `MatcherStatus = true`. 2. Serializes to JSON via `jsoniter.Marshal()`. 3. Under mutex: * **Stdout**: writes JSON (if `--json`) or formatted console output (if not `--silent`). * **File**: appends JSON line to output file (JSONL format). ### Console Format, `pkg/output/format_screen.go` ``` [› phase │] [moduleType] [moduleName] [severity] matched-at [extracted-results] [fuzz-param] ``` * Module ID split into type (`active`/`passive`) and name, colored accordingly. * Severity shown with symbol and ANSI color (Critical=magenta, High=red, Medium=yellow, Low=green). * Output truncated to terminal width. ### JSON Format, `pkg/output/format_json.go` Serializes `ResultEvent` via `jsoniter.Marshal()`. Response body is stripped unless `--include-response` is set. ### HTML Format, `pkg/output/format_html.go` Uses a streaming approach: splits the embedded HTML template at `{{.ResultsJSON}}`, writes the before-portion with simple string replacement (avoids `text/template` because bundled JS contains `{{` sequences), then streams JSON array items one at a time, then writes the after-portion. ### File Output Writer, `pkg/output/file_output_writer.go` ```go theme={null} type fileWriter struct { file *os.File mu sync.Mutex } ``` Mutex-locked, appends JSON + newline (JSONL format). Opens with `O_APPEND|O_CREATE|O_WRONLY` for safe resume across invocations. ## Stage 11: Database Persistence ### Data Models, `pkg/database/models.go` #### HTTPRecord (table: `http_records`) Fully denormalized, no separate hosts or parameters tables. Key fields: * **Identity**: `UUID` (primary key), `RequestHash` (SHA-256 of raw request) * **Host info**: `Scheme`, `Hostname`, `Port`, `IP` * **Request**: `Method`, `Path`, `URL`, `RequestHeaders` (JSONB), `RawRequest` (bytea), `RequestBody` (bytea) * **Response**: `StatusCode`, `ResponseHeaders` (JSONB), `RawResponse` (bytea), `ResponseBody` (bytea), `ResponseTitle`, `ResponseWords` * **Parameters**: `Parameters` (JSONB array of `EmbeddedParam`) * **Risk**: `RiskScore`, `Remarks` (JSONB array) * **Metadata**: `Source`, `SentAt`, `ReceivedAt`, `CreatedAt` #### Finding (table: `findings`) * **Identity**: `ID` (auto-increment), `FindingHash` (unique constraint for dedup) * **Module info**: `ModuleID`, `ModuleName`, `Description`, `Severity`, `Confidence` * **Match data**: `MatchedAt` (JSONB array), `ExtractedResults`, `Request`, `Response` * **Relations**: `HTTPRecordUUIDs` (JSONB array), `ScanUUID` * **Grouped evidence**: `AdditionalEvidence` (JSONB array of strings), request/response pairs from duplicate findings that were merged into this survivor (capped at 10 entries) The `finding_records` junction table links findings to HTTP records (many-to-many). ### Converters, `pkg/database/converters.go` * `HTTPRecord.FromHttpRequestResponse()`: converts the in-memory type to the DB model. Generates UUID, parses URL, copies headers/body, computes hashes, extracts HTML title, counts response words. * `Finding.FromResultEvent()`: maps `ResultEvent` fields to `Finding`. Sets `FindingHash = event.ID()` (the SHA-1 dedup hash). ### Repository, `pkg/database/repository.go` Key methods: | Method | Description | | ------------------------------ | ----------------------------------------------------------------------------------- | | `SaveRecord()` | Single INSERT, returns UUID | | `SaveRecordsBatch()` | Bulk INSERT in one transaction | | `SaveFinding()` | INSERT ON CONFLICT (finding\_hash) DO NOTHING + evidence append + junction table | | `DeduplicateFindings()` | Post-phase grouping: merge findings sharing (module\_id, severity, matched\_at URL) | | `CreateScanWithCursor()` | Creates scan record, copies cursor from last completed scan | | `CountRecordsAfterCursor()` | Counts new records since cursor (used for feedback loop) | | `GetRecordsWithResponseBody()` | UUID-cursor pagination for batch scanning (Kingfisher) | | `UpdateRiskScores()` | Batch CASE/WHEN UPDATE, 500 UUIDs per statement | ### RecordWriter, `pkg/database/record_writer.go` Batched asynchronous persistence for high-throughput ingestion: ```go theme={null} type RecordWriter struct { repo *Repository cfg RecordWriterConfig // BufferSize=4096, BatchSize=128, FlushInterval=50ms ch chan writeRequest // backpressure via channel capacity } ``` * `Write()` converts to `HTTPRecord`, sends to buffered channel, blocks until flushed. * `flushLoop()` runs as a single background goroutine: accumulates batch, flushes on batch-full or ticker-fire via `repo.SaveRecordsBatch()`. * Each caller gets a `WriteResult{UUID, Err}` back on a per-request result channel. ## Stage 12: Supporting Systems ### Scope Matching, `internal/config/scope_matcher.go` `ScopeMatcher` evaluates items against configurable rules across multiple dimensions (all AND-ed): 1. **Host**: glob match + origin mode filtering (cached per host) 2. **Path**: `filepath.Match` glob patterns 3. **Static file extension**: configurable extension set 4. **Status code**: exact, wildcard (`2xx`), or range (`400-499`) 5. **Content type**: glob patterns for request and response 6. **Body strings**: case-insensitive substring matching on request/response bodies **Origin modes** control how CLI targets constrain host scope: | Mode | Matching Rule | | ------------------- | ----------------------------------------- | | `all` | No restriction | | `strict` | Exact hostname match | | `balanced` | eTLD+1 must match (e.g., `*.example.com`) | | `relaxed` (default) | Host contains target keyword | ### Rate Limiting, `pkg/core/ratelimit/host_limiter.go` `HostRateLimiter` provides per-host concurrency control: * **32 fixed shards** with inline FNV-1a hashing for shard selection. * Each host gets a **buffered channel semaphore** (capacity = `MaxPerHost`, default 2). * `Acquire(ctx, host)` blocks until a slot is free; `Release(host)` frees a slot. * Background eviction goroutine removes idle entries (default: 30s idle, checked every 10s). * Per-shard capacity cap with oldest-entry eviction when exceeded. ### Host Error Circuit Breaker, `pkg/core/hosterrors/` `hosterrors.Cache` tracks consecutive errors per host: * `MarkFailed()` increments the error counter (with regex-based error matching). * `Check()` returns true when the counter reaches `MaxHostError` (default 30). * `MarkSuccess()` resets the counter (but not if already at threshold). * The executor's `feedItems()` pre-checks this to skip items for quarantined hosts. ### JS Extension Hooks, `pkg/jsext/hooks.go` **Pre-hooks** (`PreHookExecutor`): transform or filter requests before module dispatch. Return `nil` to skip the item. **Post-hooks** (`PostHookExecutor`): transform or filter results before output. Return `nil` to drop the result. `HookChain` executes hooks sequentially, passing each hook's output to the next. On error, the hook is skipped (non-fatal). On `nil` return, the chain is aborted immediately. Each hook uses a `VMPool` (`sync.Pool` of Sobek VMs), VMs are reused across concurrent invocations with no shared mutable state. ### OAST (Out-of-Band) Out-of-band callback detection for blind vulnerabilities (SSRF, XXE, etc.). The OAST service generates unique callback URLs per module/parameter/request, and is flushed at the end of the scan with a grace period to catch late callbacks. ### Endpoint Volatility Gate Every differential oracle in the scanner — blind SQLi, path normalization, IDOR, header trust, and the diff-based behavior probes — assumes the endpoint is a *function of the request*: send the same bytes twice, get the same page twice. A host behind a load balancer that round-robins between mismatched backends breaks that assumption, and the resulting "difference" is the pool, not the payload. `modkit.EndpointVolatile` closes it. Before a diff-based module reports, the gate re-samples the **unmodified** request and drops the finding when the two samples disagree. It's memoized per record, and runs only at report time, so a scan pays for it once per endpoint that was about to produce a finding, not once per probe. Ten modules gate on it as of v0.3.5; any new differential oracle should too. This is why a parked host serving one of two default server pages at random no longer produces High findings with no application behind them. It also means a genuinely non-deterministic endpoint (randomized content, per-request A/B) will suppress diff-based findings — deliberately, since no body comparison can be trusted there. ### Deduplication and Finding Grouping Three levels of deduplication prevent noise and redundancy: 1. **Request-level**: `DedupManager` prevents scanning duplicate requests (checked before module dispatch). 2. **Finding-level (inline)**: `finding_hash` unique constraint in the database uses `INSERT ON CONFLICT DO NOTHING`. When a duplicate hash is detected at insert time, `appendRecordsToFinding()` appends the new HTTP record UUIDs and request/response pair (as `AdditionalEvidence`) to the existing finding instead of creating a new row. 3. **Finding-level (post-phase grouping)**: `DeduplicateFindings()` runs after the KnownIssueScan and audit phases. It groups findings that share the same `(module_id, severity, matched_at[0] URL)` within a project, this catches cases where the same module fires multiple times on the same URL with different payloads (e.g., an injection probe producing dozens of results per endpoint). The grouping process: * Partitions findings by `module_id || severity || matched_at[0]` and orders by `created_at ASC` * Keeps the earliest finding per group as the **survivor** * Collects request/response pairs from duplicates into the survivor's `AdditionalEvidence` field (capped at 10 entries to bound storage) * Deletes all duplicate findings and their `finding_records` junction rows * Returns counts of deleted findings and merged groups for user feedback ``` Phase completes (KnownIssueScan or audit) │ ▼ DeduplicateFindings(projectUUID) │ ├── GROUP BY (module_id, severity, matched_at[0]) │ ORDER BY created_at ASC → survivor = row_number 1 │ ├── For each group with duplicates: │ Merge duplicate request/response → survivor.AdditionalEvidence │ Cap at 10 evidence entries │ ├── DELETE duplicate findings + junction rows │ └── Print feedback: "grouped N findings into M" ``` ## Putting It All Together ### End-to-End Flow ``` vigolium scan -t https://example.com │ ▼ ┌─────────────┐ │ CLI Parse │ pkg/cli/scan.go: runScanCmd() │ + Config │ Load settings, resolve strategy/profile │ + DB Init │ database.NewDB() → CreateSchema() └─────┬───────┘ │ ▼ ┌─────────────┐ │ Runner │ internal/runner/runner.go │ Build Infra│ HTTP client, scope matcher, rate limiter, hooks └─────┬───────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ RunNativeScan() — 6 Phases │ │ │ │ [Heuristics] → [Harvest] → [Discovery/Ingest] │ │ → [Spidering] → [KnownIssueScan] → [Dynamic-Assessment]│ │ │ │ Phases 0-4: populate DB with HTTP records │ │ Phase 4-5: DeduplicateFindings() after each │ │ Phase 5: scan records with modules │ └───────────────────────┬──────────────────────────────────────┘ │ ▼ (Phase 5 detail) ┌────────────────────────────────────────────────────┐ │ Executor │ │ │ │ feedItems(): │ │ source.Next() → static filter → scope check │ │ → host error check → send to worker channel │ │ │ │ worker() → processItem(): │ │ 1. Baseline HTTP fetch (or use DB response) │ │ 2. Traffic callback │ │ 3. Pre-hooks (JS transform/filter) │ │ 4. Body size enforcement │ │ 5. Scope check + DB save │ │ 6. Eligibility pre-computation │ │ 7. Passive modules (sequential) │ │ 8. Active modules (parallel, 3-way) │ │ └── per insertion point: all modules │ │ │ │ Post-processing: │ │ Flush passive modules (Flusher interface) │ │ Flush OAST service (grace period) │ └───────────────────────┬────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────┐ │ Result Emission │ │ │ │ Per-module finding cap (suppress after limit) │ │ assignModuleInfo() → emitResult(): │ │ 1. Post-hooks (JS transform/filter) │ │ 2. SaveFinding() to DB (dedup via finding_hash │ │ + evidence append on conflict) │ │ 3. OnResult → StandardWriter.Write() │ │ 4. Notifier.Send() → Telegram/Discord │ └───────────────────────┬────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────┐ │ Output │ │ │ │ Console: colored severity + module + matched URL │ │ JSON: JSONL via jsoniter │ │ HTML: embedded ag-grid template │ │ File: append-only JSONL with mutex │ └────────────────────────────────────────────────────┘ ``` ### Summary Table | Stage | Key File | Key Function | Data In | Data Out | | ---------------- | -------------------------------- | ---------------------------------------- | ------------------------ | ------------------------------------ | | CLI Entry | `cmd/vigolium/main.go` | `main()` → `cli.Execute()` | CLI args | , | | Config | `pkg/cli/scan.go` | `runScanCmd()` | Flags + YAML | `*types.Options`, `*config.Settings` | | Input | `pkg/input/source/` | `InputSource.Next()` | URLs/files/stdin | `*work.WorkItem` | | HTTP Types | `pkg/httpmsg/` | `GetRawRequestFromURL()` | URL string | `*HttpRequestResponse` | | Runner | `internal/runner/runner.go` | `RunNativeScan()` | Options + Settings | Phase results | | Executor | `pkg/core/executor.go` | `Execute()` → `processItem()` | `InputSource` + modules | `bool` (found results) | | Insertion Points | `pkg/httpmsg/insertion_point.go` | `CreateAllInsertionPoints()` | Raw request bytes | `[]InsertionPoint` | | Module Dispatch | `pkg/modules/` | `ScanPer{Host,Request,InsertionPoint}()` | `*HttpRequestResponse` | `[]*ResultEvent` | | Result Emission | `pkg/core/executor.go` | `emitResult()` | `*ResultEvent` | DB write + output | | Output | `pkg/output/output.go` | `StandardWriter.Write()` | `*ResultEvent` | Console/JSON/HTML/file | | DB Persistence | `pkg/database/` | `SaveRecord()`, `SaveFinding()` | HTTP types / ResultEvent | `HTTPRecord`, `Finding` | # Scanner Modules Reference Source: https://docs.vigolium.com/native-scan/modules-reference Reference for Vigolium's 323 scanner modules, 207 active and 116 passive, covering the OWASP Top 10 and beyond. Vigolium ships with **323 scanner modules**, 207 active and 116 passive, covering the OWASP Top 10 and beyond. Run `vigolium module ls` for the up-to-date enabled list (the registry can grow between releases without a docs refresh). Active modules send modified requests to detect vulnerabilities; passive modules analyze existing request/response pairs without generating traffic. Recent additions include the v0.3.8 batch (`ssi-injection`, `server-side-js-injection`, `smtp-header-injection`, `react-rsc-rce`, `dependent-response`, `path-relative-stylesheet`), the GraphQL security suite, the Adobe Experience Manager (AEM) family, the SaaS data-exposure family (Salesforce / ServiceNow / Power Pages), the Model Context Protocol (MCP) suite, the expanded IIS family, the `dependency-confusion` and `js-beautify` passives, and the v0.2.1 batch (out-of-band SQLi, XPath injection, unauthenticated infra-service exposure, TLS protocol/cipher audit, session fixation, and the LLM-endpoint / payment-integration / DOM-clobbering / CSS-injection / cross-origin-isolation / reverse-tabnabbing passives) — filter the list by tag with `vigolium module ls graphql`, `vigolium module ls aem`, `salesforce`, `servicenow`, `powerpages`, `mcp`, or `iis` (the positional filter matches on id, name, description, and tag; `vigolium module ls --tags` dumps every known tag). The **Module ID** column is the exact id `-m` / `--module-id` and `--module-tag` expect (`vigolium scan-url -m xss-stored,sqli-error-based`). Ids carry no `active-` / `passive-` prefix — `--module-id` requires an exact match and `-m` falls back to substring matching, so a prefixed name selects nothing. ## Severity Scale `critical` > `high` > `medium` > `low` > `suspect` > `info` ## Confidence Scale * **certain** - Definitively confirmed (payload executed, error matched) * **firm** - Likely confirmed by behavioral analysis * **tentative** - Possible but unconfirmed (heuristic-based) *** ## Active Modules (207) Active modules send modified requests to detect vulnerabilities via fuzzing, injection, and behavioral analysis. Every registered active module is listed below; `vigolium module ls --type active` prints the same set from your installed build. ### XSS | Module ID | Name | Description | Severity | Confidence | Tags | | --------------------------- | --------------------------------- | ------------------------------------------------------------------------------------- | -------- | ---------- | -------------------------------------- | | `xss-light-url-params` | XSS Light - URL Parameters | Reflected XSS in URL parameters with POST→GET conversion | High | Firm | `xss`, `injection` | | `xss-light-path` | XSS Light - Path Injection | Reflected XSS via path manipulation (recursive, cut, append) | High | Firm | `xss`, `injection` | | `xss-light-param-discovery` | XSS Light - Parameter Discovery | Reflected XSS via echo parameter discovery | High | Firm | `xss`, `injection` | | `xss-light-encoded` | XSS Light - Pre-encoded Injection | Detects XSS where the app decodes a parameter (base64 / double-URL) before reflecting | High | Firm | `injection`, `xss`, `light`, `encoded` | | `xss-dom-confirm` | XSS DOM Confirm (Browser) | Confirms reflected and DOM-based XSS by observing alert() in a real browser | High | Certain | `xss`, `dom-xss`, `browser`, `slow` | | `xss-stored` | Stored XSS (browser-confirmed) | Injects a canary, then confirms it executes on a later retrieval of the page | High | Firm | `injection`, `xss`, `stored` | ### SQL Injection | Module ID | Name | Description | Severity | Confidence | Tags | | -------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ---------- | ---------------------------- | | `sqli-error-based` | SQLi Error Based | Error-based SQLi via database error messages (MySQL, PostgreSQL, MSSQL, Oracle, SQLite) | Critical | Certain | `sqli`, `injection` | | `sqli-boolean-blind` | Blind SQL Injection (Boolean-Based) | Boolean-based blind SQLi using a randomized boundary matrix with difflib-style page-similarity comparison, a multi-round/multi-factor logic battery (AND/OR oracle, alternating comparison operators, invalid-syntax probe), and WAF-adaptive payload mutation. Only reports 200-vs-200 differentials with a large body-size delta (rejects status-flip false positives) | High | Certain | `sqli`, `injection`, `heavy` | | `sqli-out-of-band` | SQL Injection (Out-of-Band) | Blind SQLi confirmed via out-of-band DBMS callbacks (`LOAD_FILE` / `xp_dirtree` / `UTL_HTTP`) to a per-payload OAST subdomain — an unforgeable hit proves execution | Critical | Certain | `sqli`, `injection`, `oast` | ### NoSQL Injection | Module ID | Name | Description | Severity | Confidence | Tags | | --------------------------- | ------------------------ | ----------------------------------------------------------------------------- | -------- | ---------- | --------------------- | | `nosqli-error-based` | NoSQLi Error Based | NoSQL injection via error messages (MongoDB, CouchDB, Cassandra) | High | Tentative | `nosqli`, `injection` | | `nosqli-operator-injection` | NoSQL Operator Injection | MongoDB operator injection (`$ne`, `$gt`, `$regex`, `$where`) for auth bypass | High | Tentative | `nosqli`, `injection` | ### Template Injection | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------- | ------------------------------ | ------------------------------------------------------------ | -------- | ---------- | ------------------- | | `reflected-ssti` | Reflected SSTI | SSTI via math expression evaluation (e.g., `{{7*7}}=49`) | High | Certain | `ssti`, `injection` | | `ssti-detection` | SSTI Detection | Diff-based SSTI via Boolean Error-Based Blind technique | Info | Certain | `ssti`, `injection` | | `csti-detection` | Client-Side Template Injection | CSTI in AngularJS/Vue.js applications via literal reflection | Medium | Firm | `ssti`, `injection` | ### File Inclusion | Module ID | Name | Description | Severity | Confidence | Tags | | -------------------- | ------------------ | ----------------------------------------------------------------- | -------- | ---------- | ------------------ | | `lfi-generic` | LFI Generic | LFI via path traversal payloads; matches known OS file signatures | High | Certain | `lfi`, `injection` | | `lfi-path-traversal` | LFI Path Traversal | Advanced LFI with null bytes, double encoding, Unicode bypass | High | Firm | `lfi`, `injection` | ### Code Execution & Injection | Module ID | Name | Description | Severity | Confidence | Tags | | --------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | --------------------------------------------------------------- | | `code-exec` | Code Execution (RCE) | OS command injection via time-based blind (sleep/delay measurement), plus language-specific interpreter payloads across several string-breakout contexts | Critical | Certain | `rce`, `injection` | | `command-injection-echo` | OS Command Injection (Results-Based) | In-band OS command injection confirmed by reflecting a unique arithmetic marker, baseline-compared over multiple rounds to suppress false positives | Critical | Certain | `rce`, `command-injection`, `injection` | | `command-injection-oast` | OS Command Injection (Out-of-Band) | Blind OS command injection via DNS/HTTP OAST callbacks (classified distinctly from SSRF/XXE) | Critical | Certain | `rce`, `command-injection`, `oast` | | `command-injection-timing` | OS Command Injection (Time-Based) | Time-based blind OS command injection with delay-scaling confirmation across rounds | Critical | Tentative | `rce`, `command-injection`, `injection`, `heavy` | | `crlf-injection` | CRLF Injection | CRLF injection in HTTP headers via CR/LF character sequences | Medium | Firm | `injection` | | `xxe-generic` | XXE Generic | XML external entity injection in generic XML endpoints; includes blind / out-of-band detection via external- and parameter-entity DTDs referencing a unique OAST URL | Critical | Certain | `xxe`, `injection` | | `xpath-injection` | XPath Injection | XPath/XQuery injection detected via engine error signatures and a boolean oracle (e.g. `' or '1'='1`) | High | Firm | `xpath`, `injection` | | `insecure-deserialization` | Insecure Deserialization | Error-based detection for Java, PHP, Python, Ruby, and .NET deserialization | High | Firm | `injection` | | `input-behavior-probe` | Input Behavior Probe | Behavior change detection via header, path, debug param, and char probing | Info | Tentative | `injection` | | `padding-oracle` | CBC Padding Oracle | Confirms a CBC padding oracle by flipping ciphertext bytes and observing a reproducible padding-error differential | High | Firm | `cryptography`, `injection`, `heavy` | | `ldap-injection` | LDAP Injection | Detects LDAP injection via error-based and boolean-based techniques, including a negation oracle (match-all vs match-none divergence) | Medium | Firm | `injection`, `heavy` | | `log4shell-probe` | Log4Shell Probe | Detects Log4Shell (CVE-2021-44228) via JNDI payload injection with OAST callbacks | Critical | Certain | `java`, `rce`, `heavy` | | `struts-ognl-injection` | Struts OGNL Injection | Detects Apache Struts OGNL injection via Content-Type and parameter payloads | Critical | Firm | `java`, `rce`, `moderate` | | `response-header-injection` | HTTP Response Header Injection | Detects HTTP response header injection via CRLF in parameters | Medium | Certain | `crlf`, `injection`, `header`, `response-splitting`, `moderate` | | `ssi-injection` | Server-Side Includes (SSI) Injection | Injects a set/echo SSI directive whose value is emitted only when the server parses SSI, with the directive markup consumed — proving evaluation rather than reflection; also confirms `exec` out of band | High | Firm | `injection`, `ssi`, `rce`, `heavy` | | `server-side-js-injection` | Server-Side JavaScript Injection | Server-side JavaScript (Node.js) code injection through `eval` / `Function` / `vm` / `$where`, confirmed via an OAST callback or a consistent busy-wait time differential | Critical | Firm | `injection`, `ssji`, `nodejs`, `rce`, `heavy` | | `smtp-header-injection` | SMTP Header Injection | CRLF injection into a mail header via an email-bearing parameter, confirmed by grafting a `Bcc` at a unique collaborator address the mailer resolves on delivery | Medium | Firm | `injection`, `smtp`, `crlf`, `email`, `heavy` | ### SSRF & Out-of-Band (OAST) | Module ID | Name | Description | Severity | Confidence | Tags | | -------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ------------------------------------------- | | `ssrf-detection` | SSRF Detection | SSRF via in-band probes (internal IPs, cloud metadata) with response differential | High | Tentative | `ssrf`, `injection` | | `oast-probe` | OAST Probe | Blind vulnerabilities (blind SSRF, blind XXE, blind RCE) via DNS/HTTP callbacks, plus "Collaborator Everywhere" OAST headers | High | Certain | `ssrf`, `injection` | | `proxy-pingback` | Proxy Pingback | Open proxy/callback endpoints via OAST URL injection | High | Certain | `ssrf`, `injection` | | `ssrf-filter-bypass` | SSRF Filter Bypass (URL Parser Confusion) | SSRF that defeats URL allow/deny filters via parser confusion (`@`, `#`, backslash, embedded credentials) | High | Firm | `ssrf`, `injection`, `heavy` | | `ssrf-protocol-smuggling` | SSRF Protocol Smuggling (CRLF in URL) | SSRF via CRLF-in-URL protocol smuggling against fetchers that don't sanitize the target URL | High | Firm | `ssrf`, `injection`, `heavy` | | `routing-ssrf` | Routing-Based SSRF (Request-Line) | Routing-based SSRF from PortSwigger's "Cracking the Lens" — absolute-URI / `@` / `///` request-line targets with OAST + internal-marker confirmation | Info | Firm | `ssrf`, `proxy`, `routing`, `oast`, `heavy` | | `upgrade-routing-ssrf` | WebSocket-Upgrade SSRF Filter Bypass | Routing SSRF that bypasses filters via a WebSocket `Upgrade`, confirmed by a with-vs-without differential | High | Tentative | `ssrf`, `proxy`, `websocket`, `heavy` | | `pdf-generation-injection` | PDF Generation Injection | Detects HTML/JS injection into server-side PDF generation endpoints for SSRF/file read | High | Firm | `ssrf`, `injection`, `moderate` | ### Misconfiguration | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ------------------------------------------------------------------------------------ | | `cors-misconfiguration` | CORS Misconfiguration | Permissive CORS policies (reflected origins, null origin, wildcard+credentials), plus a single-mutation trusted-origin probe gated on a negative control so a blanket reflector isn't re-reported | Low | Firm | `misconfiguration` | | `host-header-injection` | Host Header Injection | Host header injection via value reflection (password reset/cache poisoning) | Medium | Firm | `misconfiguration` | | `web-cache-poisoning` | Web Cache Poisoning | Cache poisoning via unkeyed header injection (`X-Forwarded-Host`, `X-Forwarded-Scheme`, `X-Host`, `X-Forwarded-Server`, `X-Original-Host`, `X-Forwarded-Prefix`) | High | Tentative | `misconfiguration` | | `cache-poisoned-dos` | Cache-Poisoned Denial of Service (CPDoS) | CPDoS via header-only HMO/HHO variants that make a shared cache store and replay an origin error; safe unique-buster oracle, never affects shared keys | Medium | Firm | `cache-poisoning`, `cpdos`, `dos` | | `unauth-service-exposure` | Unauthenticated Infrastructure Service Exposure | Unauthenticated Docker / Kubernetes / datastore APIs exposed over HTTP, confirmed by a service-specific structural signature and re-verified | Critical | Certain | `exposure`, `infrastructure`, `misconfiguration` | | `tls-protocol-cipher-audit` | TLS Protocol & Cipher Audit | Grades a host's TLS by directly negotiating deprecated protocols and weak cipher suites | Low | Certain | `tls`, `crypto`, `transport`, `misconfiguration` | | `cache-deception` | Web Cache Deception | Detects web cache deception via path confusion with static file extensions and delimiter-confusion variants | Medium | Tentative | `cache-poisoning`, `auth-bypass`, `moderate` | | `oauth-misconfiguration` | OAuth/OIDC Misconfiguration | Detects common OAuth/OIDC misconfigurations including open redirect and missing state | Low | Firm | `authentication`, `session`, `moderate` | | `common-directory-listing` | Common Directory Listing | Detects directory listing exposure on common web servers (Apache, Nginx, IIS, Jetty, Python) | Low | Firm | `info-disclosure`, `misconfiguration`, `directory-listing`, `light` | | `dashboard-exposure` | Exposed Third-Party Dashboard | Probes for exposed third-party dashboards/consoles (Grafana, Airflow, GitLab, Jenkins, Ollama, ...) and unauthenticated version/config/data leaks | Medium | Firm | `dashboard`, `exposure`, `discovery`, `info-leak`, `default-login`, `light` | | `go-debug-endpoint-exposure` | Go Debug Endpoint Exposure | Detects exposed Go net/http/pprof and expvar debug endpoints | Medium | Firm | `golang`, `pprof`, `expvar`, `debug`, `info-disclosure`, `misconfiguration`, `light` | | `subdomain-takeover` | Subdomain Takeover | Detects dangling DNS records pointing to deprovisioned cloud services | High | Firm | `cloud`, `misconfiguration`, `moderate` | | `tls-cert-recon` | TLS Certificate Recon | Reads each host's live TLS certificate for recon: self-signed / private-CA certs and the subdomains and internal names in their SANs | Info | Certain | `recon`, `tls`, `certificate`, `fingerprint`, `light` | | `path-relative-stylesheet` | Path-Relative Stylesheet Import (PRSSI) | A page carrying a path-relative stylesheet that the server also serves at a path-info-confused URL without `X-Content-Type-Options: nosniff`, so attacker content can resolve as CSS | Low | Firm | `client-side`, `css-injection`, `prssi`, `moderate` | ### Access Control | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ---------- | --------------------------------------------------------------------------- | | `forbidden-bypass` | 403/401 Forbidden Bypass | Bypass via path manipulation, header injection, method tampering | Medium | Firm | `auth-bypass` | | `http-method-tampering` | HTTP Method Tampering | Unexpectedly enabled HTTP methods (PUT, DELETE, PATCH) and overrides | Info | Firm | `auth-bypass` | | `csrf-verify` | CSRF Token Verification | Verifies CSRF token enforcement by removing, emptying, or randomizing tokens | High | Firm | `auth-bypass` | | `idor-detection` | IDOR Detection | Missing authorization on object ID parameters via neighbor ID probing | High | Tentative | `auth-bypass` | | `mass-assignment` | Mass Assignment | Mass assignment via injecting privilege keys into JSON APIs | High | Firm | `auth-bypass` | | `open-redirect` | Open Redirect | Open redirect via injected external URL in Location/meta refresh, across a wide set of leading separators that catch blocklist-bypass redirect targets | Medium | Firm | `auth-bypass` | | `open-redirect-confusion` | Open Redirect via URL Parser Confusion | Open redirect reached through URL-parser-confusion payloads (`@`, backslash, embedded host) that slip past naive redirect validation | High | Firm | `open-redirect`, `ssrf`, `moderate` | | `session-fixation` | Session Fixation | Permissive session mechanism that adopts an attacker-supplied session ID across authentication | Medium | Firm | `session`, `auth`, `session-fixation` | | `authz-compare` | Cross-Session Authorization Compare | Compares responses across authenticated sessions to detect IDOR/BOLA | High | Firm | `idor`, `bola`, `auth-bypass`, `access-control`, `api-security`, `moderate` | | `bfla-detection` | BFLA Detection | Detects Broken Function-Level Authorization on privileged endpoints | High | Tentative | `auth-bypass`, `api-security`, `moderate` | | `idor-guid` | IDOR GUID Predictability | Detects predictable GUID patterns like UUIDv1 with extractable timestamps | Medium | Firm | `idor`, `auth-bypass`, `moderate` | ### Path Analysis | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ---------------------------------------------------------------------- | | `path-normalization` | Path Normalization | Path normalization vulnerabilities via traversal payloads against middleware/reverse proxy | High | Firm | `misconfiguration` | | `nginx-off-by-slash` | Nginx Off-by-Slash | Nginx alias traversal via missing trailing slash | High | Tentative | `misconfiguration` | | `nginx-path-escape` | Nginx Path Escape Detection | Diff-based detection for alias traversal, URL encoding bypass, semicolon injection | Info | Tentative | `misconfiguration` | | `reverse-proxy-path-confusion` | Reverse Proxy Path Confusion | Reaches access-controlled backend paths the reverse proxy and origin normalize differently (encoded slashes, dot-segments, semicolons) | High | Firm | `proxy`, `access-control`, `heavy` | | `client-path-traversal-confirm` | Client-Side Path Traversal Confirm | Browser-confirms a URL-controlled value escaping a client-side request path prefix | Medium | Firm | `cspt`, `dom`, `browser`, `heavy` | | `cdn-object-traversal-listing` | CDN Object-Storage Traversal Listing | Detects bucket object enumeration via ..; path traversal on CDN-fronted object storage | High | Firm | `cloud`, `cloud-storage`, `path-traversal`, `info-disclosure`, `light` | ### Differential & Behavior Detection | Module ID | Name | Description | Severity | Confidence | Tags | | -------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ---------------------------------------------- | | `smart-behavior-detection` | Smart Behavior Detection | Diff-based injection detection via true/false behavioral payload pairs | Info | Tentative | `detection` | | `suspect-transform` | Suspect Transform Detection | Expression evaluation, quote consumption, and unicode normalizations | Suspect | Firm | `detection` | | `backslash-transformation` | Backslash Transformation | Escape sequence interpretation, backslash consumption, character handling | Suspect | Firm | `detection` | | `internal-header-probe` | Internal Header Probe | Fuzzes custom/internal request headers advertised via Access-Control-Allow-Headers and reports value-dependent response changes | Suspect | Tentative | `cors`, `header`, `recon`, `ssrf`, `intrusive` | | `dependent-response` | Referer / User-Agent Dependent Response | Response content that varies on a spoofable `Referer` or `User-Agent` — ineffective gating, cloaking, or weak access control — with each variant re-sampled to rule out dynamic-content noise | Info | Firm | `recon`, `behavior`, `cloaking`, `moderate` | ### Prototype Pollution | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------ | -------- | ---------- | ------------------------- | | `prototype-pollution` | Prototype Pollution | Server-side prototype pollution via `__proto__` and `constructor.prototype` JSON injection | High | Firm | `javascript`, `injection` | | `client-prototype-pollution` | Client-Side Prototype Pollution | Client-side prototype pollution via JavaScript static analysis (source + gadget patterns) | Medium | Tentative | `javascript`, `injection` | ### Race Conditions | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------- | --------------------------- | ------------------------------------------------------------------------------------------ | -------- | ---------- | ----------- | | `race-interference` | Race Interference Detection | Race conditions via parallel request analysis (input storage, cross-contamination, TOCTOU) | Medium | Firm | `injection` | ### XML, JWT & HTTP Protocol | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------ | ---------------------- | --------------------------------------------------------------------------- | -------- | ---------- | ----------- | | `xml-saml-security` | XML SAML Security | XXE and DTD injection in SAML XML processing | High | Firm | `injection` | | `jwt-vulnerability` | JWT Vulnerability | JWT algorithm confusion (`none` algorithm, empty signature, RS256→HS256) | Critical | Certain | `injection` | | `http-request-smuggling` | HTTP Request Smuggling | CL.TE and TE.CL desync via conflicting Content-Length and Transfer-Encoding | Suspect | Tentative | `injection` | ### API & Endpoint Security | Module ID | Name | Description | Severity | Confidence | Tags | | -------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | --------------------------------------------------------------------------- | | `graphql-scan` | GraphQL Security Scanner | Runs only against a confirmed GraphQL endpoint: introspection & schema exercise, exposed IDE/console (GraphiQL/Playground/Altair), error- and boolean-based SQLi, field-level authorization (IDOR/BOLA), reflected XSS in errors, uncapped query batching, and DoS via deeply-nested/aliased/circular queries. Endpoint discovery walks endpoint names over parent-path prefixes and nested `api`/version permutations | Medium | Certain | `graphql`, `injection`, `idor`, `bola`, `xss`, `dos`, `batching`, `console` | | `file-upload-scan` | File Upload Scanner | File upload bypass (extension, null byte, magic bytes, SVG XXE, HTML XSS); a PHP web shell is confirmed by an arithmetic execution marker that proves evaluation, not mere storage | High | Certain | `injection` | | `default-credentials` | Default Credentials | Login endpoints tested with common credential pairs; CAPTCHA/lockout aware | High | Certain | `auth-bypass` | | `sensitive-file-discovery` | Sensitive File Discovery | \~25 marker-based sensitive files and \~1,350 generic paths (.env, .git, logs) | Medium | Tentative | `info-disclosure` | | `jsonp-callback` | JSONP Callback Injection | JSONP endpoints via callback injection enabling cross-origin data theft | Medium | Firm | `injection` | | `grpc-surface-audit` | gRPC-Web Surface Audit | gRPC-Web (HTTP/1.1) endpoints with missing authorization and exposed reflection/health services | High | Firm | `grpc`, `api`, `authorization`, `heavy` | | `llm-boundary-probe` | LLM Boundary Probe | Probes LLM endpoints for system-prompt / secret disclosure and tool abuse | High | Firm | `llm`, `ai`, `prompt-injection`, `heavy` | | `api-key-url-exposure` | API Key in URL | Detects API keys that work when moved from headers to URL parameters | Medium | Firm | `info-disclosure`, `api-security`, `light` | | `api-spec-ingest` | API Spec Ingest | Discovers API specs (OpenAPI/Swagger/Postman) and ingests endpoints for scanning | Info | Firm | `api`, `discovery`, `spec-ingest`, `light` | | `swagger-exposure` | Exposed API Documentation | Detects publicly exposed Swagger/OpenAPI/Redoc documentation routes | Low | Firm | `api`, `discovery`, `swagger`, `openapi`, `exposure`, `info-leak`, `light` | | `ws-cswsh` | WebSocket CSWSH | Tests for Cross-Site WebSocket Hijacking via insufficient origin validation | Medium | Firm | `csrf`, `session`, `moderate` | | `ws-injection` | WebSocket Injection | Tests for injection vulnerabilities in parameters forwarded to WebSocket message processing | Medium | Firm | `injection`, `xss`, `moderate` | ### Proxy & Utility | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------------- | -------------------------- | ------------------------------------------------------------------------- | -------- | ---------- | ------------------------------ | | `proxy-header-trust` | Proxy Header Trust | Cross-framework proxy header trust issues via X-Forwarded-\* manipulation | High | Firm | `misconfiguration`, `moderate` | | `api-rate-limit-bypass` | API Rate Limit Bypass | Rate limiting bypass via IP spoofing headers | Medium | Firm | `auth-bypass`, `moderate` | | `backup-file-discovery` | Backup File Discovery | Exposed backup archives derived from hostname and year variants | Medium | Tentative | `sensitive-file`, `moderate` | | `angular-template-injection` | Angular Template Injection | Angular template injection via expression evaluation | High | Firm | `angular`, `injection`, `ssti` | ### SQL Injection (Time-Based) | Module ID | Name | Description | Severity | Confidence | Tags | | ----------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ---------------------------- | | `sqli-time-blind` | Blind SQL Injection (Time-Based) | Time-based blind SQLi with a per-target statistical delay threshold and multi-round confirmation that the delay scales with the injected sleep value (rejects fixed/jitter delays); prioritizes payloads by any DBMS identified for the host | Suspect | Tentative | `injection`, `sqli`, `heavy` | ### SSRF & SSTI (Blind) | Module ID | Name | Description | Severity | Confidence | Tags | | ------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ---------------------------- | | `ssrf-blind` | Blind SSRF Detection | Blind SSRF via OAST callbacks | High | Firm | `ssrf`, `injection`, `heavy` | | `ssti-blind` | Blind SSTI | Blind SSTI via OAST callbacks and time-delay payloads; JVM template stacks are confirmed with a pure-JVM DNS/URL callback that needs no shell on the target | Critical | Firm | `injection`, `ssti`, `heavy` | ### Framework Security #### Next.js | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ---------- | --------------------------------------------------------------------- | | `nextjs-data-leakage` | Next.js Data Route Leakage | Unauthorized access to `/_next/data//.json` | High | Firm | `nextjs`, `javascript` | | `nextjs-middleware-bypass` | Next.js Middleware Bypass | CVE-2025-29927 and path normalization bypasses | Critical | Firm | `nextjs`, `javascript` | | `nextjs-image-ssrf` | Next.js Image Optimizer SSRF | SSRF via `/_next/image` with OAST and in-band probes | High | Firm | `nextjs`, `javascript` | | `nextjs-draft-mode-exposure` | Next.js Draft Mode Exposure | Insecure or unprotected Draft/Preview Mode endpoints | High | Firm | `nextjs`, `javascript` | | `nextjs-version-audit` | Next.js Version Audit | Fingerprints Next.js version and maps to known CVE advisories | High | Firm | `nextjs`, `javascript`, `fingerprint` | | `js-devserver-exposure` | JS Dev Server Exposure | Exposed webpack HMR, Vite, Nuxt, Remix dev server endpoints | Medium | Firm | `javascript` | | `fastify-hono-probe` | Fastify/Hono Probe | Detects exposed Fastify and Hono framework endpoints | Medium | Firm | `nodejs`, `misconfiguration`, `light` | | `metaframework-probe` | Metaframework Probe | Detects exposed Remix, Astro, and SvelteKit internal files and endpoints | Low | Firm | `javascript`, `misconfiguration`, `light` | | `nextjs-chunk-audit` | Next.js Static Chunk Audit | Fetches Next.js static JS chunks and extracts routes, domains, and embedded secrets | Info | Certain | `nextjs`, `javascript`, `intel`, `info-disclosure`, `medium`, `light` | | `react-rsc-rce` | React Server Components RCE | The React Flight server-action decoder RCE (CVE-2025-55182 / CVE-2025-66478) — a crafted colon-delimited object reference makes the decoder dereference a missing property, surfacing as a Next.js error digest while a well-formed control request does not | Critical | Firm | `nextjs`, `react`, `rce`, `deserialization`, `heavy` | #### Spring / Java | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------------ | -------------------------------- | ----------------------------------------------------------------------- | -------- | ---------- | ---------------------------------------------------------------- | | `spring-actuator-misconfig` | Spring Actuator Misconfiguration | Exposed Spring Boot actuator endpoints leaking env vars, health, config | High | Firm | `spring`, `java`, `misconfiguration`, `info-disclosure`, `light` | | `spring-boot-admin-exposure` | Spring Boot Admin Exposure | Exposed Spring Boot Admin dashboards | High | Firm | `spring`, `java` | | `spring-cloud-config-exposure` | Spring Cloud Config Exposure | Exposed Config Server endpoints leaking secrets | Critical | Firm | `spring`, `java` | | `spring-data-rest-exposure` | Spring Data REST Exposure | Auto-exposed repository endpoints with HAL/HATEOAS | Medium | Firm | `spring`, `java` | | `spring-debug-exposure` | Spring Debug Exposure | Debug endpoints, Whitelabel errors, stack traces | Medium | Firm | `spring`, `java` | | `spring-gateway-exposure` | Spring Gateway Exposure | Exposed Cloud Gateway actuator revealing routes | High | Firm | `spring`, `java` | | `spring-h2-console-exposure` | Spring H2 Console Exposure | Exposed H2 database web consoles | Medium | Firm | `spring`, `java`, `rce` | | `spring-jolokia-exposure` | Spring Jolokia Exposure | Exposed Jolokia JMX endpoints | High | Firm | `spring`, `java` | | `java-appserver-console` | Java App Server Console | Exposed admin consoles (WildFly, WebLogic, GlassFish) | High | Firm | `java`, `tomcat` | | `java-sensitive-files` | Java Sensitive Files | Java config files, WEB-INF, META-INF, build artifacts | Medium | Tentative | `java`, `sensitive-file` | | `tomcat-manager-exposure` | Tomcat Manager Exposure | Exposed Tomcat Manager and Host Manager interfaces | High | Firm | `tomcat`, `java` | #### Django / Flask / FastAPI (Python) | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------------- | ----------------------------- | ----------------------------------------------- | -------- | ---------- | ------------------------ | | `django-admin-exposure` | Django Admin Exposure | Exposed Django admin panel and login page | Low | Firm | `django`, `python` | | `django-browsable-api-exposure` | Django Browsable API Exposure | DRF browsable API detected via Accept header | Info | Firm | `django`, `python` | | `django-debug-exposure` | Django Debug Exposure | Django DEBUG=True information disclosure | High | Firm | `django`, `python` | | `django-debug-toolbar-exposure` | Django Debug Toolbar Exposure | Exposed django-debug-toolbar panels | High | Firm | `django`, `python` | | `flask-werkzeug-debugger` | Flask Werkzeug Debugger | Exposed Werkzeug interactive debugger (RCE) | Critical | Certain | `flask`, `python`, `rce` | | `fastapi-docs-exposure` | FastAPI Docs Exposure | Exposed FastAPI interactive API documentation | Info | Firm | `fastapi`, `python` | | `fastapi-auth-inconsistency` | FastAPI Auth Inconsistency | Unprotected operations found via OpenAPI schema | Medium | Firm | `fastapi`, `python` | #### Laravel / Symfony / PHP | Module ID | Name | Description | Severity | Confidence | Tags | | -------------------------- | ------------------------------- | ----------------------------------------------------- | -------- | ---------- | ----------------------- | | `laravel-admin-exposure` | Laravel Admin Exposure | Exposed admin panels, API docs, GraphQL endpoints | High | Tentative | `laravel`, `php` | | `laravel-devtool-exposure` | Laravel Developer Tool Exposure | Exposed Web Tinker, Clockwork, Pulse, Log Viewer | High | Firm | `laravel`, `php` | | `laravel-ignition-rce` | Laravel Ignition RCE | CVE-2021-3129 RCE via exposed Ignition endpoints | Critical | Firm | `laravel`, `php`, `rce` | | `laravel-misconfig` | Laravel Misconfiguration | Debug mode, exposed debugbar, application logs | High | Firm | `laravel`, `php` | | `laravel-sensitive-files` | Laravel Sensitive Files | PHPUnit config, SQLite DB, storage internals | Medium | Tentative | `laravel`, `php` | | `symfony-misconfig` | Symfony Misconfiguration | Exposed profiler, debug toolbar, dev front controller | High | Firm | `symfony`, `php` | | `php-composer-exposure` | PHP Composer Exposure | Exposed Composer manifests, vendor directory | High | Firm | `php` | | `php-debug-exposure` | PHP Debug Exposure | Exposed phpinfo, PHP-FPM status, phpMyAdmin | Medium | Firm | `php` | | `php-framework-debug` | PHP Framework Debug Exposure | Debug endpoints for Yii, CodeIgniter, CakePHP | Medium | Firm | `php` | | `php-path-info-misconfig` | PHP PATH\_INFO Misconfiguration | cgi.fix\_pathinfo routing ambiguity | Medium | Firm | `php` | | `php-source-disclosure` | PHP Source Disclosure | PHP source code via .phps handlers | High | Firm | `php` | #### Rails (Ruby) | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------------- | -------------------------- | ----------------------------------------------- | -------- | ---------- | --------------- | | `rails-info-exposure` | Rails Info Exposure | Exposed Rails dev/debug endpoints in production | High | Firm | `rails`, `ruby` | | `rails-admin-dashboard` | Rails Admin Dashboard | Exposed Rails ecosystem admin panels | High | Firm | `rails`, `ruby` | | `rails-sensitive-files` | Rails Sensitive Files | Exposed Rails config, credentials, artifacts | Medium | Tentative | `rails`, `ruby` | | `rails-action-mailbox-probe` | Rails Action Mailbox Probe | Exposed Action Mailbox ingress endpoints | Medium | Firm | `rails`, `ruby` | | `rails-active-storage-probe` | Rails Active Storage Probe | Exposed Active Storage direct upload endpoints | Medium | Tentative | `rails`, `ruby` | #### Express (Node.js) | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------------- | ------------------------------------ | ----------------------------------------------- | -------- | ---------- | ----------------------- | | `express-debug-probe` | Express Debug Probe | Stack trace and debug info leakage | Low | Firm | `express`, `javascript` | | `express-directory-listing` | Express Directory Listing | Directory listing via serve-index middleware | Low | Firm | `express`, `javascript` | | `express-trust-proxy-misconfig` | Express Trust Proxy Misconfiguration | Trust proxy misconfiguration via X-Forwarded-\* | Medium | Firm | `express`, `javascript` | #### ASP.NET / IIS | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------- | ---------- | ------------------------------------------------------------- | | `aspnet-blazor-exposure` | ASP.NET Blazor Exposure | Exposed Blazor WebAssembly assemblies and Server endpoints | Medium | Firm | `aspnet` | | `aspnet-health-exposure` | ASP.NET Health Endpoint Exposure | Exposed health checks, monitoring dashboards, metrics | Medium | Firm | `aspnet` | | `aspnet-identity-probe` | ASP.NET Identity Probe | Exposed Identity endpoints and IdentityServer | Medium | Firm | `aspnet` | | `aspnet-misconfig` | ASP.NET Misconfiguration | Exposed diagnostics, debug endpoints, verbose errors | High | Firm | `aspnet` | | `aspnet-sensitive-files` | ASP.NET Sensitive Files | Exposed config files, backups, sensitive directories | Medium | Tentative | `aspnet` | | `aspnet-service-exposure` | ASP.NET Service Exposure | Exposed ASMX, WCF, OData, legacy service paths | Medium | Firm | `aspnet` | | `aspnet-viewstate-scan` | ASP.NET ViewState Scan | ViewState MAC disabled, event validation bypass | High | Firm | `aspnet` | | `iis-shortname-discovery` | IIS Short Filename Discovery | IIS 8.3 short-filename enumeration via a tilde-based oracle, resolving names to feed content discovery | Medium | Certain | `iis`, `aspnet`, `info-disclosure`, `heavy` | | `iis-cookieless-source-disclosure` | IIS Cookieless Source/Config Disclosure | Downloads protected ASP.NET config/source via cookieless (`S(X)`) path-confusion | High | Firm | `iis`, `aspnet`, `info-disclosure`, `heavy` | | `iis-extension-confusion-bypass` | IIS Extension Confusion Bypass | NTFS `::$DATA` source disclosure and trailing-dot / `::$INDEX_ALLOCATION` access-control bypass | High | Firm | `iis`, `aspnet`, `access-control`, `info-disclosure`, `heavy` | #### Firebase | Module ID | Name | Description | Severity | Confidence | Tags | | ----------------------------- | ------------------------------ | ---------------------------------------------------- | -------- | ---------- | ------------------- | | `firebase-auth-misconfig` | Firebase Auth Misconfiguration | Firebase Authentication misconfigurations | Medium | Firm | `firebase` | | `firebase-functions-exposure` | Firebase Functions Exposure | Unauthenticated Cloud Functions | Medium | Firm | `firebase` | | `firebase-misconfig` | Firebase Misconfiguration | Exposed Firebase config, security rules, credentials | High | Firm | `firebase` | | `firebase-rtdb-exposure` | Firebase RTDB Exposure | Publicly readable Realtime Database | Medium | Certain | `firebase` | | `firebase-storage-exposure` | Firebase Storage Exposure | Publicly accessible Cloud Storage buckets | High | Certain | `firebase`, `cloud` | #### Cloud Infrastructure | Module ID | Name | Description | Severity | Confidence | Tags | | ----------------------- | --------------------- | ----------------------------------------------------- | -------- | ---------- | ------- | | `cloud-bucket-takeover` | Cloud Bucket Takeover | Dangling cloud storage buckets vulnerable to takeover | High | Firm | `cloud` | | `cloud-origin-bypass` | Cloud Origin Bypass | Direct access to origins bypassing CDN security | Medium | Firm | `cloud` | | `cloud-public-read` | Cloud Public Read | Publicly readable sensitive paths on cloud storage | High | Firm | `cloud` | | `cloud-storage-listing` | Cloud Storage Listing | Publicly listable S3 buckets and Azure containers | High | Certain | `cloud` | #### CMS (WordPress, Drupal, Joomla, Magento) | Module ID | Name | Description | Severity | Confidence | Tags | | ------------------------ | ------------------------------ | ----------------------------------------------------- | -------- | ---------- | ------------------------------- | | `wp-misconfig` | WordPress Misconfiguration | Exposed config files, debug logs, dangerous endpoints | High | Firm | `wordpress`, `php` | | `wp-user-enum` | WordPress User Enumeration | User enumeration via author archives and REST API | Info | Certain | `wordpress`, `php` | | `wp-xmlrpc` | WordPress XML-RPC Abuse | XML-RPC multicall brute-force and pingback abuse | Medium | Firm | `wordpress`, `php` | | `wp-ajax-exposure` | WordPress AJAX Action Exposure | Publicly accessible AJAX actions from plugins | High | Firm | `wordpress`, `php` | | `drupal-misconfig` | Drupal Misconfiguration | Exposed config files, update scripts, installer | High | Firm | `drupal`, `php` | | `drupal-user-enum` | Drupal User Enumeration | User enumeration via user profiles and JSON:API | Info | Certain | `drupal`, `php` | | `joomla-misconfig` | Joomla Misconfiguration | Exposed config backups, log/temp dirs, debug settings | High | Firm | `joomla`, `php` | | `joomla-user-enum` | Joomla User Enumeration | User enumeration via registration, API, admin login | Info | Firm | `joomla`, `php` | | `magento-misconfig` | Magento Misconfiguration | Exposed setup wizard, downloader, version files | High | Firm | `magento`, `php` | | `cms-installer-exposure` | CMS Installer Exposure | Exposed WordPress, Drupal, and Joomla install wizards | Critical | Firm | `wordpress`, `drupal`, `joomla` | #### Adobe Experience Manager (AEM) Every module gates on a confirmed AEM instance (`passive-aem-fingerprint`), is detection-only, and confirms across multiple rounds. | Module ID | Name | Description | Severity | Confidence | Tags | | ----------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------------------- | | `aem-dispatcher-bypass` | AEM Dispatcher ACL Bypass | Differential confirmation: a protected servlet blocked on the direct path but reachable through a path-normalization trick | High | Certain | `aem`, `adobe`, `dispatcher`, `cms` | | `aem-sensitive-servlet` | AEM Sensitive Servlet Disclosure | Info disclosure via AEM servlets (QueryBuilder password-hash leak, GQL/DefaultGetServlet dumps, userinfo, loginstatus, truststore), incl. dispatcher-bypass reachability | High | Firm | `aem`, `adobe`, `info-disclosure`, `cms` | | `aem-console-exposure` | AEM Console & Admin Panel Exposure | Internet-reachable AEM developer/admin consoles (CRXDE Lite, CRX Package Manager, CRX Explorer, admin dashboards) and ACL bypasses to them | High | Firm | `aem`, `adobe`, `misconfiguration`, `cms` | | `aem-content-discovery` | AEM Repository Content Discovery | Routing-aware JCR enumeration via Sling selectors (`.1.json`) and QueryBuilder predicates, harvesting config secrets, user accounts, and writable nodes (`rep:password` → Critical) | High | Firm | `aem`, `adobe`, `info-disclosure`, `jcr`, `cms` | | `aem-default-credentials` | AEM Default Credentials | AEM default and demo-account credentials on the Granite login and Felix Web Console | High | Certain | `aem`, `adobe`, `default-credentials`, `cms` | | `aem-cloudsettings-injection` | AEM Cloud Settings Node Write / EL Injection | BulkImportConfigServlet pre-auth node write (CVE-2025-54246) and the EL injection it enables (CVE-2025-54247/54248), proven with a benign marker / `7*7` read-back | Critical | Firm | `aem`, `adobe`, `el-injection`, `rce`, `cms` | | `aem-xss` | AEM Reflected XSS | Reflected XSS in AEM-specific sinks (childlist selector, CRXDE setPreferences, DAM merge-metadata, WCM contentfinder) with headless popup confirmation | High | Firm | `aem`, `adobe`, `xss`, `cms` | | `aem-ssrf` | AEM SSRF-Capable Proxy Servlet Exposure | Reachable AEM proxy/fetch servlets known for SSRF (ContentInsight proxy, opensocial/shindig, SalesforceSecretServlet, accesstoken/verify) — surface detection, no OAST fired | Medium | Tentative | `aem`, `adobe`, `ssrf`, `cms` | | `aem-oob-injection` | AEM Blind SSRF / XXE (Out-of-Band) | Out-of-band probes for the AccessTokenServlet full-read SSRF (CVE-2025-54249) and CRX Package Manager blind XXE (CVE-2025-54251), confirmed by collaborator callback | High | Firm | `aem`, `adobe`, `ssrf`, `xxe`, `oob`, `cms` | | `aem-xxe` | AEM Adaptive Forms XXE | XML external-entity processing in Adaptive Forms `guideContainer` internalsubmit (CVE-2019-8086) via an exploit-free internal-entity expansion proof | High | Firm | `aem`, `adobe`, `xxe`, `cms` | | `aem-rce-surface` | AEM RCE-Capable Surface Exposure | Exposed AEM code-execution surfaces (Groovy Console, ACS Fiddle, Forms GetDocumentServlet, WebDAV PUT) — detection only, no exec/write payload sent | Critical | Firm | `aem`, `adobe`, `rce`, `cms` | #### SaaS Platforms (Salesforce / ServiceNow / Power Pages) Each family gates on a live per-vendor fingerprint, pairs a catch-all negative control with the positive, and confirms across multiple rounds. | Module ID | Name | Description | Severity | Confidence | Tags | | --------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ------------------------------------------------------------- | | `salesforce-aura-object-exposure` | Salesforce Aura Guest Object Enumeration | An Experience Cloud Guest user can invoke the Aura `getConfigData` action and enumerate accessible SObjects (incl. custom `__c`) — an over-permissive Guest profile | Medium | Firm | `salesforce`, `aura`, `lightning`, `info-disclosure` | | `salesforce-aura-record-exposure` | Salesforce Aura Guest Record Exposure | Unauthenticated read of SObject records (User, Contact, Case, Lead, custom `__c`) through the Aura `getItems` action | High | Certain | `salesforce`, `aura`, `lightning`, `idor`, `info-disclosure` | | `salesforce-aura-apex-execution` | Salesforce Aura Guest Apex Execution | An unauthenticated Guest user can invoke `@AuraEnabled` Apex via `ApexActionController.execute` — the pivot for guest-driven SSRF, DML, and exfiltration | High | Firm | `salesforce`, `aura`, `lightning`, `apex`, `ssrf` | | `salesforce-lightning-debug-mode` | Salesforce Lightning Debug Mode Enabled | A Lightning/Experience Cloud site served in an Aura debug mode (`PRODDEBUG`/`DEV`/`JSTESTDEBUG`) leaking un-minified code and backend stacktraces | Medium | Firm | `salesforce`, `aura`, `lightning`, `debug`, `info-disclosure` | | `servicenow-widget-data-exposure` | ServiceNow Public Widget Data Exposure | Unauthenticated read of arbitrary ServiceNow tables (`sys_user`, `incident`, `cmdb_ci`, attachments, OAuth) via the public Simple/Unordered List widgets | High | Certain | `servicenow`, `idor`, `info-disclosure`, `api` | | `servicenow-kb-widget-exposure` | ServiceNow Knowledge Base Widget Exposure | Unauthenticated read of KB article contents via the public KB Article Page widget (not covered by the 2023 ACL hardening — KB access is gated by User Criteria, not ACLs) | High | Certain | `servicenow`, `idor`, `info-disclosure`, `api` | | `powerpages-dataverse-exposure` | Power Pages Dataverse Web API Data Exposure | Unauthenticated read of Microsoft Dataverse tables (contacts, accounts, leads, cases, custom) via a Power Pages portal's `/_api/` Web API | High | Certain | `powerpages`, `dataverse`, `idor`, `info-disclosure`, `api` | #### Model Context Protocol (MCP) Audited against OWASP; every active probe gates on a reachable MCP endpoint (`passive-mcp-endpoint-detect`). | Module ID | Name | Description | Severity | Confidence | Tags | | --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------------- | | `mcp-server-probe` | MCP Server Probe | Probes for exposed MCP servers, enumerates tools, and attempts unauthenticated invocation | High | Firm | `mcp`, `api-security`, `misconfiguration` | | `mcp-tool-fuzz` | MCP Tool Argument Fuzzer | Fuzzes every enumerable MCP tool's arguments for OS command injection, LFI, SSRF (OAST), and prompt injection | High | Firm | `mcp`, `rce`, `lfi`, `ssrf`, `injection` | | `mcp-resource-fuzz` | MCP Resource URI Fuzzer | Probes `resources/read` with `file://`, cloud-metadata, path-traversal, and OAST SSRF payloads | High | Firm | `mcp`, `lfi`, `ssrf`, `path-traversal` | | `mcp-prompt-fuzz` | MCP Prompt Argument Fuzzer | Fuzzes `prompts/get` arguments for SSTI, command injection, and reflective prompt injection | High | Firm | `mcp`, `ssti`, `rce`, `prompt-injection` | | `mcp-completion-enum` | MCP Completion Enumeration | Uses `completion/complete` to leak valid resource URIs and prompt argument values | Medium | Firm | `mcp`, `info-disclosure`, `enumeration` | | `mcp-method-enum` | MCP JSON-RPC Method Enumeration | Wordlist enumeration of undocumented JSON-RPC methods on MCP servers | Medium | Firm | `mcp`, `enumeration`, `info-disclosure` | | `mcp-session-checks` | MCP Session Hardening Checks | Tests `Mcp-Session-Id` entropy, attacker-supplied SID acceptance (fixation), and post-handshake reuse | Medium | Firm | `mcp`, `session`, `auth-bypass` | | `mcp-batch-abuse` | MCP JSON-RPC Batch Abuse | Tests JSON-RPC batch handling: a smuggled `tools/call` inside an `initialize` batch | High | Firm | `mcp`, `auth-bypass` | | `mcp-origin-rebinding` | MCP Origin / DNS-Rebinding Check | Verifies an MCP server enforces Origin validation on streamable HTTP transports | High | Firm | `mcp`, `dns-rebinding`, `origin` | | `mcp-tool-definition-drift` | MCP Tool Definition Drift | Detects MCP servers serving mutating, non-deterministic tool definitions (rug-pull risk) | Medium | Tentative | `mcp`, `rug-pull`, `integrity` | | `mcp-dos-amplification` | MCP DoS Amplification | An unbounded oversized `ping` batch processed with no size or rate limit | Medium | Firm | `mcp`, `dos`, `rate-limit` | *** ## Passive Modules (116) Passive modules analyze existing request/response pairs without sending new traffic. ### XSS | Module ID | Name | Description | Severity | Confidence | Tags | | ---------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------- | ---------- | --------------------------------------------- | | `dom-xss-detect` | DOM XSS Detect | DOM XSS source-to-sink data flows (location.hash, innerHTML, eval, document.write) | Low | Firm | `xss` | | `css-injection-detect` | CSS Injection | Request values reflected into a `